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