thoughts/node_modules/mongoose/lib/document.js
node_modules/mongoose/lib/document.jsRaw
1/*!
2 * Module dependencies.
3 */
4
5var EventEmitter = require('events').EventEmitter;
6var MongooseError = require('./error');
7var MixedSchema = require('./schema/mixed');
8var Schema = require('./schema');
9var ObjectExpectedError = require('./error/objectExpected');
10var StrictModeError = require('./error/strict');
11var ValidatorError = require('./schematype').ValidatorError;
12var VersionError = require('./error').VersionError;
13var utils = require('./utils');
14var clone = utils.clone;
15var isMongooseObject = utils.isMongooseObject;
16var inspect = require('util').inspect;
17var ValidationError = MongooseError.ValidationError;
18var InternalCache = require('./internal');
19var deepEqual = utils.deepEqual;
20var hooks = require('hooks-fixed');
21var PromiseProvider = require('./promise_provider');
22var DocumentArray;
23var MongooseArray;
24var Embedded;
25var flatten = require('./services/common').flatten;
26
27/**
28 * Document constructor.
29 *
30 * @param {Object} obj the values to set
31 * @param {Object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data
32 * @param {Boolean} [skipId] bool, should we auto create an ObjectId _id
33 * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
34 * @event `init`: Emitted on a document after it has was retreived from the db and fully hydrated by Mongoose.
35 * @event `save`: Emitted when the document is successfully saved
36 * @api private
37 */
38
39function Document(obj, fields, skipId) {
40 this.$__ = new InternalCache;
41 this.$__.emitter = new EventEmitter();
42 this.isNew = true;
43 this.errors = undefined;
44
45 var schema = this.schema;
46
47 if (typeof fields === 'boolean') {
48 this.$__.strictMode = fields;
49 fields = undefined;
50 } else {
51 this.$__.strictMode = schema.options && schema.options.strict;
52 this.$__.selected = fields;
53 }
54
55 var required = schema.requiredPaths(true);
56 for (var i = 0; i < required.length; ++i) {
57 this.$__.activePaths.require(required[i]);
58 }
59
60 this.$__.emitter.setMaxListeners(0);
61 this._doc = this.$__buildDoc(obj, fields, skipId);
62
63 if (obj) {
64 if (obj instanceof Document) {
65 this.isNew = obj.isNew;
66 }
67 this.set(obj, undefined, true);
68 }
69
70 if (!schema.options.strict && obj) {
71 var _this = this,
72 keys = Object.keys(this._doc);
73
74 keys.forEach(function(key) {
75 if (!(key in schema.tree)) {
76 defineKey(key, null, _this);
77 }
78 });
79 }
80
81 this.$__registerHooksFromSchema();
82}
83
84/*!
85 * Document exposes the NodeJS event emitter API, so you can use
86 * `on`, `once`, etc.
87 */
88utils.each(
89 ['on', 'once', 'emit', 'listeners', 'removeListener', 'setMaxListeners',
90 'removeAllListeners', 'addListener'],
91 function(emitterFn) {
92 Document.prototype[emitterFn] = function() {
93 return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments);
94 };
95 });
96
97Document.prototype.constructor = Document;
98
99/**
100 * The documents schema.
101 *
102 * @api public
103 * @property schema
104 */
105
106Document.prototype.schema;
107
108/**
109 * Boolean flag specifying if the document is new.
110 *
111 * @api public
112 * @property isNew
113 */
114
115Document.prototype.isNew;
116
117/**
118 * The string version of this documents _id.
119 *
120 * ####Note:
121 *
122 * 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.
123 *
124 * new Schema({ name: String }, { id: false });
125 *
126 * @api public
127 * @see Schema options /docs/guide.html#options
128 * @property id
129 */
130
131Document.prototype.id;
132
133/**
134 * Hash containing current validation errors.
135 *
136 * @api public
137 * @property errors
138 */
139
140Document.prototype.errors;
141
142/**
143 * Builds the default doc structure
144 *
145 * @param {Object} obj
146 * @param {Object} [fields]
147 * @param {Boolean} [skipId]
148 * @return {Object}
149 * @api private
150 * @method $__buildDoc
151 * @memberOf Document
152 */
153
154Document.prototype.$__buildDoc = function(obj, fields, skipId) {
155 var doc = {};
156 var exclude = null;
157 var keys;
158 var ki;
159 var _this = this;
160
161 // determine if this doc is a result of a query with
162 // excluded fields
163
164 if (fields && utils.getFunctionName(fields.constructor) === 'Object') {
165 keys = Object.keys(fields);
166 ki = keys.length;
167
168 if (ki === 1 && keys[0] === '_id') {
169 exclude = !!fields[keys[ki]];
170 } else {
171 while (ki--) {
172 if (keys[ki] !== '_id' &&
173 (!fields[keys[ki]] || typeof fields[keys[ki]] !== 'object')) {
174 exclude = !fields[keys[ki]];
175 break;
176 }
177 }
178 }
179 }
180
181 var paths = Object.keys(this.schema.paths),
182 plen = paths.length,
183 ii = 0;
184
185 for (; ii < plen; ++ii) {
186 var p = paths[ii];
187
188 if (p === '_id') {
189 if (skipId) {
190 continue;
191 }
192 if (obj && '_id' in obj) {
193 continue;
194 }
195 }
196
197 var type = this.schema.paths[p];
198 var path = p.split('.');
199 var len = path.length;
200 var last = len - 1;
201 var curPath = '';
202 var doc_ = doc;
203 var i = 0;
204 var included = false;
205
206 for (; i < len; ++i) {
207 var piece = path[i],
208 def;
209
210 curPath += piece;
211
212 // support excluding intermediary levels
213 if (exclude === true) {
214 if (curPath in fields) {
215 break;
216 }
217 } else if (fields && curPath in fields) {
218 included = true;
219 }
220
221 if (i === last) {
222 if (fields && exclude !== null) {
223 if (exclude === true) {
224 // apply defaults to all non-excluded fields
225 if (p in fields) {
226 continue;
227 }
228
229 def = type.getDefault(_this, true);
230 if (typeof def !== 'undefined') {
231 doc_[piece] = def;
232 _this.$__.activePaths.default(p);
233 }
234 } else if (included) {
235 // selected field
236 def = type.getDefault(_this, true);
237 if (typeof def !== 'undefined') {
238 doc_[piece] = def;
239 _this.$__.activePaths.default(p);
240 }
241 }
242 } else {
243 def = type.getDefault(_this, true);
244 if (typeof def !== 'undefined') {
245 doc_[piece] = def;
246 _this.$__.activePaths.default(p);
247 }
248 }
249 } else {
250 doc_ = doc_[piece] || (doc_[piece] = {});
251 curPath += '.';
252 }
253 }
254 }
255
256 return doc;
257};
258
259/**
260 * Initializes the document without setters or marking anything modified.
261 *
262 * Called internally after a document is returned from mongodb.
263 *
264 * @param {Object} doc document returned by mongo
265 * @param {Function} fn callback
266 * @api public
267 */
268
269Document.prototype.init = function(doc, opts, fn) {
270 // do not prefix this method with $__ since its
271 // used by public hooks
272
273 if (typeof opts === 'function') {
274 fn = opts;
275 opts = null;
276 }
277
278 this.isNew = false;
279
280 // handle docs with populated paths
281 // If doc._id is not null or undefined
282 if (doc._id !== null && doc._id !== undefined &&
283 opts && opts.populated && opts.populated.length) {
284 var id = String(doc._id);
285 for (var i = 0; i < opts.populated.length; ++i) {
286 var item = opts.populated[i];
287 this.populated(item.path, item._docs[id], item);
288 }
289 }
290
291 init(this, doc, this._doc);
292 this.$__storeShard();
293
294 this.emit('init', this);
295 if (fn) {
296 fn(null);
297 }
298 return this;
299};
300
301/*!
302 * Init helper.
303 *
304 * @param {Object} self document instance
305 * @param {Object} obj raw mongodb doc
306 * @param {Object} doc object we are initializing
307 * @api private
308 */
309
310function init(self, obj, doc, prefix) {
311 prefix = prefix || '';
312
313 var keys = Object.keys(obj),
314 len = keys.length,
315 schema,
316 path,
317 i;
318
319 while (len--) {
320 i = keys[len];
321 path = prefix + i;
322 schema = self.schema.path(path);
323
324 if (!schema && utils.isObject(obj[i]) &&
325 (!obj[i].constructor || utils.getFunctionName(obj[i].constructor) === 'Object')) {
326 // assume nested object
327 if (!doc[i]) {
328 doc[i] = {};
329 }
330 init(self, obj[i], doc[i], path + '.');
331 } else {
332 if (obj[i] === null) {
333 doc[i] = null;
334 } else if (obj[i] !== undefined) {
335 if (schema) {
336 try {
337 doc[i] = schema.cast(obj[i], self, true);
338 } catch (e) {
339 self.invalidate(e.path, new ValidatorError({
340 path: e.path,
341 message: e.message,
342 type: 'cast',
343 value: e.value
344 }));
345 }
346 } else {
347 doc[i] = obj[i];
348 }
349 }
350 // mark as hydrated
351 if (!self.isModified(path)) {
352 self.$__.activePaths.init(path);
353 }
354 }
355 }
356}
357
358/**
359 * Stores the current values of the shard keys.
360 *
361 * ####Note:
362 *
363 * _Shard key values do not / are not allowed to change._
364 *
365 * @api private
366 * @method $__storeShard
367 * @memberOf Document
368 */
369
370Document.prototype.$__storeShard = function() {
371 // backwards compat
372 var key = this.schema.options.shardKey || this.schema.options.shardkey;
373 if (!(key && utils.getFunctionName(key.constructor) === 'Object')) {
374 return;
375 }
376
377 var orig = this.$__.shardval = {},
378 paths = Object.keys(key),
379 len = paths.length,
380 val;
381
382 for (var i = 0; i < len; ++i) {
383 val = this.getValue(paths[i]);
384 if (isMongooseObject(val)) {
385 orig[paths[i]] = val.toObject({depopulate: true});
386 } else if (val !== null && val !== undefined && val.valueOf &&
387 // Explicitly don't take value of dates
388 (!val.constructor || utils.getFunctionName(val.constructor) !== 'Date')) {
389 orig[paths[i]] = val.valueOf();
390 } else {
391 orig[paths[i]] = val;
392 }
393 }
394};
395
396/*!
397 * Set up middleware support
398 */
399
400for (var k in hooks) {
401 if (k === 'pre' || k === 'post') {
402 Document.prototype['$' + k] = Document['$' + k] = hooks[k];
403 } else {
404 Document.prototype[k] = Document[k] = hooks[k];
405 }
406}
407
408/**
409 * Sends an update command with this document `_id` as the query selector.
410 *
411 * ####Example:
412 *
413 * weirdCar.update({$inc: {wheels:1}}, { w: 1 }, callback);
414 *
415 * ####Valid options:
416 *
417 * - same as in [Model.update](#model_Model.update)
418 *
419 * @see Model.update #model_Model.update
420 * @param {Object} doc
421 * @param {Object} options
422 * @param {Function} callback
423 * @return {Query}
424 * @api public
425 */
426
427Document.prototype.update = function update() {
428 var args = utils.args(arguments);
429 args.unshift({_id: this._id});
430 return this.constructor.update.apply(this.constructor, args);
431};
432
433/**
434 * Sets the value of a path, or many paths.
435 *
436 * ####Example:
437 *
438 * // path, value
439 * doc.set(path, value)
440 *
441 * // object
442 * doc.set({
443 * path : value
444 * , path2 : {
445 * path : value
446 * }
447 * })
448 *
449 * // on-the-fly cast to number
450 * doc.set(path, value, Number)
451 *
452 * // on-the-fly cast to string
453 * doc.set(path, value, String)
454 *
455 * // changing strict mode behavior
456 * doc.set(path, value, { strict: false });
457 *
458 * @param {String|Object} path path or object of key/vals to set
459 * @param {Any} val the value to set
460 * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes
461 * @param {Object} [options] optionally specify options that modify the behavior of the set
462 * @api public
463 */
464
465Document.prototype.set = function(path, val, type, options) {
466 if (type && utils.getFunctionName(type.constructor) === 'Object') {
467 options = type;
468 type = undefined;
469 }
470
471 var merge = options && options.merge,
472 adhoc = type && type !== true,
473 constructing = type === true,
474 adhocs;
475
476 var strict = options && 'strict' in options
477 ? options.strict
478 : this.$__.strictMode;
479
480 if (adhoc) {
481 adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {});
482 adhocs[path] = Schema.interpretAsType(path, type, this.schema.options);
483 }
484
485 if (typeof path !== 'string') {
486 // new Document({ key: val })
487
488 if (path === null || path === void 0) {
489 var _ = path;
490 path = val;
491 val = _;
492 } else {
493 var prefix = val
494 ? val + '.'
495 : '';
496
497 if (path instanceof Document) {
498 if (path.$__isNested) {
499 path = path.toObject();
500 } else {
501 path = path._doc;
502 }
503 }
504
505 var keys = Object.keys(path);
506 var i = keys.length;
507 var pathtype;
508 var key;
509
510 if (i === 0 && !this.schema.options.minimize) {
511 if (val) {
512 this.set(val, {});
513 }
514 return this;
515 }
516
517 while (i--) {
518 key = keys[i];
519 var pathName = prefix + key;
520 pathtype = this.schema.pathType(pathName);
521
522 if (path[key] !== null
523 && path[key] !== void 0
524 // need to know if plain object - no Buffer, ObjectId, ref, etc
525 && utils.isObject(path[key])
526 && (!path[key].constructor || utils.getFunctionName(path[key].constructor) === 'Object')
527 && pathtype !== 'virtual'
528 && pathtype !== 'real'
529 && !(this.$__path(pathName) instanceof MixedSchema)
530 && !(this.schema.paths[pathName] &&
531 this.schema.paths[pathName].options &&
532 this.schema.paths[pathName].options.ref)) {
533 this.set(path[key], prefix + key, constructing);
534 } else if (strict) {
535 // Don't overwrite defaults with undefined keys (gh-3981)
536 if (constructing && path[key] === void 0 &&
537 this.get(key) !== void 0) {
538 continue;
539 }
540
541 if (pathtype === 'real' || pathtype === 'virtual') {
542 // Check for setting single embedded schema to document (gh-3535)
543 if (this.schema.paths[pathName] &&
544 this.schema.paths[pathName].$isSingleNested &&
545 path[key] instanceof Document) {
546 path[key] = path[key].toObject({virtuals: false});
547 }
548 this.set(prefix + key, path[key], constructing);
549 } else if (pathtype === 'nested' && path[key] instanceof Document) {
550 this.set(prefix + key,
551 path[key].toObject({virtuals: false}), constructing);
552 } else if (strict === 'throw') {
553 if (pathtype === 'nested') {
554 throw new ObjectExpectedError(key, path[key]);
555 } else {
556 throw new StrictModeError(key);
557 }
558 }
559 } else if (path[key] !== void 0) {
560 this.set(prefix + key, path[key], constructing);
561 }
562 }
563
564 return this;
565 }
566 }
567
568 // ensure _strict is honored for obj props
569 // docschema = new Schema({ path: { nest: 'string' }})
570 // doc.set('path', obj);
571 var pathType = this.schema.pathType(path);
572 if (pathType === 'nested' && val) {
573 if (utils.isObject(val) &&
574 (!val.constructor || utils.getFunctionName(val.constructor) === 'Object')) {
575 if (!merge) {
576 this.setValue(path, null);
577 cleanModifiedSubpaths(this, path);
578 }
579
580 if (Object.keys(val).length === 0) {
581 this.setValue(path, {});
582 this.markModified(path);
583 cleanModifiedSubpaths(this, path);
584 } else {
585 this.set(val, path, constructing);
586 }
587 return this;
588 }
589 this.invalidate(path, new MongooseError.CastError('Object', val, path));
590 return this;
591 }
592
593 var schema;
594 var parts = path.split('.');
595
596 if (pathType === 'adhocOrUndefined' && strict) {
597 // check for roots that are Mixed types
598 var mixed;
599
600 for (i = 0; i < parts.length; ++i) {
601 var subpath = parts.slice(0, i + 1).join('.');
602 schema = this.schema.path(subpath);
603 if (schema instanceof MixedSchema) {
604 // allow changes to sub paths of mixed types
605 mixed = true;
606 break;
607 }
608 }
609
610 if (!mixed) {
611 if (strict === 'throw') {
612 throw new StrictModeError(path);
613 }
614 return this;
615 }
616 } else if (pathType === 'virtual') {
617 schema = this.schema.virtualpath(path);
618 schema.applySetters(val, this);
619 return this;
620 } else {
621 schema = this.$__path(path);
622 }
623
624 var pathToMark;
625
626 // When using the $set operator the path to the field must already exist.
627 // Else mongodb throws: "LEFT_SUBFIELD only supports Object"
628
629 if (parts.length <= 1) {
630 pathToMark = path;
631 } else {
632 for (i = 0; i < parts.length; ++i) {
633 subpath = parts.slice(0, i + 1).join('.');
634 if (this.isDirectModified(subpath) // earlier prefixes that are already
635 // marked as dirty have precedence
636 || this.get(subpath) === null) {
637 pathToMark = subpath;
638 break;
639 }
640 }
641
642 if (!pathToMark) {
643 pathToMark = path;
644 }
645 }
646
647 // if this doc is being constructed we should not trigger getters
648 var priorVal = constructing
649 ? undefined
650 : this.getValue(path);
651
652 if (!schema) {
653 this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
654 return this;
655 }
656
657 var shouldSet = true;
658 try {
659 // If the user is trying to set a ref path to a document with
660 // the correct model name, treat it as populated
661 var didPopulate = false;
662 if (schema.options &&
663 schema.options.ref &&
664 val instanceof Document &&
665 schema.options.ref === val.constructor.modelName) {
666 if (this.ownerDocument) {
667 this.ownerDocument().populated(this.$__fullPath(path),
668 val._id, {model: val.constructor});
669 } else {
670 this.populated(path, val._id, {model: val.constructor});
671 }
672 didPopulate = true;
673 }
674
675 var popOpts;
676 if (schema.options &&
677 Array.isArray(schema.options.type) &&
678 schema.options.type.length &&
679 schema.options.type[0].ref &&
680 Array.isArray(val) &&
681 val.length > 0 &&
682 val[0] instanceof Document &&
683 val[0].constructor.modelName &&
684 schema.options.type[0].ref === val[0].constructor.modelName) {
685 if (this.ownerDocument) {
686 popOpts = { model: val[0].constructor };
687 this.ownerDocument().populated(this.$__fullPath(path),
688 val.map(function(v) { return v._id; }), popOpts);
689 } else {
690 popOpts = { model: val[0].constructor };
691 this.populated(path, val.map(function(v) { return v._id; }), popOpts);
692 }
693 didPopulate = true;
694 }
695 val = schema.applySetters(val, this, false, priorVal);
696
697 if (!didPopulate && this.$__.populated) {
698 delete this.$__.populated[path];
699 }
700
701 this.$markValid(path);
702 } catch (e) {
703 this.invalidate(path,
704 new MongooseError.CastError(schema.instance, val, path, e));
705 shouldSet = false;
706 }
707
708 if (schema.$isSingleNested) {
709 cleanModifiedSubpaths(this, path);
710 }
711
712 if (shouldSet) {
713 this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
714 }
715
716 return this;
717};
718
719/*!
720 * ignore
721 */
722
723function cleanModifiedSubpaths(doc, path) {
724 var _modifiedPaths = Object.keys(doc.$__.activePaths.states.modify);
725 var _numModifiedPaths = _modifiedPaths.length;
726 for (var j = 0; j < _numModifiedPaths; ++j) {
727 if (_modifiedPaths[j].indexOf(path + '.') === 0) {
728 delete doc.$__.activePaths.states.modify[_modifiedPaths[j]];
729 }
730 }
731}
732
733/**
734 * Determine if we should mark this change as modified.
735 *
736 * @return {Boolean}
737 * @api private
738 * @method $__shouldModify
739 * @memberOf Document
740 */
741
742Document.prototype.$__shouldModify = function(pathToMark, path, constructing, parts, schema, val, priorVal) {
743 if (this.isNew) {
744 return true;
745 }
746
747 if (undefined === val && !this.isSelected(path)) {
748 // when a path is not selected in a query, its initial
749 // value will be undefined.
750 return true;
751 }
752
753 if (undefined === val && path in this.$__.activePaths.states.default) {
754 // we're just unsetting the default value which was never saved
755 return false;
756 }
757
758 // gh-3992: if setting a populated field to a doc, don't mark modified
759 // if they have the same _id
760 if (this.populated(path) &&
761 val instanceof Document &&
762 deepEqual(val._id, priorVal)) {
763 return false;
764 }
765
766 if (!deepEqual(val, priorVal || this.get(path))) {
767 return true;
768 }
769
770 if (!constructing &&
771 val !== null &&
772 val !== undefined &&
773 path in this.$__.activePaths.states.default &&
774 deepEqual(val, schema.getDefault(this, constructing))) {
775 // a path with a default was $unset on the server
776 // and the user is setting it to the same value again
777 return true;
778 }
779 return false;
780};
781
782/**
783 * Handles the actual setting of the value and marking the path modified if appropriate.
784 *
785 * @api private
786 * @method $__set
787 * @memberOf Document
788 */
789
790Document.prototype.$__set = function(pathToMark, path, constructing, parts, schema, val, priorVal) {
791 Embedded = Embedded || require('./types/embedded');
792
793 var shouldModify = this.$__shouldModify(pathToMark, path, constructing, parts,
794 schema, val, priorVal);
795 var _this = this;
796
797 if (shouldModify) {
798 this.markModified(pathToMark, val);
799
800 // handle directly setting arrays (gh-1126)
801 MongooseArray || (MongooseArray = require('./types/array'));
802 if (val && val.isMongooseArray) {
803 val._registerAtomic('$set', val);
804
805 // Small hack for gh-1638: if we're overwriting the entire array, ignore
806 // paths that were modified before the array overwrite
807 this.$__.activePaths.forEach(function(modifiedPath) {
808 if (modifiedPath.indexOf(path + '.') === 0) {
809 _this.$__.activePaths.ignore(modifiedPath);
810 }
811 });
812 }
813 }
814
815 var obj = this._doc,
816 i = 0,
817 l = parts.length;
818
819 for (; i < l; i++) {
820 var next = i + 1,
821 last = next === l;
822
823 if (last) {
824 obj[parts[i]] = val;
825 } else {
826 if (obj[parts[i]] && utils.getFunctionName(obj[parts[i]].constructor) === 'Object') {
827 obj = obj[parts[i]];
828 } else if (obj[parts[i]] && obj[parts[i]] instanceof Embedded) {
829 obj = obj[parts[i]];
830 } else if (obj[parts[i]] && obj[parts[i]].$isSingleNested) {
831 obj = obj[parts[i]];
832 } else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) {
833 obj = obj[parts[i]];
834 } else {
835 obj = obj[parts[i]] = {};
836 }
837 }
838 }
839};
840
841/**
842 * Gets a raw value from a path (no getters)
843 *
844 * @param {String} path
845 * @api private
846 */
847
848Document.prototype.getValue = function(path) {
849 return utils.getValue(path, this._doc);
850};
851
852/**
853 * Sets a raw value for a path (no casting, setters, transformations)
854 *
855 * @param {String} path
856 * @param {Object} value
857 * @api private
858 */
859
860Document.prototype.setValue = function(path, val) {
861 utils.setValue(path, val, this._doc);
862 return this;
863};
864
865/**
866 * Returns the value of a path.
867 *
868 * ####Example
869 *
870 * // path
871 * doc.get('age') // 47
872 *
873 * // dynamic casting to a string
874 * doc.get('age', String) // "47"
875 *
876 * @param {String} path
877 * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for on-the-fly attributes
878 * @api public
879 */
880
881Document.prototype.get = function(path, type) {
882 var adhoc;
883 if (type) {
884 adhoc = Schema.interpretAsType(path, type, this.schema.options);
885 }
886
887 var schema = this.$__path(path) || this.schema.virtualpath(path),
888 pieces = path.split('.'),
889 obj = this._doc;
890
891 for (var i = 0, l = pieces.length; i < l; i++) {
892 obj = obj === null || obj === void 0
893 ? undefined
894 : obj[pieces[i]];
895 }
896
897 if (adhoc) {
898 obj = adhoc.cast(obj);
899 }
900
901 // Check if this path is populated - don't apply getters if it is,
902 // because otherwise its a nested object. See gh-3357
903 if (schema && !this.populated(path)) {
904 obj = schema.applyGetters(obj, this);
905 }
906
907 return obj;
908};
909
910/**
911 * Returns the schematype for the given `path`.
912 *
913 * @param {String} path
914 * @api private
915 * @method $__path
916 * @memberOf Document
917 */
918
919Document.prototype.$__path = function(path) {
920 var adhocs = this.$__.adhocPaths,
921 adhocType = adhocs && adhocs[path];
922
923 if (adhocType) {
924 return adhocType;
925 }
926 return this.schema.path(path);
927};
928
929/**
930 * Marks the path as having pending changes to write to the db.
931 *
932 * _Very helpful when using [Mixed](./schematypes.html#mixed) types._
933 *
934 * ####Example:
935 *
936 * doc.mixed.type = 'changed';
937 * doc.markModified('mixed.type');
938 * doc.save() // changes to mixed.type are now persisted
939 *
940 * @param {String} path the path to mark modified
941 * @api public
942 */
943
944Document.prototype.markModified = function(path) {
945 this.$__.activePaths.modify(path);
946};
947
948/**
949 * Clears the modified state on the specified path.
950 *
951 * ####Example:
952 *
953 * doc.foo = 'bar';
954 * doc.unmarkModified('foo');
955 * doc.save() // changes to foo will not be persisted
956 *
957 * @param {String} path the path to unmark modified
958 * @api public
959 */
960
961Document.prototype.unmarkModified = function(path) {
962 this.$__.activePaths.init(path);
963};
964
965/**
966 * Returns the list of paths that have been modified.
967 *
968 * @return {Array}
969 * @api public
970 */
971
972Document.prototype.modifiedPaths = function() {
973 var directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
974 return directModifiedPaths.reduce(function(list, path) {
975 var parts = path.split('.');
976 return list.concat(parts.reduce(function(chains, part, i) {
977 return chains.concat(parts.slice(0, i).concat(part).join('.'));
978 }, []).filter(function(chain) {
979 return (list.indexOf(chain) === -1);
980 }));
981 }, []);
982};
983
984/**
985 * Returns true if this document was modified, else false.
986 *
987 * If `path` is given, checks if a path or any full path containing `path` as part of its path chain has been modified.
988 *
989 * ####Example
990 *
991 * doc.set('documents.0.title', 'changed');
992 * doc.isModified() // true
993 * doc.isModified('documents') // true
994 * doc.isModified('documents.0.title') // true
995 * doc.isModified('documents otherProp') // true
996 * doc.isDirectModified('documents') // false
997 *
998 * @param {String} [path] optional
999 * @return {Boolean}
1000 * @api public
1001 */
1002
1003Document.prototype.isModified = function(paths) {
1004 if (paths) {
1005 if (!Array.isArray(paths)) {
1006 paths = paths.split(' ');
1007 }
1008 var modified = this.modifiedPaths();
1009 return paths.some(function(path) {
1010 return !!~modified.indexOf(path);
1011 });
1012 }
1013 return this.$__.activePaths.some('modify');
1014};
1015
1016/**
1017 * Checks if a path is set to its default.
1018 *
1019 * ####Example
1020 *
1021 * MyModel = mongoose.model('test', { name: { type: String, default: 'Val '} });
1022 * var m = new MyModel();
1023 * m.$isDefault('name'); // true
1024 *
1025 * @param {String} [path]
1026 * @return {Boolean}
1027 * @method $isDefault
1028 * @api public
1029 */
1030
1031Document.prototype.$isDefault = function(path) {
1032 return (path in this.$__.activePaths.states.default);
1033};
1034
1035/**
1036 * Returns true if `path` was directly set and modified, else false.
1037 *
1038 * ####Example
1039 *
1040 * doc.set('documents.0.title', 'changed');
1041 * doc.isDirectModified('documents.0.title') // true
1042 * doc.isDirectModified('documents') // false
1043 *
1044 * @param {String} path
1045 * @return {Boolean}
1046 * @api public
1047 */
1048
1049Document.prototype.isDirectModified = function(path) {
1050 return (path in this.$__.activePaths.states.modify);
1051};
1052
1053/**
1054 * Checks if `path` was initialized.
1055 *
1056 * @param {String} path
1057 * @return {Boolean}
1058 * @api public
1059 */
1060
1061Document.prototype.isInit = function(path) {
1062 return (path in this.$__.activePaths.states.init);
1063};
1064
1065/**
1066 * Checks if `path` was selected in the source query which initialized this document.
1067 *
1068 * ####Example
1069 *
1070 * Thing.findOne().select('name').exec(function (err, doc) {
1071 * doc.isSelected('name') // true
1072 * doc.isSelected('age') // false
1073 * })
1074 *
1075 * @param {String} path
1076 * @return {Boolean}
1077 * @api public
1078 */
1079
1080Document.prototype.isSelected = function isSelected(path) {
1081 if (this.$__.selected) {
1082 if (path === '_id') {
1083 return this.$__.selected._id !== 0;
1084 }
1085
1086 var paths = Object.keys(this.$__.selected),
1087 i = paths.length,
1088 inclusive = false,
1089 cur;
1090
1091 if (i === 1 && paths[0] === '_id') {
1092 // only _id was selected.
1093 return this.$__.selected._id === 0;
1094 }
1095
1096 while (i--) {
1097 cur = paths[i];
1098 if (cur === '_id') {
1099 continue;
1100 }
1101 inclusive = !!this.$__.selected[cur];
1102 break;
1103 }
1104
1105 if (path in this.$__.selected) {
1106 return inclusive;
1107 }
1108
1109 i = paths.length;
1110 var pathDot = path + '.';
1111
1112 while (i--) {
1113 cur = paths[i];
1114 if (cur === '_id') {
1115 continue;
1116 }
1117
1118 if (cur.indexOf(pathDot) === 0) {
1119 return inclusive;
1120 }
1121
1122 if (pathDot.indexOf(cur + '.') === 0) {
1123 return inclusive;
1124 }
1125 }
1126
1127 return !inclusive;
1128 }
1129
1130 return true;
1131};
1132
1133/**
1134 * Executes registered validation rules for this document.
1135 *
1136 * ####Note:
1137 *
1138 * 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`.
1139 *
1140 * ####Example:
1141 *
1142 * doc.validate(function (err) {
1143 * if (err) handleError(err);
1144 * else // validation passed
1145 * });
1146 *
1147 * @param {Object} optional options internal options
1148 * @param {Function} callback optional callback called after validation completes, passing an error if one occurred
1149 * @return {Promise} Promise
1150 * @api public
1151 */
1152
1153Document.prototype.validate = function(options, callback) {
1154 if (typeof options === 'function') {
1155 callback = options;
1156 options = null;
1157 }
1158
1159 this.$__validate(callback);
1160};
1161
1162/*!
1163 * ignore
1164 */
1165
1166function _getPathsToValidate(doc) {
1167 // only validate required fields when necessary
1168 var paths = Object.keys(doc.$__.activePaths.states.require).filter(function(path) {
1169 if (!doc.isSelected(path) && !doc.isModified(path)) {
1170 return false;
1171 }
1172 var p = doc.schema.path(path);
1173 if (typeof p.originalRequiredValue === 'function') {
1174 return p.originalRequiredValue.call(doc);
1175 }
1176 return true;
1177 });
1178
1179 paths = paths.concat(Object.keys(doc.$__.activePaths.states.init));
1180 paths = paths.concat(Object.keys(doc.$__.activePaths.states.modify));
1181 paths = paths.concat(Object.keys(doc.$__.activePaths.states.default));
1182
1183 // gh-661: if a whole array is modified, make sure to run validation on all
1184 // the children as well
1185 for (var i = 0; i < paths.length; ++i) {
1186 var path = paths[i];
1187 var val = doc.getValue(path);
1188 if (val && val.isMongooseArray && !Buffer.isBuffer(val) && !val.isMongooseDocumentArray) {
1189 var numElements = val.length;
1190 for (var j = 0; j < numElements; ++j) {
1191 paths.push(path + '.' + j);
1192 }
1193 }
1194 }
1195
1196 var flattenOptions = { skipArrays: true };
1197 for (i = 0; i < paths.length; ++i) {
1198 var pathToCheck = paths[i];
1199 if (doc.schema.nested[pathToCheck]) {
1200 var _v = doc.getValue(pathToCheck);
1201 if (isMongooseObject(_v)) {
1202 _v = _v.toObject({ virtuals: false });
1203 }
1204 var flat = flatten(_v, '', flattenOptions);
1205 var _subpaths = Object.keys(flat).map(function(p) {
1206 return pathToCheck + '.' + p;
1207 });
1208 paths = paths.concat(_subpaths);
1209 }
1210 }
1211
1212 return paths;
1213}
1214
1215/*!
1216 * ignore
1217 */
1218
1219Document.prototype.$__validate = function(callback) {
1220 var _this = this;
1221 var _complete = function() {
1222 var err = _this.$__.validationError;
1223 _this.$__.validationError = undefined;
1224 _this.emit('validate', _this);
1225 if (err) {
1226 for (var key in err.errors) {
1227 // Make sure cast errors persist
1228 if (!_this.__parent && err.errors[key] instanceof MongooseError.CastError) {
1229 _this.invalidate(key, err.errors[key]);
1230 }
1231 }
1232
1233 return err;
1234 }
1235 };
1236
1237 // only validate required fields when necessary
1238 var paths = _getPathsToValidate(this);
1239
1240 if (paths.length === 0) {
1241 process.nextTick(function() {
1242 var err = _complete();
1243 if (err) {
1244 callback(err);
1245 return;
1246 }
1247 callback();
1248 });
1249 }
1250
1251 var validating = {},
1252 total = 0;
1253
1254 var complete = function() {
1255 var err = _complete();
1256 if (err) {
1257 callback(err);
1258 return;
1259 }
1260 callback();
1261 };
1262
1263 var validatePath = function(path) {
1264 if (validating[path]) {
1265 return;
1266 }
1267
1268 validating[path] = true;
1269 total++;
1270
1271 process.nextTick(function() {
1272 var p = _this.schema.path(path);
1273 if (!p) {
1274 return --total || complete();
1275 }
1276
1277 // If user marked as invalid or there was a cast error, don't validate
1278 if (!_this.$isValid(path)) {
1279 --total || complete();
1280 return;
1281 }
1282
1283 var val = _this.getValue(path);
1284 p.doValidate(val, function(err) {
1285 if (err) {
1286 _this.invalidate(path, err, undefined, true);
1287 }
1288 --total || complete();
1289 }, _this);
1290 });
1291 };
1292
1293 paths.forEach(validatePath);
1294};
1295
1296/**
1297 * Executes registered validation rules (skipping asynchronous validators) for this document.
1298 *
1299 * ####Note:
1300 *
1301 * This method is useful if you need synchronous validation.
1302 *
1303 * ####Example:
1304 *
1305 * var err = doc.validateSync();
1306 * if ( err ){
1307 * handleError( err );
1308 * } else {
1309 * // validation passed
1310 * }
1311 *
1312 * @param {Array|string} pathsToValidate only validate the given paths
1313 * @return {MongooseError|undefined} MongooseError if there are errors during validation, or undefined if there is no error.
1314 * @api public
1315 */
1316
1317Document.prototype.validateSync = function(pathsToValidate) {
1318 var _this = this;
1319
1320 if (typeof pathsToValidate === 'string') {
1321 pathsToValidate = pathsToValidate.split(' ');
1322 }
1323
1324 // only validate required fields when necessary
1325 var paths = _getPathsToValidate(this);
1326
1327 if (pathsToValidate && pathsToValidate.length) {
1328 var tmp = [];
1329 for (var i = 0; i < paths.length; ++i) {
1330 if (pathsToValidate.indexOf(paths[i]) !== -1) {
1331 tmp.push(paths[i]);
1332 }
1333 }
1334 paths = tmp;
1335 }
1336
1337 var validating = {};
1338
1339 paths.forEach(function(path) {
1340 if (validating[path]) {
1341 return;
1342 }
1343
1344 validating[path] = true;
1345
1346 var p = _this.schema.path(path);
1347 if (!p) {
1348 return;
1349 }
1350 if (!_this.$isValid(path)) {
1351 return;
1352 }
1353
1354 var val = _this.getValue(path);
1355 var err = p.doValidateSync(val, _this);
1356 if (err) {
1357 _this.invalidate(path, err, undefined, true);
1358 }
1359 });
1360
1361 var err = _this.$__.validationError;
1362 _this.$__.validationError = undefined;
1363 _this.emit('validate', _this);
1364
1365 if (err) {
1366 for (var key in err.errors) {
1367 // Make sure cast errors persist
1368 if (err.errors[key] instanceof MongooseError.CastError) {
1369 _this.invalidate(key, err.errors[key]);
1370 }
1371 }
1372 }
1373
1374 return err;
1375};
1376
1377/**
1378 * Marks a path as invalid, causing validation to fail.
1379 *
1380 * The `errorMsg` argument will become the message of the `ValidationError`.
1381 *
1382 * The `value` argument (if passed) will be available through the `ValidationError.value` property.
1383 *
1384 * doc.invalidate('size', 'must be less than 20', 14);
1385
1386 * doc.validate(function (err) {
1387 * console.log(err)
1388 * // prints
1389 * { message: 'Validation failed',
1390 * name: 'ValidationError',
1391 * errors:
1392 * { size:
1393 * { message: 'must be less than 20',
1394 * name: 'ValidatorError',
1395 * path: 'size',
1396 * type: 'user defined',
1397 * value: 14 } } }
1398 * })
1399 *
1400 * @param {String} path the field to invalidate
1401 * @param {String|Error} errorMsg the error which states the reason `path` was invalid
1402 * @param {Object|String|Number|any} value optional invalid value
1403 * @param {String} [kind] optional `kind` property for the error
1404 * @return {ValidationError} the current ValidationError, with all currently invalidated paths
1405 * @api public
1406 */
1407
1408Document.prototype.invalidate = function(path, err, val, kind) {
1409 if (!this.$__.validationError) {
1410 this.$__.validationError = new ValidationError(this);
1411 }
1412
1413 if (this.$__.validationError.errors[path]) {
1414 return;
1415 }
1416
1417 if (!err || typeof err === 'string') {
1418 err = new ValidatorError({
1419 path: path,
1420 message: err,
1421 type: kind || 'user defined',
1422 value: val
1423 });
1424 }
1425
1426 if (this.$__.validationError === err) {
1427 return this.$__.validationError;
1428 }
1429
1430 this.$__.validationError.errors[path] = err;
1431 return this.$__.validationError;
1432};
1433
1434/**
1435 * Marks a path as valid, removing existing validation errors.
1436 *
1437 * @param {String} path the field to mark as valid
1438 * @api private
1439 * @method $markValid
1440 * @receiver Document
1441 */
1442
1443Document.prototype.$markValid = function(path) {
1444 if (!this.$__.validationError || !this.$__.validationError.errors[path]) {
1445 return;
1446 }
1447
1448 delete this.$__.validationError.errors[path];
1449 if (Object.keys(this.$__.validationError.errors).length === 0) {
1450 this.$__.validationError = null;
1451 }
1452};
1453
1454/**
1455 * Checks if a path is invalid
1456 *
1457 * @param {String} path the field to check
1458 * @method $isValid
1459 * @api private
1460 * @receiver Document
1461 */
1462
1463Document.prototype.$isValid = function(path) {
1464 return !this.$__.validationError || !this.$__.validationError.errors[path];
1465};
1466
1467/**
1468 * Resets the internal modified state of this document.
1469 *
1470 * @api private
1471 * @return {Document}
1472 * @method $__reset
1473 * @memberOf Document
1474 */
1475
1476Document.prototype.$__reset = function reset() {
1477 var _this = this;
1478 DocumentArray || (DocumentArray = require('./types/documentarray'));
1479
1480 this.$__.activePaths
1481 .map('init', 'modify', function(i) {
1482 return _this.getValue(i);
1483 })
1484 .filter(function(val) {
1485 return val && val instanceof Array && val.isMongooseDocumentArray && val.length;
1486 })
1487 .forEach(function(array) {
1488 var i = array.length;
1489 while (i--) {
1490 var doc = array[i];
1491 if (!doc) {
1492 continue;
1493 }
1494 doc.$__reset();
1495 }
1496 });
1497
1498 // clear atomics
1499 this.$__dirty().forEach(function(dirt) {
1500 var type = dirt.value;
1501 if (type && type._atomics) {
1502 type._atomics = {};
1503 }
1504 });
1505
1506 // Clear 'dirty' cache
1507 this.$__.activePaths.clear('modify');
1508 this.$__.activePaths.clear('default');
1509 this.$__.validationError = undefined;
1510 this.errors = undefined;
1511 _this = this;
1512 this.schema.requiredPaths().forEach(function(path) {
1513 _this.$__.activePaths.require(path);
1514 });
1515
1516 return this;
1517};
1518
1519/**
1520 * Returns this documents dirty paths / vals.
1521 *
1522 * @api private
1523 * @method $__dirty
1524 * @memberOf Document
1525 */
1526
1527Document.prototype.$__dirty = function() {
1528 var _this = this;
1529
1530 var all = this.$__.activePaths.map('modify', function(path) {
1531 return {
1532 path: path,
1533 value: _this.getValue(path),
1534 schema: _this.$__path(path)
1535 };
1536 });
1537
1538 // gh-2558: if we had to set a default and the value is not undefined,
1539 // we have to save as well
1540 all = all.concat(this.$__.activePaths.map('default', function(path) {
1541 if (path === '_id' || !_this.getValue(path)) {
1542 return;
1543 }
1544 return {
1545 path: path,
1546 value: _this.getValue(path),
1547 schema: _this.$__path(path)
1548 };
1549 }));
1550
1551 // Sort dirty paths in a flat hierarchy.
1552 all.sort(function(a, b) {
1553 return (a.path < b.path ? -1 : (a.path > b.path ? 1 : 0));
1554 });
1555
1556 // Ignore "foo.a" if "foo" is dirty already.
1557 var minimal = [],
1558 lastPath,
1559 top;
1560
1561 all.forEach(function(item) {
1562 if (!item) {
1563 return;
1564 }
1565 if (item.path.indexOf(lastPath) !== 0) {
1566 lastPath = item.path + '.';
1567 minimal.push(item);
1568 top = item;
1569 } else {
1570 // special case for top level MongooseArrays
1571 if (top.value && top.value._atomics && top.value.hasAtomics()) {
1572 // the `top` array itself and a sub path of `top` are being modified.
1573 // the only way to honor all of both modifications is through a $set
1574 // of entire array.
1575 top.value._atomics = {};
1576 top.value._atomics.$set = top.value;
1577 }
1578 }
1579 });
1580
1581 top = lastPath = null;
1582 return minimal;
1583};
1584
1585/*!
1586 * Compiles schemas.
1587 */
1588
1589function compile(tree, proto, prefix, options) {
1590 var keys = Object.keys(tree),
1591 i = keys.length,
1592 limb,
1593 key;
1594
1595 while (i--) {
1596 key = keys[i];
1597 limb = tree[key];
1598
1599 defineKey(key,
1600 ((utils.getFunctionName(limb.constructor) === 'Object'
1601 && Object.keys(limb).length)
1602 && (!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type))
1603 ? limb
1604 : null)
1605 , proto
1606 , prefix
1607 , keys
1608 , options);
1609 }
1610}
1611
1612// gets descriptors for all properties of `object`
1613// makes all properties non-enumerable to match previous behavior to #2211
1614function getOwnPropertyDescriptors(object) {
1615 var result = {};
1616
1617 Object.getOwnPropertyNames(object).forEach(function(key) {
1618 result[key] = Object.getOwnPropertyDescriptor(object, key);
1619 result[key].enumerable = true;
1620 });
1621
1622 return result;
1623}
1624
1625/*!
1626 * Defines the accessor named prop on the incoming prototype.
1627 */
1628
1629function defineKey(prop, subprops, prototype, prefix, keys, options) {
1630 var path = (prefix ? prefix + '.' : '') + prop;
1631 prefix = prefix || '';
1632
1633 if (subprops) {
1634 Object.defineProperty(prototype, prop, {
1635 enumerable: true,
1636 configurable: true,
1637 get: function() {
1638 var _this = this;
1639 if (!this.$__.getters) {
1640 this.$__.getters = {};
1641 }
1642
1643 if (!this.$__.getters[path]) {
1644 var nested = Object.create(Object.getPrototypeOf(this), getOwnPropertyDescriptors(this));
1645
1646 // save scope for nested getters/setters
1647 if (!prefix) {
1648 nested.$__.scope = this;
1649 }
1650
1651 // shadow inherited getters from sub-objects so
1652 // thing.nested.nested.nested... doesn't occur (gh-366)
1653 var i = 0,
1654 len = keys.length;
1655
1656 for (; i < len; ++i) {
1657 // over-write the parents getter without triggering it
1658 Object.defineProperty(nested, keys[i], {
1659 enumerable: false, // It doesn't show up.
1660 writable: true, // We can set it later.
1661 configurable: true, // We can Object.defineProperty again.
1662 value: undefined // It shadows its parent.
1663 });
1664 }
1665
1666 Object.defineProperty(nested, 'toObject', {
1667 enumerable: true,
1668 configurable: true,
1669 writable: false,
1670 value: function() {
1671 return _this.get(path);
1672 }
1673 });
1674
1675 Object.defineProperty(nested, 'toJSON', {
1676 enumerable: true,
1677 configurable: true,
1678 writable: false,
1679 value: function() {
1680 return _this.get(path);
1681 }
1682 });
1683
1684 Object.defineProperty(nested, '$__isNested', {
1685 enumerable: true,
1686 configurable: true,
1687 writable: false,
1688 value: true
1689 });
1690
1691 compile(subprops, nested, path, options);
1692 this.$__.getters[path] = nested;
1693 }
1694
1695 return this.$__.getters[path];
1696 },
1697 set: function(v) {
1698 if (v instanceof Document) {
1699 v = v.toObject();
1700 }
1701 return (this.$__.scope || this).set(path, v);
1702 }
1703 });
1704 } else {
1705 Object.defineProperty(prototype, prop, {
1706 enumerable: true,
1707 configurable: true,
1708 get: function() {
1709 return this.get.call(this.$__.scope || this, path);
1710 },
1711 set: function(v) {
1712 return this.set.call(this.$__.scope || this, path, v);
1713 }
1714 });
1715 }
1716}
1717
1718/**
1719 * Assigns/compiles `schema` into this documents prototype.
1720 *
1721 * @param {Schema} schema
1722 * @api private
1723 * @method $__setSchema
1724 * @memberOf Document
1725 */
1726
1727Document.prototype.$__setSchema = function(schema) {
1728 compile(schema.tree, this, undefined, schema.options);
1729 this.schema = schema;
1730};
1731
1732
1733/**
1734 * Get active path that were changed and are arrays
1735 *
1736 * @api private
1737 * @method $__getArrayPathsToValidate
1738 * @memberOf Document
1739 */
1740
1741Document.prototype.$__getArrayPathsToValidate = function() {
1742 DocumentArray || (DocumentArray = require('./types/documentarray'));
1743
1744 // validate all document arrays.
1745 return this.$__.activePaths
1746 .map('init', 'modify', function(i) {
1747 return this.getValue(i);
1748 }.bind(this))
1749 .filter(function(val) {
1750 return val && val instanceof Array && val.isMongooseDocumentArray && val.length;
1751 }).reduce(function(seed, array) {
1752 return seed.concat(array);
1753 }, [])
1754 .filter(function(doc) {
1755 return doc;
1756 });
1757};
1758
1759
1760/**
1761 * Get all subdocs (by bfs)
1762 *
1763 * @api private
1764 * @method $__getAllSubdocs
1765 * @memberOf Document
1766 */
1767
1768Document.prototype.$__getAllSubdocs = function() {
1769 DocumentArray || (DocumentArray = require('./types/documentarray'));
1770 Embedded = Embedded || require('./types/embedded');
1771
1772 function docReducer(seed, path) {
1773 var val = this[path];
1774
1775 if (val instanceof Embedded) {
1776 seed.push(val);
1777 }
1778 if (val && val.$isSingleNested) {
1779 seed = Object.keys(val._doc).reduce(docReducer.bind(val._doc), seed);
1780 seed.push(val);
1781 }
1782 if (val && val.isMongooseDocumentArray) {
1783 val.forEach(function _docReduce(doc) {
1784 if (!doc || !doc._doc) {
1785 return;
1786 }
1787 if (doc instanceof Embedded) {
1788 seed.push(doc);
1789 }
1790 seed = Object.keys(doc._doc).reduce(docReducer.bind(doc._doc), seed);
1791 });
1792 } else if (val instanceof Document && val.$__isNested) {
1793 val = val.toObject();
1794 if (val) {
1795 seed = Object.keys(val).reduce(docReducer.bind(val), seed);
1796 }
1797 }
1798 return seed;
1799 }
1800
1801 var subDocs = Object.keys(this._doc).reduce(docReducer.bind(this), []);
1802
1803 return subDocs;
1804};
1805
1806/**
1807 * Executes methods queued from the Schema definition
1808 *
1809 * @api private
1810 * @method $__registerHooksFromSchema
1811 * @memberOf Document
1812 */
1813
1814Document.prototype.$__registerHooksFromSchema = function() {
1815 Embedded = Embedded || require('./types/embedded');
1816 var Promise = PromiseProvider.get();
1817
1818 var _this = this;
1819 var q = _this.schema && _this.schema.callQueue;
1820 if (!q.length) {
1821 return _this;
1822 }
1823
1824 // we are only interested in 'pre' hooks, and group by point-cut
1825 var toWrap = q.reduce(function(seed, pair) {
1826 if (pair[0] !== 'pre' && pair[0] !== 'post' && pair[0] !== 'on') {
1827 _this[pair[0]].apply(_this, pair[1]);
1828 return seed;
1829 }
1830 var args = [].slice.call(pair[1]);
1831 var pointCut = pair[0] === 'on' ? 'post' : args[0];
1832 if (!(pointCut in seed)) {
1833 seed[pointCut] = {post: [], pre: []};
1834 }
1835 if (pair[0] === 'post') {
1836 seed[pointCut].post.push(args);
1837 } else if (pair[0] === 'on') {
1838 seed[pointCut].push(args);
1839 } else {
1840 seed[pointCut].pre.push(args);
1841 }
1842 return seed;
1843 }, {post: []});
1844
1845 // 'post' hooks are simpler
1846 toWrap.post.forEach(function(args) {
1847 _this.on.apply(_this, args);
1848 });
1849 delete toWrap.post;
1850
1851 // 'init' should be synchronous on subdocuments
1852 if (toWrap.init && _this instanceof Embedded) {
1853 if (toWrap.init.pre) {
1854 toWrap.init.pre.forEach(function(args) {
1855 _this.$pre.apply(_this, args);
1856 });
1857 }
1858 if (toWrap.init.post) {
1859 toWrap.init.post.forEach(function(args) {
1860 _this.$post.apply(_this, args);
1861 });
1862 }
1863 delete toWrap.init;
1864 } else if (toWrap.set) {
1865 // Set hooks also need to be sync re: gh-3479
1866 if (toWrap.set.pre) {
1867 toWrap.set.pre.forEach(function(args) {
1868 _this.$pre.apply(_this, args);
1869 });
1870 }
1871 if (toWrap.set.post) {
1872 toWrap.set.post.forEach(function(args) {
1873 _this.$post.apply(_this, args);
1874 });
1875 }
1876 delete toWrap.set;
1877 }
1878
1879 Object.keys(toWrap).forEach(function(pointCut) {
1880 // this is so we can wrap everything into a promise;
1881 var newName = ('$__original_' + pointCut);
1882 if (!_this[pointCut]) {
1883 return;
1884 }
1885 _this[newName] = _this[pointCut];
1886 _this[pointCut] = function wrappedPointCut() {
1887 var args = [].slice.call(arguments);
1888 var lastArg = args.pop();
1889 var fn;
1890 var originalStack = new Error().stack;
1891 var $results;
1892 if (lastArg && typeof lastArg !== 'function') {
1893 args.push(lastArg);
1894 } else {
1895 fn = lastArg;
1896 }
1897
1898 var promise = new Promise.ES6(function(resolve, reject) {
1899 args.push(function(error) {
1900 if (error) {
1901 // gh-2633: since VersionError is very generic, take the
1902 // stack trace of the original save() function call rather
1903 // than the async trace
1904 if (error instanceof VersionError) {
1905 error.stack = originalStack;
1906 }
1907 _this.$__handleReject(error);
1908 reject(error);
1909 return;
1910 }
1911
1912 // There may be multiple results and promise libs other than
1913 // mpromise don't support passing multiple values to `resolve()`
1914 $results = Array.prototype.slice.call(arguments, 1);
1915 resolve.apply(promise, $results);
1916 });
1917
1918 _this[newName].apply(_this, args);
1919 });
1920 if (fn) {
1921 if (_this.constructor.$wrapCallback) {
1922 fn = _this.constructor.$wrapCallback(fn);
1923 }
1924 return promise.then(
1925 function() {
1926 process.nextTick(function() {
1927 fn.apply(null, [null].concat($results));
1928 });
1929 },
1930 function(error) {
1931 process.nextTick(function() {
1932 fn(error);
1933 });
1934 });
1935 }
1936 return promise;
1937 };
1938
1939 toWrap[pointCut].pre.forEach(function(args) {
1940 args[0] = newName;
1941 _this.$pre.apply(_this, args);
1942 });
1943 toWrap[pointCut].post.forEach(function(args) {
1944 args[0] = newName;
1945 _this.$post.apply(_this, args);
1946 });
1947 });
1948 return _this;
1949};
1950
1951Document.prototype.$__handleReject = function handleReject(err) {
1952 // emit on the Model if listening
1953 if (this.listeners('error').length) {
1954 this.emit('error', err);
1955 } else if (this.constructor.listeners && this.constructor.listeners('error').length) {
1956 this.constructor.emit('error', err);
1957 } else if (this.listeners && this.listeners('error').length) {
1958 this.emit('error', err);
1959 }
1960};
1961
1962/**
1963 * Internal helper for toObject() and toJSON() that doesn't manipulate options
1964 *
1965 * @api private
1966 * @method $toObject
1967 * @memberOf Document
1968 */
1969
1970Document.prototype.$toObject = function(options, json) {
1971 var defaultOptions = {transform: true, json: json};
1972
1973 if (options && options.depopulate && !options._skipDepopulateTopLevel && this.$__.wasPopulated) {
1974 // populated paths that we set to a document
1975 return clone(this._id, options);
1976 }
1977
1978 // If we're calling toObject on a populated doc, we may want to skip
1979 // depopulated on the top level
1980 if (options && options._skipDepopulateTopLevel) {
1981 options._skipDepopulateTopLevel = false;
1982 }
1983
1984 // When internally saving this document we always pass options,
1985 // bypassing the custom schema options.
1986 if (!(options && utils.getFunctionName(options.constructor) === 'Object') ||
1987 (options && options._useSchemaOptions)) {
1988 if (json) {
1989 options = this.schema.options.toJSON ?
1990 clone(this.schema.options.toJSON) :
1991 {};
1992 options.json = true;
1993 options._useSchemaOptions = true;
1994 } else {
1995 options = this.schema.options.toObject ?
1996 clone(this.schema.options.toObject) :
1997 {};
1998 options.json = false;
1999 options._useSchemaOptions = true;
2000 }
2001 }
2002
2003 for (var key in defaultOptions) {
2004 if (options[key] === undefined) {
2005 options[key] = defaultOptions[key];
2006 }
2007 }
2008
2009 ('minimize' in options) || (options.minimize = this.schema.options.minimize);
2010
2011 // remember the root transform function
2012 // to save it from being overwritten by sub-transform functions
2013 var originalTransform = options.transform;
2014
2015 var ret = clone(this._doc, options) || {};
2016
2017 if (options.getters) {
2018 applyGetters(this, ret, 'paths', options);
2019 // applyGetters for paths will add nested empty objects;
2020 // if minimize is set, we need to remove them.
2021 if (options.minimize) {
2022 ret = minimize(ret) || {};
2023 }
2024 }
2025
2026 if (options.virtuals || options.getters && options.virtuals !== false) {
2027 applyGetters(this, ret, 'virtuals', options);
2028 }
2029
2030 if (options.versionKey === false && this.schema.options.versionKey) {
2031 delete ret[this.schema.options.versionKey];
2032 }
2033
2034 var transform = options.transform;
2035
2036 // In the case where a subdocument has its own transform function, we need to
2037 // check and see if the parent has a transform (options.transform) and if the
2038 // child schema has a transform (this.schema.options.toObject) In this case,
2039 // we need to adjust options.transform to be the child schema's transform and
2040 // not the parent schema's
2041 if (transform === true ||
2042 (this.schema.options.toObject && transform)) {
2043 var opts = options.json ? this.schema.options.toJSON : this.schema.options.toObject;
2044
2045 if (opts) {
2046 transform = (typeof options.transform === 'function' ? options.transform : opts.transform);
2047 }
2048 } else {
2049 options.transform = originalTransform;
2050 }
2051
2052 if (typeof transform === 'function') {
2053 var xformed = transform(this, ret, options);
2054 if (typeof xformed !== 'undefined') {
2055 ret = xformed;
2056 }
2057 }
2058
2059 return ret;
2060};
2061
2062/**
2063 * Converts this document into a plain javascript object, ready for storage in MongoDB.
2064 *
2065 * Buffers are converted to instances of [mongodb.Binary](http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html) for proper storage.
2066 *
2067 * ####Options:
2068 *
2069 * - `getters` apply all getters (path and virtual getters)
2070 * - `virtuals` apply virtual getters (can override `getters` option)
2071 * - `minimize` remove empty objects (defaults to true)
2072 * - `transform` a transform function to apply to the resulting document before returning
2073 * - `depopulate` depopulate any populated paths, replacing them with their original refs (defaults to false)
2074 * - `versionKey` whether to include the version key (defaults to true)
2075 * - `retainKeyOrder` keep the order of object keys. If this is set to true, `Object.keys(new Doc({ a: 1, b: 2}).toObject())` will always produce `['a', 'b']` (defaults to false)
2076 *
2077 * ####Getters/Virtuals
2078 *
2079 * Example of only applying path getters
2080 *
2081 * doc.toObject({ getters: true, virtuals: false })
2082 *
2083 * Example of only applying virtual getters
2084 *
2085 * doc.toObject({ virtuals: true })
2086 *
2087 * Example of applying both path and virtual getters
2088 *
2089 * doc.toObject({ getters: true })
2090 *
2091 * To apply these options to every document of your schema by default, set your [schemas](#schema_Schema) `toObject` option to the same argument.
2092 *
2093 * schema.set('toObject', { virtuals: true })
2094 *
2095 * ####Transform
2096 *
2097 * 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.
2098 *
2099 * Transform functions receive three arguments
2100 *
2101 * function (doc, ret, options) {}
2102 *
2103 * - `doc` The mongoose document which is being converted
2104 * - `ret` The plain object representation which has been converted
2105 * - `options` The options in use (either schema options or the options passed inline)
2106 *
2107 * ####Example
2108 *
2109 * // specify the transform schema option
2110 * if (!schema.options.toObject) schema.options.toObject = {};
2111 * schema.options.toObject.transform = function (doc, ret, options) {
2112 * // remove the _id of every document before returning the result
2113 * delete ret._id;
2114 * return ret;
2115 * }
2116 *
2117 * // without the transformation in the schema
2118 * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
2119 *
2120 * // with the transformation
2121 * doc.toObject(); // { name: 'Wreck-it Ralph' }
2122 *
2123 * With transformations we can do a lot more than remove properties. We can even return completely new customized objects:
2124 *
2125 * if (!schema.options.toObject) schema.options.toObject = {};
2126 * schema.options.toObject.transform = function (doc, ret, options) {
2127 * return { movie: ret.name }
2128 * }
2129 *
2130 * // without the transformation in the schema
2131 * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
2132 *
2133 * // with the transformation
2134 * doc.toObject(); // { movie: 'Wreck-it Ralph' }
2135 *
2136 * _Note: if a transform function returns `undefined`, the return value will be ignored._
2137 *
2138 * Transformations may also be applied inline, overridding any transform set in the options:
2139 *
2140 * function xform (doc, ret, options) {
2141 * return { inline: ret.name, custom: true }
2142 * }
2143 *
2144 * // pass the transform as an inline option
2145 * doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true }
2146 *
2147 * _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`_
2148 *
2149 * if (!schema.options.toObject) schema.options.toObject = {};
2150 * schema.options.toObject.hide = '_id';
2151 * schema.options.toObject.transform = function (doc, ret, options) {
2152 * if (options.hide) {
2153 * options.hide.split(' ').forEach(function (prop) {
2154 * delete ret[prop];
2155 * });
2156 * }
2157 * return ret;
2158 * }
2159 *
2160 * var doc = new Doc({ _id: 'anId', secret: 47, name: 'Wreck-it Ralph' });
2161 * doc.toObject(); // { secret: 47, name: 'Wreck-it Ralph' }
2162 * doc.toObject({ hide: 'secret _id' }); // { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }
2163 * doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' }
2164 *
2165 * Transforms are applied _only to the document and are not applied to sub-documents_.
2166 *
2167 * Transforms, like all of these options, are also available for `toJSON`.
2168 *
2169 * See [schema options](/docs/guide.html#toObject) for some more details.
2170 *
2171 * _During save, no custom options are applied to the document before being sent to the database._
2172 *
2173 * @param {Object} [options]
2174 * @return {Object} js object
2175 * @see mongodb.Binary http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html
2176 * @api public
2177 */
2178
2179Document.prototype.toObject = function(options) {
2180 return this.$toObject(options);
2181};
2182
2183/*!
2184 * Minimizes an object, removing undefined values and empty objects
2185 *
2186 * @param {Object} object to minimize
2187 * @return {Object}
2188 */
2189
2190function minimize(obj) {
2191 var keys = Object.keys(obj),
2192 i = keys.length,
2193 hasKeys,
2194 key,
2195 val;
2196
2197 while (i--) {
2198 key = keys[i];
2199 val = obj[key];
2200
2201 if (utils.isObject(val)) {
2202 obj[key] = minimize(val);
2203 }
2204
2205 if (undefined === obj[key]) {
2206 delete obj[key];
2207 continue;
2208 }
2209
2210 hasKeys = true;
2211 }
2212
2213 return hasKeys
2214 ? obj
2215 : undefined;
2216}
2217
2218/*!
2219 * Applies virtuals properties to `json`.
2220 *
2221 * @param {Document} self
2222 * @param {Object} json
2223 * @param {String} type either `virtuals` or `paths`
2224 * @return {Object} `json`
2225 */
2226
2227function applyGetters(self, json, type, options) {
2228 var schema = self.schema,
2229 paths = Object.keys(schema[type]),
2230 i = paths.length,
2231 path;
2232
2233 while (i--) {
2234 path = paths[i];
2235
2236 var parts = path.split('.'),
2237 plen = parts.length,
2238 last = plen - 1,
2239 branch = json,
2240 part;
2241
2242 for (var ii = 0; ii < plen; ++ii) {
2243 part = parts[ii];
2244 if (ii === last) {
2245 branch[part] = clone(self.get(path), options);
2246 } else {
2247 branch = branch[part] || (branch[part] = {});
2248 }
2249 }
2250 }
2251
2252 return json;
2253}
2254
2255/**
2256 * The return value of this method is used in calls to JSON.stringify(doc).
2257 *
2258 * 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.
2259 *
2260 * schema.set('toJSON', { virtuals: true })
2261 *
2262 * See [schema options](/docs/guide.html#toJSON) for details.
2263 *
2264 * @param {Object} options
2265 * @return {Object}
2266 * @see Document#toObject #document_Document-toObject
2267 * @api public
2268 */
2269
2270Document.prototype.toJSON = function(options) {
2271 return this.$toObject(options, true);
2272};
2273
2274/**
2275 * Helper for console.log
2276 *
2277 * @api public
2278 */
2279
2280Document.prototype.inspect = function(options) {
2281 var isPOJO = options &&
2282 utils.getFunctionName(options.constructor) === 'Object';
2283 var opts;
2284 if (isPOJO) {
2285 opts = options;
2286 opts.minimize = false;
2287 opts.retainKeyOrder = true;
2288 }
2289 return this.toObject(opts);
2290};
2291
2292/**
2293 * Helper for console.log
2294 *
2295 * @api public
2296 * @method toString
2297 */
2298
2299Document.prototype.toString = function() {
2300 return inspect(this.inspect());
2301};
2302
2303/**
2304 * Returns true if the Document stores the same data as doc.
2305 *
2306 * Documents are considered equal when they have matching `_id`s, unless neither
2307 * document has an `_id`, in which case this function falls back to using
2308 * `deepEqual()`.
2309 *
2310 * @param {Document} doc a document to compare
2311 * @return {Boolean}
2312 * @api public
2313 */
2314
2315Document.prototype.equals = function(doc) {
2316 if (!doc) {
2317 return false;
2318 }
2319
2320 var tid = this.get('_id');
2321 var docid = doc.get ? doc.get('_id') : doc;
2322 if (!tid && !docid) {
2323 return deepEqual(this, doc);
2324 }
2325 return tid && tid.equals
2326 ? tid.equals(docid)
2327 : tid === docid;
2328};
2329
2330/**
2331 * Populates document references, executing the `callback` when complete.
2332 * If you want to use promises instead, use this function with
2333 * [`execPopulate()`](#document_Document-execPopulate)
2334 *
2335 * ####Example:
2336 *
2337 * doc
2338 * .populate('company')
2339 * .populate({
2340 * path: 'notes',
2341 * match: /airline/,
2342 * select: 'text',
2343 * model: 'modelName'
2344 * options: opts
2345 * }, function (err, user) {
2346 * assert(doc._id === user._id) // the document itself is passed
2347 * })
2348 *
2349 * // summary
2350 * doc.populate(path) // not executed
2351 * doc.populate(options); // not executed
2352 * doc.populate(path, callback) // executed
2353 * doc.populate(options, callback); // executed
2354 * doc.populate(callback); // executed
2355 * doc.populate(options).execPopulate() // executed, returns promise
2356 *
2357 *
2358 * ####NOTE:
2359 *
2360 * Population does not occur unless a `callback` is passed *or* you explicitly
2361 * call `execPopulate()`.
2362 * Passing the same path a second time will overwrite the previous path options.
2363 * See [Model.populate()](#model_Model.populate) for explaination of options.
2364 *
2365 * @see Model.populate #model_Model.populate
2366 * @see Document.execPopulate #document_Document-execPopulate
2367 * @param {String|Object} [path] The path to populate or an options object
2368 * @param {Function} [callback] When passed, population is invoked
2369 * @api public
2370 * @return {Document} this
2371 */
2372
2373Document.prototype.populate = function populate() {
2374 if (arguments.length === 0) {
2375 return this;
2376 }
2377
2378 var pop = this.$__.populate || (this.$__.populate = {});
2379 var args = utils.args(arguments);
2380 var fn;
2381
2382 if (typeof args[args.length - 1] === 'function') {
2383 fn = args.pop();
2384 }
2385
2386 // allow `doc.populate(callback)`
2387 if (args.length) {
2388 // use hash to remove duplicate paths
2389 var res = utils.populate.apply(null, args);
2390 for (var i = 0; i < res.length; ++i) {
2391 pop[res[i].path] = res[i];
2392 }
2393 }
2394
2395 if (fn) {
2396 var paths = utils.object.vals(pop);
2397 this.$__.populate = undefined;
2398 paths.__noPromise = true;
2399 this.constructor.populate(this, paths, fn);
2400 }
2401
2402 return this;
2403};
2404
2405/**
2406 * Explicitly executes population and returns a promise. Useful for ES2015
2407 * integration.
2408 *
2409 * ####Example:
2410 *
2411 * var promise = doc.
2412 * populate('company').
2413 * populate({
2414 * path: 'notes',
2415 * match: /airline/,
2416 * select: 'text',
2417 * model: 'modelName'
2418 * options: opts
2419 * }).
2420 * execPopulate();
2421 *
2422 * // summary
2423 * doc.execPopulate().then(resolve, reject);
2424 *
2425 *
2426 * @see Document.populate #document_Document-populate
2427 * @api public
2428 * @return {Promise} promise that resolves to the document when population is done
2429 */
2430
2431Document.prototype.execPopulate = function() {
2432 var Promise = PromiseProvider.get();
2433 var _this = this;
2434 return new Promise.ES6(function(resolve, reject) {
2435 _this.populate(function(error, res) {
2436 if (error) {
2437 reject(error);
2438 } else {
2439 resolve(res);
2440 }
2441 });
2442 });
2443};
2444
2445/**
2446 * Gets _id(s) used during population of the given `path`.
2447 *
2448 * ####Example:
2449 *
2450 * Model.findOne().populate('author').exec(function (err, doc) {
2451 * console.log(doc.author.name) // Dr.Seuss
2452 * console.log(doc.populated('author')) // '5144cf8050f071d979c118a7'
2453 * })
2454 *
2455 * If the path was not populated, undefined is returned.
2456 *
2457 * @param {String} path
2458 * @return {Array|ObjectId|Number|Buffer|String|undefined}
2459 * @api public
2460 */
2461
2462Document.prototype.populated = function(path, val, options) {
2463 // val and options are internal
2464
2465 if (val === null || val === void 0) {
2466 if (!this.$__.populated) {
2467 return undefined;
2468 }
2469 var v = this.$__.populated[path];
2470 if (v) {
2471 return v.value;
2472 }
2473 return undefined;
2474 }
2475
2476 // internal
2477
2478 if (val === true) {
2479 if (!this.$__.populated) {
2480 return undefined;
2481 }
2482 return this.$__.populated[path];
2483 }
2484
2485 this.$__.populated || (this.$__.populated = {});
2486 this.$__.populated[path] = {value: val, options: options};
2487 return val;
2488};
2489
2490/**
2491 * Takes a populated field and returns it to its unpopulated state.
2492 *
2493 * ####Example:
2494 *
2495 * Model.findOne().populate('author').exec(function (err, doc) {
2496 * console.log(doc.author.name); // Dr.Seuss
2497 * console.log(doc.depopulate('author'));
2498 * console.log(doc.author); // '5144cf8050f071d979c118a7'
2499 * })
2500 *
2501 * If the path was not populated, this is a no-op.
2502 *
2503 * @param {String} path
2504 * @see Document.populate #document_Document-populate
2505 * @api public
2506 */
2507
2508Document.prototype.depopulate = function(path) {
2509 var populatedIds = this.populated(path);
2510 if (!populatedIds) {
2511 return;
2512 }
2513 delete this.$__.populated[path];
2514 this.set(path, populatedIds);
2515};
2516
2517
2518/**
2519 * Returns the full path to this document.
2520 *
2521 * @param {String} [path]
2522 * @return {String}
2523 * @api private
2524 * @method $__fullPath
2525 * @memberOf Document
2526 */
2527
2528Document.prototype.$__fullPath = function(path) {
2529 // overridden in SubDocuments
2530 return path || '';
2531};
2532
2533/*!
2534 * Module exports.
2535 */
2536
2537Document.ValidationError = ValidationError;
2538module.exports = exports = Document;