arnaucube
arnaucube/thoughtsPublic Self-hosted repository
thoughts/node_modules/mongoose/lib/document.js
node_modules/mongoose/lib/document.jsRaw
   1/*!
   2 * Module dependencies.
   3 */
   4
   5var EventEmitter = require('events').EventEmitter
   6  , setMaxListeners = EventEmitter.prototype.setMaxListeners
   7  , MongooseError = require('./error')
   8  , MixedSchema = require('./schema/mixed')
   9  , Schema = require('./schema')
  10  , ValidatorError = require('./schematype').ValidatorError
  11  , utils = require('./utils')
  12  , clone = utils.clone
  13  , isMongooseObject = utils.isMongooseObject
  14  , inspect = require('util').inspect
  15  , ElemMatchError = MongooseError.ElemMatchError
  16  , ValidationError = MongooseError.ValidationError
  17  , DocumentError = MongooseError.DocumentError
  18  , InternalCache = require('./internal')
  19  , deepEqual = utils.deepEqual
  20  , hooks = require('hooks')
  21  , DocumentArray
  22  , MongooseArray
  23  , Embedded
  24
  25/**
  26 * Document constructor.
  27 *
  28 * @param {Object} obj the values to set
  29 * @param {Object} [opts] optional object containing the fields which were selected in the query returning this document and any populated paths data
  30 * @param {Boolean} [skipId] bool, should we auto create an ObjectId _id
  31 * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
  32 * @event `init`: Emitted on a document after it has was retreived from the db and fully hydrated by Mongoose.
  33 * @event `save`: Emitted when the document is successfully saved
  34 * @api private
  35 */
  36
  37function Document (obj, fields, skipId) {
  38  this.$__ = new InternalCache;
  39  this.isNew = true;
  40  this.errors = undefined;
  41
  42  var schema = this.schema;
  43
  44  if ('boolean' === typeof fields) {
  45    this.$__.strictMode = fields;
  46    fields = undefined;
  47  } else {
  48    this.$__.strictMode = schema.options && schema.options.strict;
  49    this.$__.selected = fields;
  50  }
  51
  52  var required = schema.requiredPaths();
  53  for (var i = 0; i < required.length; ++i) {
  54    this.$__.activePaths.require(required[i]);
  55  }
  56
  57  setMaxListeners.call(this, 0);
  58  this._doc = this.$__buildDoc(obj, fields, skipId);
  59
  60  if (obj) {
  61    this.set(obj, undefined, true);
  62  }
  63
  64  this.$__registerHooks();
  65}
  66
  67/*!
  68 * Inherit from EventEmitter.
  69 */
  70
  71Document.prototype.__proto__ = EventEmitter.prototype;
  72
  73/**
  74 * The documents schema.
  75 *
  76 * @api public
  77 * @property schema
  78 */
  79
  80Document.prototype.schema;
  81
  82/**
  83 * Boolean flag specifying if the document is new.
  84 *
  85 * @api public
  86 * @property isNew
  87 */
  88
  89Document.prototype.isNew;
  90
  91/**
  92 * The string version of this documents _id.
  93 *
  94 * ####Note:
  95 *
  96 * This getter exists on all documents by default. The getter can be disabled by setting the `id` [option](/docs/guide.html#id) of its `Schema` to false at construction time.
  97 *
  98 *     new Schema({ name: String }, { id: false });
  99 *
 100 * @api public
 101 * @see Schema options /docs/guide.html#options
 102 * @property id
 103 */
 104
 105Document.prototype.id;
 106
 107/**
 108 * Hash containing current validation errors.
 109 *
 110 * @api public
 111 * @property errors
 112 */
 113
 114Document.prototype.errors;
 115
 116/**
 117 * Builds the default doc structure
 118 *
 119 * @param {Object} obj
 120 * @param {Object} [fields]
 121 * @param {Boolean} [skipId]
 122 * @return {Object}
 123 * @api private
 124 * @method $__buildDoc
 125 * @memberOf Document
 126 */
 127
 128Document.prototype.$__buildDoc = function (obj, fields, skipId) {
 129  var doc = {}
 130    , self = this
 131    , exclude
 132    , keys
 133    , key
 134    , ki
 135
 136  // determine if this doc is a result of a query with
 137  // excluded fields
 138  if (fields && 'Object' === fields.constructor.name) {
 139    keys = Object.keys(fields);
 140    ki = keys.length;
 141
 142    while (ki--) {
 143      if ('_id' !== keys[ki]) {
 144        exclude = 0 === fields[keys[ki]];
 145        break;
 146      }
 147    }
 148  }
 149
 150  var paths = Object.keys(this.schema.paths)
 151    , plen = paths.length
 152    , ii = 0
 153
 154  for (; ii < plen; ++ii) {
 155    var p = paths[ii];
 156
 157    if ('_id' == p) {
 158      if (skipId) continue;
 159      if (obj && '_id' in obj) continue;
 160    }
 161
 162    var type = this.schema.paths[p]
 163      , path = p.split('.')
 164      , len = path.length
 165      , last = len-1
 166      , curPath = ''
 167      , doc_ = doc
 168      , i = 0
 169
 170    for (; i < len; ++i) {
 171      var piece = path[i]
 172        , def
 173
 174      // support excluding intermediary levels
 175      if (exclude) {
 176        curPath += piece;
 177        if (curPath in fields) break;
 178        curPath += '.';
 179      }
 180
 181      if (i === last) {
 182        if (fields) {
 183          if (exclude) {
 184            // apply defaults to all non-excluded fields
 185            if (p in fields) continue;
 186
 187            def = type.getDefault(self, true);
 188            if ('undefined' !== typeof def) {
 189              doc_[piece] = def;
 190              self.$__.activePaths.default(p);
 191            }
 192
 193          } else if (p in fields) {
 194            // selected field
 195            def = type.getDefault(self, true);
 196            if ('undefined' !== typeof def) {
 197              doc_[piece] = def;
 198              self.$__.activePaths.default(p);
 199            }
 200          }
 201        } else {
 202          def = type.getDefault(self, true);
 203          if ('undefined' !== typeof def) {
 204            doc_[piece] = def;
 205            self.$__.activePaths.default(p);
 206          }
 207        }
 208      } else {
 209        doc_ = doc_[piece] || (doc_[piece] = {});
 210      }
 211    }
 212  };
 213
 214  return doc;
 215};
 216
 217/**
 218 * Initializes the document without setters or marking anything modified.
 219 *
 220 * Called internally after a document is returned from mongodb.
 221 *
 222 * @param {Object} doc document returned by mongo
 223 * @param {Function} fn callback
 224 * @api private
 225 */
 226
 227Document.prototype.init = function (doc, opts, fn) {
 228  // do not prefix this method with $__ since its
 229  // used by public hooks
 230
 231  if ('function' == typeof opts) {
 232    fn = opts;
 233    opts = null;
 234  }
 235
 236  this.isNew = false;
 237
 238  // handle docs with populated paths
 239  if (doc._id && opts && opts.populated && opts.populated.length) {
 240    var id = String(doc._id);
 241    for (var i = 0; i < opts.populated.length; ++i) {
 242      var item = opts.populated[i];
 243      this.populated(item.path, item._docs[id], item);
 244    }
 245  }
 246
 247  init(this, doc, this._doc);
 248  this.$__storeShard();
 249
 250  this.emit('init', this);
 251  if (fn) fn(null);
 252  return this;
 253};
 254
 255/*!
 256 * Init helper.
 257 *
 258 * @param {Object} self document instance
 259 * @param {Object} obj raw mongodb doc
 260 * @param {Object} doc object we are initializing
 261 * @api private
 262 */
 263
 264function init (self, obj, doc, prefix) {
 265  prefix = prefix || '';
 266
 267  var keys = Object.keys(obj)
 268    , len = keys.length
 269    , schema
 270    , path
 271    , i;
 272
 273  while (len--) {
 274    i = keys[len];
 275    path = prefix + i;
 276    schema = self.schema.path(path);
 277
 278    if (!schema && utils.isObject(obj[i]) &&
 279        (!obj[i].constructor || 'Object' == obj[i].constructor.name)) {
 280      // assume nested object
 281      if (!doc[i]) doc[i] = {};
 282      init(self, obj[i], doc[i], path + '.');
 283    } else {
 284      if (obj[i] === null) {
 285        doc[i] = null;
 286      } else if (obj[i] !== undefined) {
 287        if (schema) {
 288          self.$__try(function(){
 289            doc[i] = schema.cast(obj[i], self, true);
 290          });
 291        } else {
 292          doc[i] = obj[i];
 293        }
 294      }
 295      // mark as hydrated
 296      self.$__.activePaths.init(path);
 297    }
 298  }
 299};
 300
 301/**
 302 * Stores the current values of the shard keys.
 303 *
 304 * ####Note:
 305 *
 306 * _Shard key values do not / are not allowed to change._
 307 *
 308 * @api private
 309 * @method $__storeShard
 310 * @memberOf Document
 311 */
 312
 313Document.prototype.$__storeShard = function () {
 314  // backwards compat
 315  var key = this.schema.options.shardKey || this.schema.options.shardkey;
 316  if (!(key && 'Object' == key.constructor.name)) return;
 317
 318  var orig = this.$__.shardval = {}
 319    , paths = Object.keys(key)
 320    , len = paths.length
 321    , val
 322
 323  for (var i = 0; i < len; ++i) {
 324    val = this.getValue(paths[i]);
 325    if (isMongooseObject(val)) {
 326      orig[paths[i]] = val.toObject({ depopulate: true })
 327    } else if (null != val && val.valueOf) {
 328      orig[paths[i]] = val.valueOf();
 329    } else {
 330      orig[paths[i]] = val;
 331    }
 332  }
 333}
 334
 335/*!
 336 * Set up middleware support
 337 */
 338
 339for (var k in hooks) {
 340  Document.prototype[k] = Document[k] = hooks[k];
 341}
 342
 343/**
 344 * Sends an update command with this document `_id` as the query selector.
 345 *
 346 * ####Example:
 347 *
 348 *     weirdCar.update({$inc: {wheels:1}}, { w: 1 }, callback);
 349 *
 350 * ####Valid options:
 351 *
 352 *  - same as in [Model.update](#model_Model.update)
 353 *
 354 * @see Model.update #model_Model.update
 355 * @param {Object} doc
 356 * @param {Object} options
 357 * @param {Function} callback
 358 * @return {Query}
 359 * @api public
 360 */
 361
 362Document.prototype.update = function update () {
 363  var args = utils.args(arguments);
 364  args.unshift({_id: this._id});
 365  return this.constructor.update.apply(this.constructor, args);
 366}
 367
 368/**
 369 * Sets the value of a path, or many paths.
 370 *
 371 * ####Example:
 372 *
 373 *     // path, value
 374 *     doc.set(path, value)
 375 *
 376 *     // object
 377 *     doc.set({
 378 *         path  : value
 379 *       , path2 : {
 380 *            path  : value
 381 *         }
 382 *     })
 383 *
 384 *     // only-the-fly cast to number
 385 *     doc.set(path, value, Number)
 386 *
 387 *     // only-the-fly cast to string
 388 *     doc.set(path, value, String)
 389 *
 390 *     // changing strict mode behavior
 391 *     doc.set(path, value, { strict: false });
 392 *
 393 * @param {String|Object} path path or object of key/vals to set
 394 * @param {Any} val the value to set
 395 * @param {Schema|String|Number|Buffer|etc..} [type] optionally specify a type for "on-the-fly" attributes
 396 * @param {Object} [options] optionally specify options that modify the behavior of the set
 397 * @api public
 398 */
 399
 400Document.prototype.set = function (path, val, type, options) {
 401  if (type && 'Object' == type.constructor.name) {
 402    options = type;
 403    type = undefined;
 404  }
 405
 406  var merge = options && options.merge
 407    , adhoc = type && true !== type
 408    , constructing = true === type
 409    , adhocs
 410
 411  var strict = options && 'strict' in options
 412    ? options.strict
 413    : this.$__.strictMode;
 414
 415  if (adhoc) {
 416    adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {});
 417    adhocs[path] = Schema.interpretAsType(path, type);
 418  }
 419
 420  if ('string' !== typeof path) {
 421    // new Document({ key: val })
 422
 423    if (null === path || undefined === path) {
 424      var _ = path;
 425      path = val;
 426      val = _;
 427
 428    } else {
 429      var prefix = val
 430        ? val + '.'
 431        : '';
 432
 433      if (path instanceof Document) path = path._doc;
 434
 435      var keys = Object.keys(path)
 436        , i = keys.length
 437        , pathtype
 438        , key
 439
 440
 441      while (i--) {
 442        key = keys[i];
 443        pathtype = this.schema.pathType(prefix + key);
 444        if (null != path[key]
 445            // need to know if plain object - no Buffer, ObjectId, ref, etc
 446            && utils.isObject(path[key])
 447            && (!path[key].constructor || 'Object' == path[key].constructor.name)
 448            && 'virtual' != pathtype
 449            && !(this.$__path(prefix + key) instanceof MixedSchema)
 450            && !(this.schema.paths[key] && this.schema.paths[key].options.ref)
 451          ) {
 452          this.set(path[key], prefix + key, constructing);
 453        } else if (strict) {
 454          if ('real' === pathtype || 'virtual' === pathtype) {
 455            this.set(prefix + key, path[key], constructing);
 456          } else if ('throw' == strict) {
 457            throw new Error("Field `" + key + "` is not in schema.");
 458          }
 459        } else if (undefined !== path[key]) {
 460          this.set(prefix + key, path[key], constructing);
 461        }
 462      }
 463
 464      return this;
 465    }
 466  }
 467
 468  // ensure _strict is honored for obj props
 469  // docschema = new Schema({ path: { nest: 'string' }})
 470  // doc.set('path', obj);
 471  var pathType = this.schema.pathType(path);
 472  if ('nested' == pathType && val && utils.isObject(val) &&
 473      (!val.constructor || 'Object' == val.constructor.name)) {
 474    if (!merge) this.setValue(path, null);
 475    this.set(val, path, constructing);
 476    return this;
 477  }
 478
 479  var schema;
 480  var parts = path.split('.');
 481
 482  if ('adhocOrUndefined' == pathType && strict) {
 483
 484    // check for roots that are Mixed types
 485    var mixed;
 486
 487    for (var i = 0; i < parts.length; ++i) {
 488      var subpath = parts.slice(0, i+1).join('.');
 489      schema = this.schema.path(subpath);
 490      if (schema instanceof MixedSchema) {
 491        // allow changes to sub paths of mixed types
 492        mixed = true;
 493        break;
 494      }
 495    }
 496
 497    if (!mixed) {
 498      if ('throw' == strict) {
 499        throw new Error("Field `" + path + "` is not in schema.");
 500      }
 501      return this;
 502    }
 503
 504  } else if ('virtual' == pathType) {
 505    schema = this.schema.virtualpath(path);
 506    schema.applySetters(val, this);
 507    return this;
 508  } else {
 509    schema = this.$__path(path);
 510  }
 511
 512  var pathToMark;
 513
 514  // When using the $set operator the path to the field must already exist.
 515  // Else mongodb throws: "LEFT_SUBFIELD only supports Object"
 516
 517  if (parts.length <= 1) {
 518    pathToMark = path;
 519  } else {
 520    for (var i = 0; i < parts.length; ++i) {
 521      var subpath = parts.slice(0, i+1).join('.');
 522      if (this.isDirectModified(subpath) // earlier prefixes that are already
 523                                         // marked as dirty have precedence
 524          || this.get(subpath) === null) {
 525        pathToMark = subpath;
 526        break;
 527      }
 528    }
 529
 530    if (!pathToMark) pathToMark = path;
 531  }
 532
 533  // if this doc is being constructed we should not trigger getters
 534  var priorVal = constructing
 535    ? undefined
 536    : this.get(path);
 537
 538  if (!schema || undefined === val) {
 539    this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
 540    return this;
 541  }
 542
 543  var self = this;
 544  var shouldSet = this.$__try(function(){
 545    val = schema.applySetters(val, self, false, priorVal);
 546  });
 547
 548  if (shouldSet) {
 549    this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
 550  }
 551
 552  return this;
 553}
 554
 555/**
 556 * Determine if we should mark this change as modified.
 557 *
 558 * @return {Boolean}
 559 * @api private
 560 * @method $__shouldModify
 561 * @memberOf Document
 562 */
 563
 564Document.prototype.$__shouldModify = function (
 565    pathToMark, path, constructing, parts, schema, val, priorVal) {
 566
 567  if (this.isNew) return true;
 568  if (this.isDirectModified(pathToMark)) return false;
 569
 570  if (undefined === val && !this.isSelected(path)) {
 571    // when a path is not selected in a query, its initial
 572    // value will be undefined.
 573    return true;
 574  }
 575
 576  if (undefined === val && path in this.$__.activePaths.states.default) {
 577    // we're just unsetting the default value which was never saved
 578    return false;
 579  }
 580
 581  if (!deepEqual(val, priorVal || this.get(path))) {
 582    return true;
 583  }
 584
 585  if (!constructing &&
 586      null != val &&
 587      path in this.$__.activePaths.states.default &&
 588      deepEqual(val, schema.getDefault(this, constructing))) {
 589    // a path with a default was $unset on the server
 590    // and the user is setting it to the same value again
 591    return true;
 592  }
 593
 594  return false;
 595}
 596
 597/**
 598 * Handles the actual setting of the value and marking the path modified if appropriate.
 599 *
 600 * @api private
 601 * @method $__set
 602 * @memberOf Document
 603 */
 604
 605Document.prototype.$__set = function (
 606    pathToMark, path, constructing, parts, schema, val, priorVal) {
 607
 608  var shouldModify = this.$__shouldModify.apply(this, arguments);
 609
 610  if (shouldModify) {
 611    this.markModified(pathToMark, val);
 612
 613    // handle directly setting arrays (gh-1126)
 614    MongooseArray || (MongooseArray = require('./types/array'));
 615    if (val instanceof MongooseArray) {
 616      val._registerAtomic('$set', val);
 617    }
 618  }
 619
 620  var obj = this._doc
 621    , i = 0
 622    , l = parts.length
 623
 624  for (; i < l; i++) {
 625    var next = i + 1
 626      , last = next === l;
 627
 628    if (last) {
 629      obj[parts[i]] = val;
 630    } else {
 631      if (obj[parts[i]] && 'Object' === obj[parts[i]].constructor.name) {
 632        obj = obj[parts[i]];
 633      } else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) {
 634        obj = obj[parts[i]];
 635      } else {
 636        obj = obj[parts[i]] = {};
 637      }
 638    }
 639  }
 640}
 641
 642/**
 643 * Gets a raw value from a path (no getters)
 644 *
 645 * @param {String} path
 646 * @api private
 647 */
 648
 649Document.prototype.getValue = function (path) {
 650  return utils.getValue(path, this._doc);
 651}
 652
 653/**
 654 * Sets a raw value for a path (no casting, setters, transformations)
 655 *
 656 * @param {String} path
 657 * @param {Object} value
 658 * @api private
 659 */
 660
 661Document.prototype.setValue = function (path, val) {
 662  utils.setValue(path, val, this._doc);
 663  return this;
 664}
 665
 666/**
 667 * Returns the value of a path.
 668 *
 669 * ####Example
 670 *
 671 *     // path
 672 *     doc.get('age') // 47
 673 *
 674 *     // dynamic casting to a string
 675 *     doc.get('age', String) // "47"
 676 *
 677 * @param {String} path
 678 * @param {Schema|String|Number|Buffer|etc..} [type] optionally specify a type for on-the-fly attributes
 679 * @api public
 680 */
 681
 682Document.prototype.get = function (path, type) {
 683  var adhocs;
 684  if (type) {
 685    adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {});
 686    adhocs[path] = Schema.interpretAsType(path, type);
 687  }
 688
 689  var schema = this.$__path(path) || this.schema.virtualpath(path)
 690    , pieces = path.split('.')
 691    , obj = this._doc;
 692
 693  for (var i = 0, l = pieces.length; i < l; i++) {
 694    obj = undefined === obj || null === obj
 695      ? undefined
 696      : obj[pieces[i]];
 697  }
 698
 699  if (schema) {
 700    obj = schema.applyGetters(obj, this);
 701  }
 702
 703  return obj;
 704};
 705
 706/**
 707 * Returns the schematype for the given `path`.
 708 *
 709 * @param {String} path
 710 * @api private
 711 * @method $__path
 712 * @memberOf Document
 713 */
 714
 715Document.prototype.$__path = function (path) {
 716  var adhocs = this.$__.adhocPaths
 717    , adhocType = adhocs && adhocs[path];
 718
 719  if (adhocType) {
 720    return adhocType;
 721  } else {
 722    return this.schema.path(path);
 723  }
 724};
 725
 726/**
 727 * Marks the path as having pending changes to write to the db.
 728 *
 729 * _Very helpful when using [Mixed](./schematypes.html#mixed) types._
 730 *
 731 * ####Example:
 732 *
 733 *     doc.mixed.type = 'changed';
 734 *     doc.markModified('mixed.type');
 735 *     doc.save() // changes to mixed.type are now persisted
 736 *
 737 * @param {String} path the path to mark modified
 738 * @api public
 739 */
 740
 741Document.prototype.markModified = function (path) {
 742  this.$__.activePaths.modify(path);
 743}
 744
 745/**
 746 * Catches errors that occur during execution of `fn` and stores them to later be passed when `save()` is executed.
 747 *
 748 * @param {Function} fn function to execute
 749 * @param {Object} scope the scope with which to call fn
 750 * @api private
 751 * @method $__try
 752 * @memberOf Document
 753 */
 754
 755Document.prototype.$__try = function (fn, scope) {
 756  var res;
 757  try {
 758    fn.call(scope);
 759    res = true;
 760  } catch (e) {
 761    this.$__error(e);
 762    res = false;
 763  }
 764  return res;
 765};
 766
 767/**
 768 * Returns the list of paths that have been modified.
 769 *
 770 * @return {Array}
 771 * @api public
 772 */
 773
 774Document.prototype.modifiedPaths = function () {
 775  var directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
 776
 777  return directModifiedPaths.reduce(function (list, path) {
 778    var parts = path.split('.');
 779    return list.concat(parts.reduce(function (chains, part, i) {
 780      return chains.concat(parts.slice(0, i).concat(part).join('.'));
 781    }, []));
 782  }, []);
 783};
 784
 785/**
 786 * Returns true if this document was modified, else false.
 787 *
 788 * If `path` is given, checks if a path or any full path containing `path` as part of its path chain has been modified.
 789 *
 790 * ####Example
 791 *
 792 *     doc.set('documents.0.title', 'changed');
 793 *     doc.isModified()                    // true
 794 *     doc.isModified('documents')         // true
 795 *     doc.isModified('documents.0.title') // true
 796 *     doc.isDirectModified('documents')   // false
 797 *
 798 * @param {String} [path] optional
 799 * @return {Boolean}
 800 * @api public
 801 */
 802
 803Document.prototype.isModified = function (path) {
 804  return path
 805    ? !!~this.modifiedPaths().indexOf(path)
 806    : this.$__.activePaths.some('modify');
 807};
 808
 809/**
 810 * Returns true if `path` was directly set and modified, else false.
 811 *
 812 * ####Example
 813 *
 814 *     doc.set('documents.0.title', 'changed');
 815 *     doc.isDirectModified('documents.0.title') // true
 816 *     doc.isDirectModified('documents') // false
 817 *
 818 * @param {String} path
 819 * @return {Boolean}
 820 * @api public
 821 */
 822
 823Document.prototype.isDirectModified = function (path) {
 824  return (path in this.$__.activePaths.states.modify);
 825};
 826
 827/**
 828 * Checks if `path` was initialized.
 829 *
 830 * @param {String} path
 831 * @return {Boolean}
 832 * @api public
 833 */
 834
 835Document.prototype.isInit = function (path) {
 836  return (path in this.$__.activePaths.states.init);
 837};
 838
 839/**
 840 * Checks if `path` was selected in the source query which initialized this document.
 841 *
 842 * ####Example
 843 *
 844 *     Thing.findOne().select('name').exec(function (err, doc) {
 845 *        doc.isSelected('name') // true
 846 *        doc.isSelected('age')  // false
 847 *     })
 848 *
 849 * @param {String} path
 850 * @return {Boolean}
 851 * @api public
 852 */
 853
 854Document.prototype.isSelected = function isSelected (path) {
 855  if (this.$__.selected) {
 856
 857    if ('_id' === path) {
 858      return 0 !== this.$__.selected._id;
 859    }
 860
 861    var paths = Object.keys(this.$__.selected)
 862      , i = paths.length
 863      , inclusive = false
 864      , cur
 865
 866    if (1 === i && '_id' === paths[0]) {
 867      // only _id was selected.
 868      return 0 === this.$__.selected._id;
 869    }
 870
 871    while (i--) {
 872      cur = paths[i];
 873      if ('_id' == cur) continue;
 874      inclusive = !! this.$__.selected[cur];
 875      break;
 876    }
 877
 878    if (path in this.$__.selected) {
 879      return inclusive;
 880    }
 881
 882    i = paths.length;
 883    var pathDot = path + '.';
 884
 885    while (i--) {
 886      cur = paths[i];
 887      if ('_id' == cur) continue;
 888
 889      if (0 === cur.indexOf(pathDot)) {
 890        return inclusive;
 891      }
 892
 893      if (0 === pathDot.indexOf(cur + '.')) {
 894        return inclusive;
 895      }
 896    }
 897
 898    return ! inclusive;
 899  }
 900
 901  return true;
 902}
 903
 904/**
 905 * Executes registered validation rules for this document.
 906 *
 907 * ####Note:
 908 *
 909 * This method is called `pre` save and if a validation rule is violated, [save](#model_Model-save) is aborted and the error is returned to your `callback`.
 910 *
 911 * ####Example:
 912 *
 913 *     doc.validate(function (err) {
 914 *       if (err) handleError(err);
 915 *       else // validation passed
 916 *     });
 917 *
 918 * @param {Function} cb called after validation completes, passing an error if one occurred
 919 * @api public
 920 */
 921
 922Document.prototype.validate = function (cb) {
 923  var self = this
 924
 925  // only validate required fields when necessary
 926  var paths = Object.keys(this.$__.activePaths.states.require).filter(function (path) {
 927    if (!self.isSelected(path) && !self.isModified(path)) return false;
 928    return true;
 929  });
 930
 931  paths = paths.concat(Object.keys(this.$__.activePaths.states.init));
 932  paths = paths.concat(Object.keys(this.$__.activePaths.states.modify));
 933  paths = paths.concat(Object.keys(this.$__.activePaths.states.default));
 934
 935  if (0 === paths.length) {
 936    complete();
 937    return this;
 938  }
 939
 940  var validating = {}
 941    , total = 0;
 942
 943  paths.forEach(validatePath);
 944  return this;
 945
 946  function validatePath (path) {
 947    if (validating[path]) return;
 948
 949    validating[path] = true;
 950    total++;
 951
 952    process.nextTick(function(){
 953      var p = self.schema.path(path);
 954      if (!p) return --total || complete();
 955
 956      var val = self.getValue(path);
 957      p.doValidate(val, function (err) {
 958        if (err) {
 959          self.invalidate(
 960              path
 961            , err
 962            , undefined
 963            , true // embedded docs
 964            );
 965        }
 966        --total || complete();
 967      }, self);
 968    });
 969  }
 970
 971  function complete () {
 972    var err = self.$__.validationError;
 973    self.$__.validationError = undefined;
 974    self.emit('validate', self);
 975    cb(err);
 976  }
 977};
 978
 979/**
 980 * Marks a path as invalid, causing validation to fail.
 981 *
 982 * @param {String} path the field to invalidate
 983 * @param {String|Error} err the error which states the reason `path` was invalid
 984 * @param {Object|String|Number|any} value optional invalid value
 985 * @api public
 986 */
 987
 988Document.prototype.invalidate = function (path, err, val) {
 989  if (!this.$__.validationError) {
 990    this.$__.validationError = new ValidationError(this);
 991  }
 992
 993  if (!err || 'string' === typeof err) {
 994    // sniffing arguments:
 995    // need to handle case where user does not pass value
 996    // so our error message is cleaner
 997    err = 2 < arguments.length
 998      ? new ValidatorError(path, err, val)
 999      : new ValidatorError(path, err)
1000  }
1001
1002  this.$__.validationError.errors[path] = err;
1003}
1004
1005/**
1006 * Resets the internal modified state of this document.
1007 *
1008 * @api private
1009 * @return {Document}
1010 * @method $__reset
1011 * @memberOf Document
1012 */
1013
1014Document.prototype.$__reset = function reset () {
1015  var self = this;
1016  DocumentArray || (DocumentArray = require('./types/documentarray'));
1017
1018  this.$__.activePaths
1019  .map('init', 'modify', function (i) {
1020    return self.getValue(i);
1021  })
1022  .filter(function (val) {
1023    return val && val instanceof DocumentArray && val.length;
1024  })
1025  .forEach(function (array) {
1026    var i = array.length;
1027    while (i--) {
1028      var doc = array[i];
1029      if (!doc) continue;
1030      doc.$__reset();
1031    }
1032  });
1033
1034  // clear atomics
1035  this.$__dirty().forEach(function (dirt) {
1036    var type = dirt.value;
1037    if (type && type._atomics) {
1038      type._atomics = {};
1039    }
1040  });
1041
1042  // Clear 'modify'('dirty') cache
1043  this.$__.activePaths.clear('modify');
1044  this.$__.validationError = undefined;
1045  this.errors = undefined;
1046  var self = this;
1047  this.schema.requiredPaths().forEach(function (path) {
1048    self.$__.activePaths.require(path);
1049  });
1050
1051  return this;
1052}
1053
1054/**
1055 * Returns this documents dirty paths / vals.
1056 *
1057 * @api private
1058 * @method $__dirty
1059 * @memberOf Document
1060 */
1061
1062Document.prototype.$__dirty = function () {
1063  var self = this;
1064
1065  var all = this.$__.activePaths.map('modify', function (path) {
1066    return { path: path
1067           , value: self.getValue(path)
1068           , schema: self.$__path(path) };
1069  });
1070
1071  // Sort dirty paths in a flat hierarchy.
1072  all.sort(function (a, b) {
1073    return (a.path < b.path ? -1 : (a.path > b.path ? 1 : 0));
1074  });
1075
1076  // Ignore "foo.a" if "foo" is dirty already.
1077  var minimal = []
1078    , lastPath
1079    , top;
1080
1081  all.forEach(function (item, i) {
1082    if (item.path.indexOf(lastPath) !== 0) {
1083      lastPath = item.path + '.';
1084      minimal.push(item);
1085      top = item;
1086    } else {
1087      // special case for top level MongooseArrays
1088      if (top.value && top.value._atomics && top.value.hasAtomics()) {
1089        // the `top` array itself and a sub path of `top` are being modified.
1090        // the only way to honor all of both modifications is through a $set
1091        // of entire array.
1092        top.value._atomics = {};
1093        top.value._atomics.$set = top.value;
1094      }
1095    }
1096  });
1097
1098  top = lastPath = null;
1099  return minimal;
1100}
1101
1102/*!
1103 * Compiles schemas.
1104 */
1105
1106function compile (tree, proto, prefix) {
1107  var keys = Object.keys(tree)
1108    , i = keys.length
1109    , limb
1110    , key;
1111
1112  while (i--) {
1113    key = keys[i];
1114    limb = tree[key];
1115
1116    define(key
1117        , (('Object' === limb.constructor.name
1118               && Object.keys(limb).length)
1119               && (!limb.type || limb.type.type)
1120               ? limb
1121               : null)
1122        , proto
1123        , prefix
1124        , keys);
1125  }
1126};
1127
1128/*!
1129 * Defines the accessor named prop on the incoming prototype.
1130 */
1131
1132function define (prop, subprops, prototype, prefix, keys) {
1133  var prefix = prefix || ''
1134    , path = (prefix ? prefix + '.' : '') + prop;
1135
1136  if (subprops) {
1137
1138    Object.defineProperty(prototype, prop, {
1139        enumerable: true
1140      , get: function () {
1141          if (!this.$__.getters)
1142            this.$__.getters = {};
1143
1144          if (!this.$__.getters[path]) {
1145            var nested = Object.create(this);
1146
1147            // save scope for nested getters/setters
1148            if (!prefix) nested.$__.scope = this;
1149
1150            // shadow inherited getters from sub-objects so
1151            // thing.nested.nested.nested... doesn't occur (gh-366)
1152            var i = 0
1153              , len = keys.length;
1154
1155            for (; i < len; ++i) {
1156              // over-write the parents getter without triggering it
1157              Object.defineProperty(nested, keys[i], {
1158                  enumerable: false   // It doesn't show up.
1159                , writable: true      // We can set it later.
1160                , configurable: true  // We can Object.defineProperty again.
1161                , value: undefined    // It shadows its parent.
1162              });
1163            }
1164
1165            nested.toObject = function () {
1166              return this.get(path);
1167            };
1168
1169            compile(subprops, nested, path);
1170            this.$__.getters[path] = nested;
1171          }
1172
1173          return this.$__.getters[path];
1174        }
1175      , set: function (v) {
1176          if (v instanceof Document) v = v.toObject();
1177          return (this.$__.scope || this).set(path, v);
1178        }
1179    });
1180
1181  } else {
1182
1183    Object.defineProperty(prototype, prop, {
1184        enumerable: true
1185      , get: function ( ) { return this.get.call(this.$__.scope || this, path); }
1186      , set: function (v) { return this.set.call(this.$__.scope || this, path, v); }
1187    });
1188  }
1189};
1190
1191/**
1192 * Assigns/compiles `schema` into this documents prototype.
1193 *
1194 * @param {Schema} schema
1195 * @api private
1196 * @method $__setSchema
1197 * @memberOf Document
1198 */
1199
1200Document.prototype.$__setSchema = function (schema) {
1201  compile(schema.tree, this);
1202  this.schema = schema;
1203}
1204
1205/**
1206 * Register default hooks
1207 *
1208 * @api private
1209 * @method $__registerHooks
1210 * @memberOf Document
1211 */
1212
1213Document.prototype.$__registerHooks = function () {
1214  if (!this.save) return;
1215
1216  DocumentArray || (DocumentArray = require('./types/documentarray'));
1217
1218  this.pre('save', function (next) {
1219    // validate all document arrays.
1220    // we keep the error semaphore to make sure we don't
1221    // call `save` unnecessarily (we only need 1 error)
1222    var subdocs = 0
1223      , error = false
1224      , self = this;
1225
1226    // check for DocumentArrays
1227    var arrays = this.$__.activePaths
1228    .map('init', 'modify', function (i) {
1229      return self.getValue(i);
1230    })
1231    .filter(function (val) {
1232      return val && val instanceof DocumentArray && val.length;
1233    });
1234
1235    if (!arrays.length)
1236      return next();
1237
1238    arrays.forEach(function (array) {
1239      if (error) return;
1240
1241      // handle sparse arrays by using for loop vs array.forEach
1242      // which skips the sparse elements
1243
1244      var len = array.length
1245      subdocs += len;
1246
1247      for (var i = 0; i < len; ++i) {
1248        if (error) break;
1249
1250        var doc = array[i];
1251        if (!doc) {
1252          --subdocs || next();
1253          continue;
1254        }
1255
1256        doc.save(handleSave);
1257      }
1258    });
1259
1260    function handleSave (err) {
1261      if (error) return;
1262
1263      if (err) {
1264        self.$__.validationError = undefined;
1265        return next(error = err);
1266      }
1267
1268      --subdocs || next();
1269    }
1270
1271  }, function (err) {
1272    // emit on the Model if listening
1273    if (this.constructor.listeners('error').length) {
1274      this.constructor.emit('error', err);
1275    } else {
1276      // emit on the connection
1277      if (!this.db.listeners('error').length) {
1278        err.stack = 'No listeners detected, throwing. '
1279                  + 'Consider adding an error listener to your connection.\n'
1280                  + err.stack
1281      }
1282      this.db.emit('error', err);
1283    }
1284  }).pre('save', function checkForExistingErrors (next) {
1285    // if any doc.set() calls failed
1286    var err = this.$__.saveError;
1287    if (err) {
1288      this.$__.saveError = null;
1289      next(err);
1290    } else {
1291      next();
1292    }
1293  }).pre('save', function validation (next) {
1294    return this.validate(next);
1295  });
1296
1297  // add user defined queues
1298  this.$__doQueue();
1299};
1300
1301/**
1302 * Registers an error
1303 *
1304 * @param {Error} err
1305 * @api private
1306 * @method $__error
1307 * @memberOf Document
1308 */
1309
1310Document.prototype.$__error = function (err) {
1311  this.$__.saveError = err;
1312  return this;
1313};
1314
1315/**
1316 * Executes methods queued from the Schema definition
1317 *
1318 * @api private
1319 * @method $__doQueue
1320 * @memberOf Document
1321 */
1322
1323Document.prototype.$__doQueue = function () {
1324  var q = this.schema && this.schema.callQueue;
1325  if (q) {
1326    for (var i = 0, l = q.length; i < l; i++) {
1327      this[q[i][0]].apply(this, q[i][1]);
1328    }
1329  }
1330  return this;
1331};
1332
1333/**
1334 * Converts this document into a plain javascript object, ready for storage in MongoDB.
1335 *
1336 * Buffers are converted to instances of [mongodb.Binary](http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html) for proper storage.
1337 *
1338 * ####Options:
1339 *
1340 * - `getters` apply all getters (path and virtual getters)
1341 * - `virtuals` apply virtual getters (can override `getters` option)
1342 * - `minimize` remove empty objects (defaults to true)
1343 * - `transform` a transform function to apply to the resulting document before returning
1344 *
1345 * ####Getters/Virtuals
1346 *
1347 * Example of only applying path getters
1348 *
1349 *     doc.toObject({ getters: true, virtuals: false })
1350 *
1351 * Example of only applying virtual getters
1352 *
1353 *     doc.toObject({ virtuals: true })
1354 *
1355 * Example of applying both path and virtual getters
1356 *
1357 *     doc.toObject({ getters: true })
1358 *
1359 * To apply these options to every document of your schema by default, set your [schemas](#schema_Schema) `toObject` option to the same argument.
1360 *
1361 *     schema.set('toObject', { virtuals: true })
1362 *
1363 * ####Transform
1364 *
1365 * We may need to perform a transformation of the resulting object based on some criteria, say to remove some sensitive information or return a custom object. In this case we set the optional `transform` function.
1366 *
1367 * Transform functions receive three arguments
1368 *
1369 *     function (doc, ret, options) {}
1370 *
1371 * - `doc` The mongoose document which is being converted
1372 * - `ret` The plain object representation which has been converted
1373 * - `options` The options in use (either schema options or the options passed inline)
1374 *
1375 * ####Example
1376 *
1377 *     // specify the transform schema option
1378 *     if (!schema.options.toObject) schema.options.toObject = {};
1379 *     schema.options.toObject.transform = function (doc, ret, options) {
1380 *       // remove the _id of every document before returning the result
1381 *       delete ret._id;
1382 *     }
1383 *
1384 *     // without the transformation in the schema
1385 *     doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
1386 *
1387 *     // with the transformation
1388 *     doc.toObject(); // { name: 'Wreck-it Ralph' }
1389 *
1390 * With transformations we can do a lot more than remove properties. We can even return completely new customized objects:
1391 *
1392 *     if (!schema.options.toObject) schema.options.toObject = {};
1393 *     schema.options.toObject.transform = function (doc, ret, options) {
1394 *       return { movie: ret.name }
1395 *     }
1396 *
1397 *     // without the transformation in the schema
1398 *     doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
1399 *
1400 *     // with the transformation
1401 *     doc.toObject(); // { movie: 'Wreck-it Ralph' }
1402 *
1403 * _Note: if a transform function returns `undefined`, the return value will be ignored._
1404 *
1405 * Transformations may also be applied inline, overridding any transform set in the options:
1406 *
1407 *     function xform (doc, ret, options) {
1408 *       return { inline: ret.name, custom: true }
1409 *     }
1410 *
1411 *     // pass the transform as an inline option
1412 *     doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true }
1413 *
1414 * _Note: if you call `toObject` and pass any options, the transform declared in your schema options will __not__ be applied. To force its application pass `transform: true`_
1415 *
1416 *     if (!schema.options.toObject) schema.options.toObject = {};
1417 *     schema.options.toObject.hide = '_id';
1418 *     schema.options.toObject.transform = function (doc, ret, options) {
1419 *       if (options.hide) {
1420 *         options.hide.split(' ').forEach(function (prop) {
1421 *           delete ret[prop];
1422 *         });
1423 *       }
1424 *     }
1425 *
1426 *     var doc = new Doc({ _id: 'anId', secret: 47, name: 'Wreck-it Ralph' });
1427 *     doc.toObject();                                        // { secret: 47, name: 'Wreck-it Ralph' }
1428 *     doc.toObject({ hide: 'secret _id' });                  // { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }
1429 *     doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' }
1430 *
1431 * Transforms are applied to the document _and each of its sub-documents_. To determine whether or not you are currently operating on a sub-document you might use the following guard:
1432 *
1433 *     if ('function' == typeof doc.ownerDocument) {
1434 *       // working with a sub doc
1435 *     }
1436 *
1437 * Transforms, like all of these options, are also available for `toJSON`.
1438 *
1439 * See [schema options](/docs/guide.html#toObject) for some more details.
1440 *
1441 * _During save, no custom options are applied to the document before being sent to the database._
1442 *
1443 * @param {Object} [options]
1444 * @return {Object} js object
1445 * @see mongodb.Binary http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html
1446 * @api public
1447 */
1448
1449Document.prototype.toObject = function (options) {
1450  if (options && options.depopulate && this.$__.wasPopulated) {
1451    // populated paths that we set to a document
1452    return clone(this._id, options);
1453  }
1454
1455  // When internally saving this document we always pass options,
1456  // bypassing the custom schema options.
1457  if (!(options && 'Object' == options.constructor.name)) {
1458    options = this.schema.options.toObject
1459      ? clone(this.schema.options.toObject)
1460      : {};
1461  }
1462
1463  ;('minimize' in options) || (options.minimize = this.schema.options.minimize);
1464
1465  var ret = clone(this._doc, options);
1466
1467  if (options.virtuals || options.getters && false !== options.virtuals) {
1468    applyGetters(this, ret, 'virtuals', options);
1469  }
1470
1471  if (options.getters) {
1472    applyGetters(this, ret, 'paths', options);
1473  }
1474
1475  if (true === options.transform) {
1476    var opts = options.json
1477      ? this.schema.options.toJSON
1478      : this.schema.options.toObject;
1479    if (opts) {
1480      options.transform = opts.transform;
1481    }
1482  }
1483
1484  if ('function' == typeof options.transform) {
1485    var xformed = options.transform(this, ret, options);
1486    if ('undefined' != typeof xformed) ret = xformed;
1487  }
1488
1489  return ret;
1490};
1491
1492/*!
1493 * Applies virtuals properties to `json`.
1494 *
1495 * @param {Document} self
1496 * @param {Object} json
1497 * @param {String} type either `virtuals` or `paths`
1498 * @return {Object} `json`
1499 */
1500
1501function applyGetters (self, json, type, options) {
1502  var schema = self.schema
1503    , paths = Object.keys(schema[type])
1504    , i = paths.length
1505    , path
1506
1507  while (i--) {
1508    path = paths[i];
1509
1510    var parts = path.split('.')
1511      , plen = parts.length
1512      , last = plen - 1
1513      , branch = json
1514      , part
1515
1516    for (var ii = 0; ii < plen; ++ii) {
1517      part = parts[ii];
1518      if (ii === last) {
1519        branch[part] = clone(self.get(path), options);
1520      } else {
1521        branch = branch[part] || (branch[part] = {});
1522      }
1523    }
1524  }
1525
1526  return json;
1527}
1528
1529/**
1530 * The return value of this method is used in calls to JSON.stringify(doc).
1531 *
1532 * This method accepts the same options as [Document#toObject](#document_Document-toObject). To apply the options to every document of your schema by default, set your [schemas](#schema_Schema) `toJSON` option to the same argument.
1533 *
1534 *     schema.set('toJSON', { virtuals: true })
1535 *
1536 * See [schema options](/docs/guide.html#toJSON) for details.
1537 *
1538 * @param {Object} options same options as [Document#toObject](#document_Document-toObject)
1539 * @return {Object}
1540 * @see Document#toObject #document_Document-toObject
1541
1542 * @api public
1543 */
1544
1545Document.prototype.toJSON = function (options) {
1546  // check for object type since an array of documents
1547  // being stringified passes array indexes instead
1548  // of options objects. JSON.stringify([doc, doc])
1549  if (!(options && 'Object' == options.constructor.name)) {
1550    options = this.schema.options.toJSON
1551      ? clone(this.schema.options.toJSON)
1552      : {};
1553  }
1554  options.json = true;
1555  return this.toObject(options);
1556};
1557
1558/**
1559 * Helper for console.log
1560 *
1561 * @api public
1562 */
1563
1564Document.prototype.inspect = function (options) {
1565  var opts = options && 'Object' == options.constructor.name ? options :
1566      this.schema.options.toObject ? clone(this.schema.options.toObject) :
1567      {};
1568  opts.minimize = false;
1569  return inspect(this.toObject(opts));
1570};
1571
1572/**
1573 * Helper for console.log
1574 *
1575 * @api public
1576 * @method toString
1577 */
1578
1579Document.prototype.toString = Document.prototype.inspect;
1580
1581/**
1582 * Returns true if the Document stores the same data as doc.
1583 *
1584 * Documents are considered equal when they have matching `_id`s.
1585 *
1586 * @param {Document} doc a document to compare
1587 * @return {Boolean}
1588 * @api public
1589 */
1590
1591Document.prototype.equals = function (doc) {
1592  var tid = this.get('_id');
1593  var docid = doc.get('_id');
1594  return tid && tid.equals
1595    ? tid.equals(docid)
1596    : tid === docid;
1597}
1598
1599/**
1600 * Populates document references, executing the `callback` when complete.
1601 *
1602 * ####Example:
1603 *
1604 *     doc
1605 *     .populate('company')
1606 *     .populate({
1607 *       path: 'notes',
1608 *       match: /airline/,
1609 *       select: 'text',
1610 *       model: 'modelName'
1611 *       options: opts
1612 *     }, function (err, user) {
1613 *       assert(doc._id == user._id) // the document itself is passed
1614 *     })
1615 *
1616 *     // summary
1617 *     doc.populate(path)               // not executed
1618 *     doc.populate(options);           // not executed
1619 *     doc.populate(path, callback)     // executed
1620 *     doc.populate(options, callback); // executed
1621 *     doc.populate(callback);          // executed
1622 *
1623 *
1624 * ####NOTE:
1625 *
1626 * Population does not occur unless a `callback` is passed.
1627 * Passing the same path a second time will overwrite the previous path options.
1628 * See [Model.populate()](#model_Model.populate) for explaination of options.
1629 *
1630 * @see Model.populate #model_Model.populate
1631 * @param {String|Object} [path] The path to populate or an options object
1632 * @param {Function} [callback] When passed, population is invoked
1633 * @api public
1634 * @return {Document} this
1635 */
1636
1637Document.prototype.populate = function populate () {
1638  if (0 === arguments.length) return this;
1639
1640  var pop = this.$__.populate || (this.$__.populate = {});
1641  var args = utils.args(arguments);
1642  var fn;
1643
1644  if ('function' == typeof args[args.length-1]) {
1645    fn = args.pop();
1646  }
1647
1648  // allow `doc.populate(callback)`
1649  if (args.length) {
1650    // use hash to remove duplicate paths
1651    var res = utils.populate.apply(null, args);
1652    for (var i = 0; i < res.length; ++i) {
1653      pop[res[i].path] = res[i];
1654    }
1655  }
1656
1657  if (fn) {
1658    var paths = utils.object.vals(pop);
1659    this.$__.populate = undefined;
1660    this.constructor.populate(this, paths, fn);
1661  }
1662
1663  return this;
1664}
1665
1666/**
1667 * Gets _id(s) used during population of the given `path`.
1668 *
1669 * ####Example:
1670 *
1671 *     Model.findOne().populate('author').exec(function (err, doc) {
1672 *       console.log(doc.author.name)         // Dr.Seuss
1673 *       console.log(doc.populated('author')) // '5144cf8050f071d979c118a7'
1674 *     })
1675 *
1676 * If the path was not populated, undefined is returned.
1677 *
1678 * @param {String} path
1679 * @return {Array|ObjectId|Number|Buffer|String|undefined}
1680 * @api public
1681 */
1682
1683Document.prototype.populated = function (path, val, options) {
1684  // val and options are internal
1685
1686  if (null == val) {
1687    if (!this.$__.populated) return undefined;
1688    var v = this.$__.populated[path];
1689    if (v) return v.value;
1690    return undefined;
1691  }
1692
1693  // internal
1694
1695  if (true === val) {
1696    if (!this.$__.populated) return undefined;
1697    return this.$__.populated[path];
1698  }
1699
1700  this.$__.populated || (this.$__.populated = {});
1701  this.$__.populated[path] = { value: val, options: options };
1702  return val;
1703}
1704
1705/**
1706 * Returns the full path to this document.
1707 *
1708 * @param {String} [path]
1709 * @return {String}
1710 * @api private
1711 * @method $__fullPath
1712 * @memberOf Document
1713 */
1714
1715Document.prototype.$__fullPath = function (path) {
1716  // overridden in SubDocuments
1717  return path || '';
1718}
1719
1720/*!
1721 * Module exports.
1722 */
1723
1724Document.ValidationError = ValidationError;
1725module.exports = exports = Document;
1726exports.Error = DocumentError;