UNPKG

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