thoughts/node_modules/mongoose/lib/model.js
node_modules/mongoose/lib/model.jsRaw
1/*!
2 * Module dependencies.
3 */
4
5var Document = require('./document');
6var MongooseError = require('./error');
7var VersionError = MongooseError.VersionError;
8var DivergentArrayError = MongooseError.DivergentArrayError;
9var Query = require('./query');
10var Aggregate = require('./aggregate');
11var Schema = require('./schema');
12var utils = require('./utils');
13var hasOwnProperty = utils.object.hasOwnProperty;
14var isMongooseObject = utils.isMongooseObject;
15var EventEmitter = require('events').EventEmitter;
16var util = require('util');
17var tick = utils.tick;
18
19var async = require('async');
20var PromiseProvider = require('./promise_provider');
21
22var VERSION_WHERE = 1,
23 VERSION_INC = 2,
24 VERSION_ALL = VERSION_WHERE | VERSION_INC;
25
26/**
27 * Model constructor
28 *
29 * Provides the interface to MongoDB collections as well as creates document instances.
30 *
31 * @param {Object} doc values with which to create the document
32 * @inherits Document http://mongoosejs.com/docs/api.html#document-js
33 * @event `error`: If listening to this event, it is emitted when a document was saved without passing a callback and an `error` occurred. If not listening, the event bubbles to the connection used to create this Model.
34 * @event `index`: Emitted after `Model#ensureIndexes` completes. If an error occurred it is passed with the event.
35 * @event `index-single-start`: Emitted when an individual index starts within `Model#ensureIndexes`. The fields and options being used to build the index are also passed with the event.
36 * @event `index-single-done`: Emitted when an individual index finishes within `Model#ensureIndexes`. If an error occurred it is passed with the event. The fields, options, and index name are also passed.
37 * @api public
38 */
39
40function Model(doc, fields, skipId) {
41 Document.call(this, doc, fields, skipId);
42}
43
44/*!
45 * Inherits from Document.
46 *
47 * All Model.prototype features are available on
48 * top level (non-sub) documents.
49 */
50
51Model.prototype.__proto__ = Document.prototype;
52
53/**
54 * Connection the model uses.
55 *
56 * @api public
57 * @property db
58 */
59
60Model.prototype.db;
61
62/**
63 * Collection the model uses.
64 *
65 * @api public
66 * @property collection
67 */
68
69Model.prototype.collection;
70
71/**
72 * The name of the model
73 *
74 * @api public
75 * @property modelName
76 */
77
78Model.prototype.modelName;
79
80/**
81 * If this is a discriminator model, `baseModelName` is the name of
82 * the base model.
83 *
84 * @api public
85 * @property baseModelName
86 */
87
88Model.prototype.baseModelName;
89
90Model.prototype.$__handleSave = function(options, callback) {
91 var _this = this;
92 if (!options.safe && this.schema.options.safe) {
93 options.safe = this.schema.options.safe;
94 }
95 if (typeof options.safe === 'boolean') {
96 options.safe = null;
97 }
98
99 if (this.isNew) {
100 // send entire doc
101 var toObjectOptions = {};
102 if (this.schema.options.toObject &&
103 this.schema.options.toObject.retainKeyOrder) {
104 toObjectOptions.retainKeyOrder = true;
105 }
106
107 toObjectOptions.depopulate = 1;
108 toObjectOptions._skipDepopulateTopLevel = true;
109 toObjectOptions.transform = false;
110
111 var obj = this.toObject(toObjectOptions);
112
113 if (!utils.object.hasOwnProperty(obj || {}, '_id')) {
114 // documents must have an _id else mongoose won't know
115 // what to update later if more changes are made. the user
116 // wouldn't know what _id was generated by mongodb either
117 // nor would the ObjectId generated my mongodb necessarily
118 // match the schema definition.
119 setTimeout(function() {
120 callback(new Error('document must have an _id before saving'));
121 }, 0);
122 return;
123 }
124
125 this.$__version(true, obj);
126 this.collection.insert(obj, options.safe, function(err, ret) {
127 if (err) {
128 _this.isNew = true;
129 _this.emit('isNew', true);
130
131 callback(err);
132 return;
133 }
134
135 callback(null, ret);
136 });
137 this.$__reset();
138 this.isNew = false;
139 this.emit('isNew', false);
140 // Make it possible to retry the insert
141 this.$__.inserting = true;
142 } else {
143 // Make sure we don't treat it as a new object on error,
144 // since it already exists
145 this.$__.inserting = false;
146
147 var delta = this.$__delta();
148
149 if (delta) {
150 if (delta instanceof Error) {
151 callback(delta);
152 return;
153 }
154 var where = this.$__where(delta[0]);
155
156 if (where instanceof Error) {
157 callback(where);
158 return;
159 }
160
161 this.collection.update(where, delta[1], options.safe, function(err, ret) {
162 if (err) {
163 callback(err);
164 return;
165 }
166 callback(null, ret);
167 });
168 } else {
169 this.$__reset();
170 callback();
171 return;
172 }
173
174 this.emit('isNew', false);
175 }
176};
177
178/*!
179 * ignore
180 */
181
182Model.prototype.$__save = function(options, callback) {
183 var _this = this;
184
185 _this.$__handleSave(options, function(error, result) {
186 if (error) {
187 return _this.schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) {
188 callback(error);
189 });
190 }
191
192 _this.$__reset();
193 _this.$__storeShard();
194
195 var numAffected = 0;
196 if (result) {
197 if (Array.isArray(result)) {
198 numAffected = result.length;
199 } else if (result.result && result.result.n !== undefined) {
200 numAffected = result.result.n;
201 } else if (result.result && result.result.nModified !== undefined) {
202 numAffected = result.result.nModified;
203 } else {
204 numAffected = result;
205 }
206 }
207
208 // was this an update that required a version bump?
209 if (_this.$__.version && !_this.$__.inserting) {
210 var doIncrement = VERSION_INC === (VERSION_INC & _this.$__.version);
211 _this.$__.version = undefined;
212
213 if (numAffected <= 0) {
214 // the update failed. pass an error back
215 var err = new VersionError(_this);
216 return callback(err);
217 }
218
219 // increment version if was successful
220 if (doIncrement) {
221 var key = _this.schema.options.versionKey;
222 var version = _this.getValue(key) | 0;
223 _this.setValue(key, version + 1);
224 }
225 }
226
227 _this.emit('save', _this, numAffected);
228 callback(null, _this, numAffected);
229 });
230};
231
232/**
233 * Saves this document.
234 *
235 * ####Example:
236 *
237 * product.sold = Date.now();
238 * product.save(function (err, product, numAffected) {
239 * if (err) ..
240 * })
241 *
242 * The callback will receive three parameters
243 *
244 * 1. `err` if an error occurred
245 * 2. `product` which is the saved `product`
246 * 3. `numAffected` will be 1 when the document was successfully persisted to MongoDB, otherwise 0. Unless you tweak mongoose's internals, you don't need to worry about checking this parameter for errors - checking `err` is sufficient to make sure your document was properly saved.
247 *
248 * As an extra measure of flow control, save will return a Promise.
249 * ####Example:
250 * product.save().then(function(product) {
251 * ...
252 * });
253 *
254 * For legacy reasons, mongoose stores object keys in reverse order on initial
255 * save. That is, `{ a: 1, b: 2 }` will be saved as `{ b: 2, a: 1 }` in
256 * MongoDB. To override this behavior, set
257 * [the `toObject.retainKeyOrder` option](http://mongoosejs.com/docs/api.html#document_Document-toObject)
258 * to true on your schema.
259 *
260 * @param {Object} [options] options optional options
261 * @param {Object} [options.safe] overrides [schema's safe option](http://mongoosejs.com//docs/guide.html#safe)
262 * @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
263 * @param {Function} [fn] optional callback
264 * @return {Promise} Promise
265 * @api public
266 * @see middleware http://mongoosejs.com/docs/middleware.html
267 */
268
269Model.prototype.save = function(options, fn) {
270 if (typeof options === 'function') {
271 fn = options;
272 options = undefined;
273 }
274
275 if (!options) {
276 options = {};
277 }
278
279 if (fn) {
280 fn = this.constructor.$wrapCallback(fn);
281 }
282
283 return this.$__save(options, fn);
284};
285
286/*!
287 * Determines whether versioning should be skipped for the given path
288 *
289 * @param {Document} self
290 * @param {String} path
291 * @return {Boolean} true if versioning should be skipped for the given path
292 */
293function shouldSkipVersioning(self, path) {
294 var skipVersioning = self.schema.options.skipVersioning;
295 if (!skipVersioning) return false;
296
297 // Remove any array indexes from the path
298 path = path.replace(/\.\d+\./, '.');
299
300 return skipVersioning[path];
301}
302
303/*!
304 * Apply the operation to the delta (update) clause as
305 * well as track versioning for our where clause.
306 *
307 * @param {Document} self
308 * @param {Object} where
309 * @param {Object} delta
310 * @param {Object} data
311 * @param {Mixed} val
312 * @param {String} [operation]
313 */
314
315function operand(self, where, delta, data, val, op) {
316 // delta
317 op || (op = '$set');
318 if (!delta[op]) delta[op] = {};
319 delta[op][data.path] = val;
320
321 // disabled versioning?
322 if (self.schema.options.versionKey === false) return;
323
324 // path excluded from versioning?
325 if (shouldSkipVersioning(self, data.path)) return;
326
327 // already marked for versioning?
328 if (VERSION_ALL === (VERSION_ALL & self.$__.version)) return;
329
330 switch (op) {
331 case '$set':
332 case '$unset':
333 case '$pop':
334 case '$pull':
335 case '$pullAll':
336 case '$push':
337 case '$pushAll':
338 case '$addToSet':
339 break;
340 default:
341 // nothing to do
342 return;
343 }
344
345 // ensure updates sent with positional notation are
346 // editing the correct array element.
347 // only increment the version if an array position changes.
348 // modifying elements of an array is ok if position does not change.
349
350 if (op === '$push' || op === '$pushAll' || op === '$addToSet') {
351 self.$__.version = VERSION_INC;
352 } else if (/^\$p/.test(op)) {
353 // potentially changing array positions
354 self.increment();
355 } else if (Array.isArray(val)) {
356 // $set an array
357 self.increment();
358 } else if (/\.\d+\.|\.\d+$/.test(data.path)) {
359 // now handling $set, $unset
360 // subpath of array
361 self.$__.version = VERSION_WHERE;
362 }
363}
364
365/*!
366 * Compiles an update and where clause for a `val` with _atomics.
367 *
368 * @param {Document} self
369 * @param {Object} where
370 * @param {Object} delta
371 * @param {Object} data
372 * @param {Array} value
373 */
374
375function handleAtomics(self, where, delta, data, value) {
376 if (delta.$set && delta.$set[data.path]) {
377 // $set has precedence over other atomics
378 return;
379 }
380
381 if (typeof value.$__getAtomics === 'function') {
382 value.$__getAtomics().forEach(function(atomic) {
383 var op = atomic[0];
384 var val = atomic[1];
385 operand(self, where, delta, data, val, op);
386 });
387 return;
388 }
389
390 // legacy support for plugins
391
392 var atomics = value._atomics,
393 ops = Object.keys(atomics),
394 i = ops.length,
395 val,
396 op;
397
398 if (i === 0) {
399 // $set
400
401 if (isMongooseObject(value)) {
402 value = value.toObject({depopulate: 1});
403 } else if (value.valueOf) {
404 value = value.valueOf();
405 }
406
407 return operand(self, where, delta, data, value);
408 }
409
410 function iter(mem) {
411 return isMongooseObject(mem)
412 ? mem.toObject({depopulate: 1})
413 : mem;
414 }
415
416 while (i--) {
417 op = ops[i];
418 val = atomics[op];
419
420 if (isMongooseObject(val)) {
421 val = val.toObject({depopulate: 1});
422 } else if (Array.isArray(val)) {
423 val = val.map(iter);
424 } else if (val.valueOf) {
425 val = val.valueOf();
426 }
427
428 if (op === '$addToSet') {
429 val = {$each: val};
430 }
431
432 operand(self, where, delta, data, val, op);
433 }
434}
435
436/**
437 * Produces a special query document of the modified properties used in updates.
438 *
439 * @api private
440 * @method $__delta
441 * @memberOf Model
442 */
443
444Model.prototype.$__delta = function() {
445 var dirty = this.$__dirty();
446 if (!dirty.length && VERSION_ALL !== this.$__.version) return;
447
448 var where = {},
449 delta = {},
450 len = dirty.length,
451 divergent = [],
452 d = 0;
453
454 where._id = this._doc._id;
455
456 for (; d < len; ++d) {
457 var data = dirty[d];
458 var value = data.value;
459
460 var match = checkDivergentArray(this, data.path, value);
461 if (match) {
462 divergent.push(match);
463 continue;
464 }
465
466 var pop = this.populated(data.path, true);
467 if (!pop && this.$__.selected) {
468 // If any array was selected using an $elemMatch projection, we alter the path and where clause
469 // NOTE: MongoDB only supports projected $elemMatch on top level array.
470 var pathSplit = data.path.split('.');
471 var top = pathSplit[0];
472 if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) {
473 // If the selected array entry was modified
474 if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') {
475 where[top] = this.$__.selected[top];
476 pathSplit[1] = '$';
477 data.path = pathSplit.join('.');
478 }
479 // if the selected array was modified in any other way throw an error
480 else {
481 divergent.push(data.path);
482 continue;
483 }
484 }
485 }
486
487 if (divergent.length) continue;
488
489 if (undefined === value) {
490 operand(this, where, delta, data, 1, '$unset');
491 } else if (value === null) {
492 operand(this, where, delta, data, null);
493 } else if (value._path && value._atomics) {
494 // arrays and other custom types (support plugins etc)
495 handleAtomics(this, where, delta, data, value);
496 } else if (value._path && Buffer.isBuffer(value)) {
497 // MongooseBuffer
498 value = value.toObject();
499 operand(this, where, delta, data, value);
500 } else {
501 value = utils.clone(value, {depopulate: 1});
502 operand(this, where, delta, data, value);
503 }
504 }
505
506 if (divergent.length) {
507 return new DivergentArrayError(divergent);
508 }
509
510 if (this.$__.version) {
511 this.$__version(where, delta);
512 }
513
514 return [where, delta];
515};
516
517/*!
518 * Determine if array was populated with some form of filter and is now
519 * being updated in a manner which could overwrite data unintentionally.
520 *
521 * @see https://github.com/Automattic/mongoose/issues/1334
522 * @param {Document} doc
523 * @param {String} path
524 * @return {String|undefined}
525 */
526
527function checkDivergentArray(doc, path, array) {
528 // see if we populated this path
529 var pop = doc.populated(path, true);
530
531 if (!pop && doc.$__.selected) {
532 // If any array was selected using an $elemMatch projection, we deny the update.
533 // NOTE: MongoDB only supports projected $elemMatch on top level array.
534 var top = path.split('.')[0];
535 if (doc.$__.selected[top + '.$']) {
536 return top;
537 }
538 }
539
540 if (!(pop && array && array.isMongooseArray)) return;
541
542 // If the array was populated using options that prevented all
543 // documents from being returned (match, skip, limit) or they
544 // deselected the _id field, $pop and $set of the array are
545 // not safe operations. If _id was deselected, we do not know
546 // how to remove elements. $pop will pop off the _id from the end
547 // of the array in the db which is not guaranteed to be the
548 // same as the last element we have here. $set of the entire array
549 // would be similarily destructive as we never received all
550 // elements of the array and potentially would overwrite data.
551 var check = pop.options.match ||
552 pop.options.options && hasOwnProperty(pop.options.options, 'limit') || // 0 is not permitted
553 pop.options.options && pop.options.options.skip || // 0 is permitted
554 pop.options.select && // deselected _id?
555 (pop.options.select._id === 0 ||
556 /\s?-_id\s?/.test(pop.options.select));
557
558 if (check) {
559 var atomics = array._atomics;
560 if (Object.keys(atomics).length === 0 || atomics.$set || atomics.$pop) {
561 return path;
562 }
563 }
564}
565
566/**
567 * Appends versioning to the where and update clauses.
568 *
569 * @api private
570 * @method $__version
571 * @memberOf Model
572 */
573
574Model.prototype.$__version = function(where, delta) {
575 var key = this.schema.options.versionKey;
576
577 if (where === true) {
578 // this is an insert
579 if (key) this.setValue(key, delta[key] = 0);
580 return;
581 }
582
583 // updates
584
585 // only apply versioning if our versionKey was selected. else
586 // there is no way to select the correct version. we could fail
587 // fast here and force them to include the versionKey but
588 // thats a bit intrusive. can we do this automatically?
589 if (!this.isSelected(key)) {
590 return;
591 }
592
593 // $push $addToSet don't need the where clause set
594 if (VERSION_WHERE === (VERSION_WHERE & this.$__.version)) {
595 where[key] = this.getValue(key);
596 }
597
598 if (VERSION_INC === (VERSION_INC & this.$__.version)) {
599 if (!delta.$set || typeof delta.$set[key] === 'undefined') {
600 delta.$inc || (delta.$inc = {});
601 delta.$inc[key] = 1;
602 }
603 }
604};
605
606/**
607 * Signal that we desire an increment of this documents version.
608 *
609 * ####Example:
610 *
611 * Model.findById(id, function (err, doc) {
612 * doc.increment();
613 * doc.save(function (err) { .. })
614 * })
615 *
616 * @see versionKeys http://mongoosejs.com/docs/guide.html#versionKey
617 * @api public
618 */
619
620Model.prototype.increment = function increment() {
621 this.$__.version = VERSION_ALL;
622 return this;
623};
624
625/**
626 * Returns a query object which applies shardkeys if they exist.
627 *
628 * @api private
629 * @method $__where
630 * @memberOf Model
631 */
632
633Model.prototype.$__where = function _where(where) {
634 where || (where = {});
635
636 var paths,
637 len;
638
639 if (!where._id) {
640 where._id = this._doc._id;
641 }
642
643 if (this.$__.shardval) {
644 paths = Object.keys(this.$__.shardval);
645 len = paths.length;
646
647 for (var i = 0; i < len; ++i) {
648 where[paths[i]] = this.$__.shardval[paths[i]];
649 }
650 }
651
652 if (this._doc._id == null) {
653 return new Error('No _id found on document!');
654 }
655
656 return where;
657};
658
659/**
660 * Removes this document from the db.
661 *
662 * ####Example:
663 * product.remove(function (err, product) {
664 * if (err) return handleError(err);
665 * Product.findById(product._id, function (err, product) {
666 * console.log(product) // null
667 * })
668 * })
669 *
670 *
671 * As an extra measure of flow control, remove will return a Promise (bound to `fn` if passed) so it could be chained, or hooked to recive errors
672 *
673 * ####Example:
674 * product.remove().then(function (product) {
675 * ...
676 * }).onRejected(function (err) {
677 * assert.ok(err)
678 * })
679 *
680 * @param {function(err,product)} [fn] optional callback
681 * @return {Promise} Promise
682 * @api public
683 */
684
685Model.prototype.remove = function remove(options, fn) {
686 if (typeof options === 'function') {
687 fn = options;
688 options = undefined;
689 }
690
691 if (!options) {
692 options = {};
693 }
694
695 if (this.$__.removing) {
696 if (fn) {
697 this.$__.removing.then(
698 function(res) { fn(null, res); },
699 function(err) { fn(err); });
700 }
701 return this;
702 }
703
704 var _this = this;
705 var Promise = PromiseProvider.get();
706
707 if (fn) {
708 fn = this.constructor.$wrapCallback(fn);
709 }
710
711 this.$__.removing = new Promise.ES6(function(resolve, reject) {
712 var where = _this.$__where();
713 if (where instanceof Error) {
714 reject(where);
715 fn && fn(where);
716 return;
717 }
718
719 if (!options.safe && _this.schema.options.safe) {
720 options.safe = _this.schema.options.safe;
721 }
722
723 _this.collection.remove(where, options, function(err) {
724 if (!err) {
725 _this.emit('remove', _this);
726 resolve(_this);
727 fn && fn(null, _this);
728 return;
729 }
730 reject(err);
731 fn && fn(err);
732 });
733 });
734 return this.$__.removing;
735};
736
737/**
738 * Returns another Model instance.
739 *
740 * ####Example:
741 *
742 * var doc = new Tank;
743 * doc.model('User').findById(id, callback);
744 *
745 * @param {String} name model name
746 * @api public
747 */
748
749Model.prototype.model = function model(name) {
750 return this.db.model(name);
751};
752
753/**
754 * Adds a discriminator type.
755 *
756 * ####Example:
757 *
758 * function BaseSchema() {
759 * Schema.apply(this, arguments);
760 *
761 * this.add({
762 * name: String,
763 * createdAt: Date
764 * });
765 * }
766 * util.inherits(BaseSchema, Schema);
767 *
768 * var PersonSchema = new BaseSchema();
769 * var BossSchema = new BaseSchema({ department: String });
770 *
771 * var Person = mongoose.model('Person', PersonSchema);
772 * var Boss = Person.discriminator('Boss', BossSchema);
773 *
774 * @param {String} name discriminator model name
775 * @param {Schema} schema discriminator model schema
776 * @api public
777 */
778
779Model.discriminator = function discriminator(name, schema) {
780 var CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
781 toJSON: true,
782 toObject: true,
783 _id: true,
784 id: true
785 };
786
787 if (!(schema && schema.instanceOfSchema)) {
788 throw new Error('You must pass a valid discriminator Schema');
789 }
790
791 if (this.schema.discriminatorMapping && !this.schema.discriminatorMapping.isRoot) {
792 throw new Error('Discriminator "' + name +
793 '" can only be a discriminator of the root model');
794 }
795
796 var key = this.schema.options.discriminatorKey;
797 if (schema.path(key)) {
798 throw new Error('Discriminator "' + name +
799 '" cannot have field with name "' + key + '"');
800 }
801
802 function merge(schema, baseSchema) {
803 utils.merge(schema, baseSchema);
804
805 var obj = {};
806 obj[key] = {
807 default: name,
808 set: function() {
809 throw new Error('Can\'t set discriminator key "' + key + '"');
810 }
811 };
812 obj[key][schema.options.typeKey] = String;
813 schema.add(obj);
814 schema.discriminatorMapping = {key: key, value: name, isRoot: false};
815
816 if (baseSchema.options.collection) {
817 schema.options.collection = baseSchema.options.collection;
818 }
819
820 var toJSON = schema.options.toJSON;
821 var toObject = schema.options.toObject;
822 var _id = schema.options._id;
823 var id = schema.options.id;
824
825 var keys = Object.keys(schema.options);
826
827 for (var i = 0; i < keys.length; ++i) {
828 var _key = keys[i];
829 if (!CUSTOMIZABLE_DISCRIMINATOR_OPTIONS[_key]) {
830 if (!utils.deepEqual(schema.options[_key], baseSchema.options[_key])) {
831 throw new Error('Can\'t customize discriminator option ' + _key +
832 ' (can only modify ' +
833 Object.keys(CUSTOMIZABLE_DISCRIMINATOR_OPTIONS).join(', ') +
834 ')');
835 }
836 }
837 }
838
839 schema.options = utils.clone(baseSchema.options);
840 if (toJSON) schema.options.toJSON = toJSON;
841 if (toObject) schema.options.toObject = toObject;
842 if (typeof _id !== 'undefined') {
843 schema.options._id = _id;
844 }
845 schema.options.id = id;
846
847 schema.callQueue = baseSchema.callQueue.concat(schema.callQueue.slice(schema._defaultMiddleware.length));
848 schema._requiredpaths = undefined; // reset just in case Schema#requiredPaths() was called on either schema
849 }
850
851 // merges base schema into new discriminator schema and sets new type field.
852 merge(schema, this.schema);
853
854 if (!this.discriminators) {
855 this.discriminators = {};
856 }
857
858 if (!this.schema.discriminatorMapping) {
859 this.schema.discriminatorMapping = {key: key, value: null, isRoot: true};
860 }
861
862 if (this.discriminators[name]) {
863 throw new Error('Discriminator with name "' + name + '" already exists');
864 }
865 if (this.db.models[name]) {
866 throw new MongooseError.OverwriteModelError(name);
867 }
868
869 this.discriminators[name] = this.db.model(name, schema, this.collection.name);
870 this.discriminators[name].prototype.__proto__ = this.prototype;
871 Object.defineProperty(this.discriminators[name], 'baseModelName', {
872 value: this.modelName,
873 configurable: true,
874 writable: false
875 });
876
877 // apply methods and statics
878 applyMethods(this.discriminators[name], schema);
879 applyStatics(this.discriminators[name], schema);
880
881 return this.discriminators[name];
882};
883
884// Model (class) features
885
886/*!
887 * Give the constructor the ability to emit events.
888 */
889
890for (var i in EventEmitter.prototype) {
891 Model[i] = EventEmitter.prototype[i];
892}
893
894/**
895 * Called when the model compiles.
896 *
897 * @api private
898 */
899
900Model.init = function init() {
901 if ((this.schema.options.autoIndex) ||
902 (this.schema.options.autoIndex === null && this.db.config.autoIndex)) {
903 this.ensureIndexes({ __noPromise: true });
904 }
905
906 this.schema.emit('init', this);
907};
908
909/**
910 * Sends `ensureIndex` commands to mongo for each index declared in the schema.
911 *
912 * ####Example:
913 *
914 * Event.ensureIndexes(function (err) {
915 * if (err) return handleError(err);
916 * });
917 *
918 * After completion, an `index` event is emitted on this `Model` passing an error if one occurred.
919 *
920 * ####Example:
921 *
922 * var eventSchema = new Schema({ thing: { type: 'string', unique: true }})
923 * var Event = mongoose.model('Event', eventSchema);
924 *
925 * Event.on('index', function (err) {
926 * if (err) console.error(err); // error occurred during index creation
927 * })
928 *
929 * _NOTE: It is not recommended that you run this in production. Index creation may impact database performance depending on your load. Use with caution._
930 *
931 * The `ensureIndex` commands are not sent in parallel. This is to avoid the `MongoError: cannot add index with a background operation in progress` error. See [this ticket](https://github.com/Automattic/mongoose/issues/1365) for more information.
932 *
933 * @param {Object} [options] internal options
934 * @param {Function} [cb] optional callback
935 * @return {Promise}
936 * @api public
937 */
938
939Model.ensureIndexes = function ensureIndexes(options, callback) {
940 if (typeof options === 'function') {
941 callback = options;
942 options = null;
943 }
944
945 if (options && options.__noPromise) {
946 _ensureIndexes(this, callback);
947 return;
948 }
949
950 if (callback) {
951 callback = this.$wrapCallback(callback);
952 }
953
954 var _this = this;
955 var Promise = PromiseProvider.get();
956 return new Promise.ES6(function(resolve, reject) {
957 _ensureIndexes(_this, function(error) {
958 if (error) {
959 callback && callback(error);
960 reject(error);
961 }
962 callback && callback();
963 resolve();
964 });
965 });
966};
967
968function _ensureIndexes(model, callback) {
969 var indexes = model.schema.indexes();
970 if (!indexes.length) {
971 setImmediate(function() {
972 callback && callback();
973 });
974 return;
975 }
976 // Indexes are created one-by-one to support how MongoDB < 2.4 deals
977 // with background indexes.
978
979 var done = function(err) {
980 if (err && model.schema.options.emitIndexErrors) {
981 model.emit('error', err);
982 }
983 model.emit('index', err);
984 callback && callback(err);
985 };
986
987 var indexSingleDone = function(err, fields, options, name) {
988 model.emit('index-single-done', err, fields, options, name);
989 };
990 var indexSingleStart = function(fields, options) {
991 model.emit('index-single-start', fields, options);
992 };
993
994 var create = function() {
995 var index = indexes.shift();
996 if (!index) return done();
997
998 var indexFields = index[0];
999 var options = index[1];
1000 _handleSafe(options);
1001
1002 indexSingleStart(indexFields, options);
1003
1004 model.collection.ensureIndex(indexFields, options, tick(function(err, name) {
1005 indexSingleDone(err, indexFields, options, name);
1006 if (err) {
1007 return done(err);
1008 }
1009 create();
1010 }));
1011 };
1012
1013 setImmediate(function() {
1014 create();
1015 });
1016}
1017
1018function _handleSafe(options) {
1019 if (options.safe) {
1020 if (typeof options.safe === 'boolean') {
1021 options.w = options.safe;
1022 delete options.safe;
1023 }
1024 if (typeof options.safe === 'object') {
1025 options.w = options.safe.w;
1026 options.j = options.safe.j;
1027 options.wtimeout = options.safe.wtimeout;
1028 delete options.safe;
1029 }
1030 }
1031}
1032
1033/**
1034 * Schema the model uses.
1035 *
1036 * @property schema
1037 * @receiver Model
1038 * @api public
1039 */
1040
1041Model.schema;
1042
1043/*!
1044 * Connection instance the model uses.
1045 *
1046 * @property db
1047 * @receiver Model
1048 * @api public
1049 */
1050
1051Model.db;
1052
1053/*!
1054 * Collection the model uses.
1055 *
1056 * @property collection
1057 * @receiver Model
1058 * @api public
1059 */
1060
1061Model.collection;
1062
1063/**
1064 * Base Mongoose instance the model uses.
1065 *
1066 * @property base
1067 * @receiver Model
1068 * @api public
1069 */
1070
1071Model.base;
1072
1073/**
1074 * Registered discriminators for this model.
1075 *
1076 * @property discriminators
1077 * @receiver Model
1078 * @api public
1079 */
1080
1081Model.discriminators;
1082
1083/**
1084 * Removes documents from the collection.
1085 *
1086 * ####Example:
1087 *
1088 * Comment.remove({ title: 'baby born from alien father' }, function (err) {
1089 *
1090 * });
1091 *
1092 * ####Note:
1093 *
1094 * To remove documents without waiting for a response from MongoDB, do not pass a `callback`, then call `exec` on the returned [Query](#query-js):
1095 *
1096 * var query = Comment.remove({ _id: id });
1097 * query.exec();
1098 *
1099 * ####Note:
1100 *
1101 * This method sends a remove command directly to MongoDB, no Mongoose documents are involved. Because no Mongoose documents are involved, _no middleware (hooks) are executed_.
1102 *
1103 * @param {Object} conditions
1104 * @param {Function} [callback]
1105 * @return {Query}
1106 * @api public
1107 */
1108
1109Model.remove = function remove(conditions, callback) {
1110 if (typeof conditions === 'function') {
1111 callback = conditions;
1112 conditions = {};
1113 }
1114
1115 // get the mongodb collection object
1116 var mq = new this.Query(conditions, {}, this, this.collection);
1117
1118 if (callback) {
1119 callback = this.$wrapCallback(callback);
1120 }
1121
1122 return mq.remove(callback);
1123};
1124
1125/**
1126 * Finds documents
1127 *
1128 * The `conditions` are cast to their respective SchemaTypes before the command is sent.
1129 *
1130 * ####Examples:
1131 *
1132 * // named john and at least 18
1133 * MyModel.find({ name: 'john', age: { $gte: 18 }});
1134 *
1135 * // executes immediately, passing results to callback
1136 * MyModel.find({ name: 'john', age: { $gte: 18 }}, function (err, docs) {});
1137 *
1138 * // name LIKE john and only selecting the "name" and "friends" fields, executing immediately
1139 * MyModel.find({ name: /john/i }, 'name friends', function (err, docs) { })
1140 *
1141 * // passing options
1142 * MyModel.find({ name: /john/i }, null, { skip: 10 })
1143 *
1144 * // passing options and executing immediately
1145 * MyModel.find({ name: /john/i }, null, { skip: 10 }, function (err, docs) {});
1146 *
1147 * // executing a query explicitly
1148 * var query = MyModel.find({ name: /john/i }, null, { skip: 10 })
1149 * query.exec(function (err, docs) {});
1150 *
1151 * // using the promise returned from executing a query
1152 * var query = MyModel.find({ name: /john/i }, null, { skip: 10 });
1153 * var promise = query.exec();
1154 * promise.addBack(function (err, docs) {});
1155 *
1156 * @param {Object} conditions
1157 * @param {Object} [projection] optional fields to return (http://bit.ly/1HotzBo)
1158 * @param {Object} [options] optional
1159 * @param {Function} [callback]
1160 * @return {Query}
1161 * @see field selection #query_Query-select
1162 * @see promise #promise-js
1163 * @api public
1164 */
1165
1166Model.find = function find(conditions, projection, options, callback) {
1167 if (typeof conditions === 'function') {
1168 callback = conditions;
1169 conditions = {};
1170 projection = null;
1171 options = null;
1172 } else if (typeof projection === 'function') {
1173 callback = projection;
1174 projection = null;
1175 options = null;
1176 } else if (typeof options === 'function') {
1177 callback = options;
1178 options = null;
1179 }
1180
1181 var mq = new this.Query({}, {}, this, this.collection);
1182 mq.select(projection);
1183 mq.setOptions(options);
1184 if (this.schema.discriminatorMapping && mq.selectedInclusively()) {
1185 mq.select(this.schema.options.discriminatorKey);
1186 }
1187
1188 if (callback) {
1189 callback = this.$wrapCallback(callback);
1190 }
1191
1192 return mq.find(conditions, callback);
1193};
1194
1195/**
1196 * Finds a single document by its _id field. `findById(id)` is almost*
1197 * equivalent to `findOne({ _id: id })`.
1198 *
1199 * The `id` is cast based on the Schema before sending the command.
1200 *
1201 * Note: `findById()` triggers `findOne` hooks.
1202 *
1203 * * Except for how it treats `undefined`. Because the MongoDB driver
1204 * deletes keys that have value `undefined`, `findById(undefined)` gets
1205 * translated to `findById({ _id: null })`.
1206 *
1207 * ####Example:
1208 *
1209 * // find adventure by id and execute immediately
1210 * Adventure.findById(id, function (err, adventure) {});
1211 *
1212 * // same as above
1213 * Adventure.findById(id).exec(callback);
1214 *
1215 * // select only the adventures name and length
1216 * Adventure.findById(id, 'name length', function (err, adventure) {});
1217 *
1218 * // same as above
1219 * Adventure.findById(id, 'name length').exec(callback);
1220 *
1221 * // include all properties except for `length`
1222 * Adventure.findById(id, '-length').exec(function (err, adventure) {});
1223 *
1224 * // passing options (in this case return the raw js objects, not mongoose documents by passing `lean`
1225 * Adventure.findById(id, 'name', { lean: true }, function (err, doc) {});
1226 *
1227 * // same as above
1228 * Adventure.findById(id, 'name').lean().exec(function (err, doc) {});
1229 *
1230 * @param {Object|String|Number} id value of `_id` to query by
1231 * @param {Object} [projection] optional fields to return (http://bit.ly/1HotzBo)
1232 * @param {Object} [options] optional
1233 * @param {Function} [callback]
1234 * @return {Query}
1235 * @see field selection #query_Query-select
1236 * @see lean queries #query_Query-lean
1237 * @api public
1238 */
1239
1240Model.findById = function findById(id, projection, options, callback) {
1241 if (typeof id === 'undefined') {
1242 id = null;
1243 }
1244
1245 if (callback) {
1246 callback = this.$wrapCallback(callback);
1247 }
1248
1249 return this.findOne({_id: id}, projection, options, callback);
1250};
1251
1252/**
1253 * Finds one document.
1254 *
1255 * The `conditions` are cast to their respective SchemaTypes before the command is sent.
1256 *
1257 * ####Example:
1258 *
1259 * // find one iphone adventures - iphone adventures??
1260 * Adventure.findOne({ type: 'iphone' }, function (err, adventure) {});
1261 *
1262 * // same as above
1263 * Adventure.findOne({ type: 'iphone' }).exec(function (err, adventure) {});
1264 *
1265 * // select only the adventures name
1266 * Adventure.findOne({ type: 'iphone' }, 'name', function (err, adventure) {});
1267 *
1268 * // same as above
1269 * Adventure.findOne({ type: 'iphone' }, 'name').exec(function (err, adventure) {});
1270 *
1271 * // specify options, in this case lean
1272 * Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }, callback);
1273 *
1274 * // same as above
1275 * Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }).exec(callback);
1276 *
1277 * // chaining findOne queries (same as above)
1278 * Adventure.findOne({ type: 'iphone' }).select('name').lean().exec(callback);
1279 *
1280 * @param {Object} [conditions]
1281 * @param {Object} [projection] optional fields to return (http://bit.ly/1HotzBo)
1282 * @param {Object} [options] optional
1283 * @param {Function} [callback]
1284 * @return {Query}
1285 * @see field selection #query_Query-select
1286 * @see lean queries #query_Query-lean
1287 * @api public
1288 */
1289
1290Model.findOne = function findOne(conditions, projection, options, callback) {
1291 if (typeof options === 'function') {
1292 callback = options;
1293 options = null;
1294 } else if (typeof projection === 'function') {
1295 callback = projection;
1296 projection = null;
1297 options = null;
1298 } else if (typeof conditions === 'function') {
1299 callback = conditions;
1300 conditions = {};
1301 projection = null;
1302 options = null;
1303 }
1304
1305 // get the mongodb collection object
1306 var mq = new this.Query({}, {}, this, this.collection);
1307 mq.select(projection);
1308 mq.setOptions(options);
1309 if (this.schema.discriminatorMapping && mq.selectedInclusively()) {
1310 mq.select(this.schema.options.discriminatorKey);
1311 }
1312
1313 if (callback) {
1314 callback = this.$wrapCallback(callback);
1315 }
1316
1317 return mq.findOne(conditions, callback);
1318};
1319
1320/**
1321 * Counts number of matching documents in a database collection.
1322 *
1323 * ####Example:
1324 *
1325 * Adventure.count({ type: 'jungle' }, function (err, count) {
1326 * if (err) ..
1327 * console.log('there are %d jungle adventures', count);
1328 * });
1329 *
1330 * @param {Object} conditions
1331 * @param {Function} [callback]
1332 * @return {Query}
1333 * @api public
1334 */
1335
1336Model.count = function count(conditions, callback) {
1337 if (typeof conditions === 'function') {
1338 callback = conditions;
1339 conditions = {};
1340 }
1341
1342 // get the mongodb collection object
1343 var mq = new this.Query({}, {}, this, this.collection);
1344
1345 if (callback) {
1346 callback = this.$wrapCallback(callback);
1347 }
1348
1349 return mq.count(conditions, callback);
1350};
1351
1352/**
1353 * Creates a Query for a `distinct` operation.
1354 *
1355 * Passing a `callback` immediately executes the query.
1356 *
1357 * ####Example
1358 *
1359 * Link.distinct('url', { clicks: {$gt: 100}}, function (err, result) {
1360 * if (err) return handleError(err);
1361 *
1362 * assert(Array.isArray(result));
1363 * console.log('unique urls with more than 100 clicks', result);
1364 * })
1365 *
1366 * var query = Link.distinct('url');
1367 * query.exec(callback);
1368 *
1369 * @param {String} field
1370 * @param {Object} [conditions] optional
1371 * @param {Function} [callback]
1372 * @return {Query}
1373 * @api public
1374 */
1375
1376Model.distinct = function distinct(field, conditions, callback) {
1377 // get the mongodb collection object
1378 var mq = new this.Query({}, {}, this, this.collection);
1379
1380 if (typeof conditions === 'function') {
1381 callback = conditions;
1382 conditions = {};
1383 }
1384 if (callback) {
1385 callback = this.$wrapCallback(callback);
1386 }
1387
1388 return mq.distinct(field, conditions, callback);
1389};
1390
1391/**
1392 * Creates a Query, applies the passed conditions, and returns the Query.
1393 *
1394 * For example, instead of writing:
1395 *
1396 * User.find({age: {$gte: 21, $lte: 65}}, callback);
1397 *
1398 * we can instead write:
1399 *
1400 * User.where('age').gte(21).lte(65).exec(callback);
1401 *
1402 * Since the Query class also supports `where` you can continue chaining
1403 *
1404 * User
1405 * .where('age').gte(21).lte(65)
1406 * .where('name', /^b/i)
1407 * ... etc
1408 *
1409 * @param {String} path
1410 * @param {Object} [val] optional value
1411 * @return {Query}
1412 * @api public
1413 */
1414
1415Model.where = function where(path, val) {
1416 void val; // eslint
1417 // get the mongodb collection object
1418 var mq = new this.Query({}, {}, this, this.collection).find({});
1419 return mq.where.apply(mq, arguments);
1420};
1421
1422/**
1423 * Creates a `Query` and specifies a `$where` condition.
1424 *
1425 * Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via `find({ $where: javascript })`, or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model.
1426 *
1427 * Blog.$where('this.username.indexOf("val") !== -1').exec(function (err, docs) {});
1428 *
1429 * @param {String|Function} argument is a javascript string or anonymous function
1430 * @method $where
1431 * @memberOf Model
1432 * @return {Query}
1433 * @see Query.$where #query_Query-%24where
1434 * @api public
1435 */
1436
1437Model.$where = function $where() {
1438 var mq = new this.Query({}, {}, this, this.collection).find({});
1439 return mq.$where.apply(mq, arguments);
1440};
1441
1442/**
1443 * Issues a mongodb findAndModify update command.
1444 *
1445 * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any) to the callback. The query executes immediately if `callback` is passed else a Query object is returned.
1446 *
1447 * ####Options:
1448 *
1449 * - `new`: bool - if true, return the modified document rather than the original. defaults to false (changed in 4.0)
1450 * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
1451 * - `fields`: {Object|String} - Field selection. Equivalent to `.select(fields).findOneAndUpdate()`
1452 * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
1453 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1454 * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
1455 * - `setDefaultsOnInsert`: if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/).
1456 * - `passRawResult`: if true, passes the [raw result from the MongoDB driver as the third callback parameter](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
1457 *
1458 *
1459 * ####Examples:
1460 *
1461 * A.findOneAndUpdate(conditions, update, options, callback) // executes
1462 * A.findOneAndUpdate(conditions, update, options) // returns Query
1463 * A.findOneAndUpdate(conditions, update, callback) // executes
1464 * A.findOneAndUpdate(conditions, update) // returns Query
1465 * A.findOneAndUpdate() // returns Query
1466 *
1467 * ####Note:
1468 *
1469 * All top level update keys which are not `atomic` operation names are treated as set operations:
1470 *
1471 * ####Example:
1472 *
1473 * var query = { name: 'borne' };
1474 * Model.findOneAndUpdate(query, { name: 'jason borne' }, options, callback)
1475 *
1476 * // is sent as
1477 * Model.findOneAndUpdate(query, { $set: { name: 'jason borne' }}, options, callback)
1478 *
1479 * This helps prevent accidentally overwriting your document with `{ name: 'jason borne' }`.
1480 *
1481 * ####Note:
1482 *
1483 * Values are cast to their appropriate types when using the findAndModify helpers.
1484 * However, the below are never executed.
1485 *
1486 * - defaults
1487 * - setters
1488 *
1489 * `findAndModify` helpers support limited defaults and validation. You can
1490 * enable these by setting the `setDefaultsOnInsert` and `runValidators` options,
1491 * respectively.
1492 *
1493 * If you need full-fledged validation, use the traditional approach of first
1494 * retrieving the document.
1495 *
1496 * Model.findById(id, function (err, doc) {
1497 * if (err) ..
1498 * doc.name = 'jason borne';
1499 * doc.save(callback);
1500 * });
1501 *
1502 * @param {Object} [conditions]
1503 * @param {Object} [update]
1504 * @param {Object} [options]
1505 * @param {Function} [callback]
1506 * @return {Query}
1507 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1508 * @api public
1509 */
1510
1511Model.findOneAndUpdate = function(conditions, update, options, callback) {
1512 if (typeof options === 'function') {
1513 callback = options;
1514 options = null;
1515 } else if (arguments.length === 1) {
1516 if (typeof conditions === 'function') {
1517 var msg = 'Model.findOneAndUpdate(): First argument must not be a function.\n\n'
1518 + ' ' + this.modelName + '.findOneAndUpdate(conditions, update, options, callback)\n'
1519 + ' ' + this.modelName + '.findOneAndUpdate(conditions, update, options)\n'
1520 + ' ' + this.modelName + '.findOneAndUpdate(conditions, update)\n'
1521 + ' ' + this.modelName + '.findOneAndUpdate(update)\n'
1522 + ' ' + this.modelName + '.findOneAndUpdate()\n';
1523 throw new TypeError(msg);
1524 }
1525 update = conditions;
1526 conditions = undefined;
1527 }
1528 if (callback) {
1529 callback = this.$wrapCallback(callback);
1530 }
1531
1532 var fields;
1533 if (options && options.fields) {
1534 fields = options.fields;
1535 }
1536
1537 update = utils.clone(update, {depopulate: 1});
1538 if (this.schema.options.versionKey && options && options.upsert) {
1539 if (!update.$setOnInsert) {
1540 update.$setOnInsert = {};
1541 }
1542 update.$setOnInsert[this.schema.options.versionKey] = 0;
1543 }
1544
1545 var mq = new this.Query({}, {}, this, this.collection);
1546 mq.select(fields);
1547
1548 return mq.findOneAndUpdate(conditions, update, options, callback);
1549};
1550
1551/**
1552 * Issues a mongodb findAndModify update command by a document's _id field.
1553 * `findByIdAndUpdate(id, ...)` is equivalent to `findOneAndUpdate({ _id: id }, ...)`.
1554 *
1555 * Finds a matching document, updates it according to the `update` arg,
1556 * passing any `options`, and returns the found document (if any) to the
1557 * callback. The query executes immediately if `callback` is passed else a
1558 * Query object is returned.
1559 *
1560 * This function triggers `findOneAndUpdate` middleware.
1561 *
1562 * ####Options:
1563 *
1564 * - `new`: bool - true to return the modified document rather than the original. defaults to false
1565 * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
1566 * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
1567 * - `setDefaultsOnInsert`: if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/).
1568 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1569 * - `select`: sets the document fields to return
1570 *
1571 * ####Examples:
1572 *
1573 * A.findByIdAndUpdate(id, update, options, callback) // executes
1574 * A.findByIdAndUpdate(id, update, options) // returns Query
1575 * A.findByIdAndUpdate(id, update, callback) // executes
1576 * A.findByIdAndUpdate(id, update) // returns Query
1577 * A.findByIdAndUpdate() // returns Query
1578 *
1579 * ####Note:
1580 *
1581 * All top level update keys which are not `atomic` operation names are treated as set operations:
1582 *
1583 * ####Example:
1584 *
1585 * Model.findByIdAndUpdate(id, { name: 'jason borne' }, options, callback)
1586 *
1587 * // is sent as
1588 * Model.findByIdAndUpdate(id, { $set: { name: 'jason borne' }}, options, callback)
1589 *
1590 * This helps prevent accidentally overwriting your document with `{ name: 'jason borne' }`.
1591 *
1592 * ####Note:
1593 *
1594 * Values are cast to their appropriate types when using the findAndModify helpers.
1595 * However, the below are never executed.
1596 *
1597 * - defaults
1598 * - setters
1599 *
1600 * `findAndModify` helpers support limited defaults and validation. You can
1601 * enable these by setting the `setDefaultsOnInsert` and `runValidators` options,
1602 * respectively.
1603 *
1604 * If you need full-fledged validation, use the traditional approach of first
1605 * retrieving the document.
1606 *
1607 * Model.findById(id, function (err, doc) {
1608 * if (err) ..
1609 * doc.name = 'jason borne';
1610 * doc.save(callback);
1611 * });
1612 *
1613 * @param {Object|Number|String} id value of `_id` to query by
1614 * @param {Object} [update]
1615 * @param {Object} [options]
1616 * @param {Function} [callback]
1617 * @return {Query}
1618 * @see Model.findOneAndUpdate #model_Model.findOneAndUpdate
1619 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1620 * @api public
1621 */
1622
1623Model.findByIdAndUpdate = function(id, update, options, callback) {
1624 if (callback) {
1625 callback = this.$wrapCallback(callback);
1626 }
1627 if (arguments.length === 1) {
1628 if (typeof id === 'function') {
1629 var msg = 'Model.findByIdAndUpdate(): First argument must not be a function.\n\n'
1630 + ' ' + this.modelName + '.findByIdAndUpdate(id, callback)\n'
1631 + ' ' + this.modelName + '.findByIdAndUpdate(id)\n'
1632 + ' ' + this.modelName + '.findByIdAndUpdate()\n';
1633 throw new TypeError(msg);
1634 }
1635 return this.findOneAndUpdate({_id: id}, undefined);
1636 }
1637
1638 // if a model is passed in instead of an id
1639 if (id instanceof Document) {
1640 id = id._id;
1641 }
1642
1643 return this.findOneAndUpdate.call(this, {_id: id}, update, options, callback);
1644};
1645
1646/**
1647 * Issue a mongodb findAndModify remove command.
1648 *
1649 * Finds a matching document, removes it, passing the found document (if any) to the callback.
1650 *
1651 * Executes immediately if `callback` is passed else a Query object is returned.
1652 *
1653 * ####Options:
1654 *
1655 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1656 * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
1657 * - `select`: sets the document fields to return
1658 *
1659 * ####Examples:
1660 *
1661 * A.findOneAndRemove(conditions, options, callback) // executes
1662 * A.findOneAndRemove(conditions, options) // return Query
1663 * A.findOneAndRemove(conditions, callback) // executes
1664 * A.findOneAndRemove(conditions) // returns Query
1665 * A.findOneAndRemove() // returns Query
1666 *
1667 * Values are cast to their appropriate types when using the findAndModify helpers.
1668 * However, the below are never executed.
1669 *
1670 * - defaults
1671 * - setters
1672 *
1673 * `findAndModify` helpers support limited defaults and validation. You can
1674 * enable these by setting the `setDefaultsOnInsert` and `runValidators` options,
1675 * respectively.
1676 *
1677 * If you need full-fledged validation, use the traditional approach of first
1678 * retrieving the document.
1679 *
1680 * Model.findById(id, function (err, doc) {
1681 * if (err) ..
1682 * doc.name = 'jason borne';
1683 * doc.save(callback);
1684 * });
1685 *
1686 * @param {Object} conditions
1687 * @param {Object} [options]
1688 * @param {Function} [callback]
1689 * @return {Query}
1690 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1691 * @api public
1692 */
1693
1694Model.findOneAndRemove = function(conditions, options, callback) {
1695 if (arguments.length === 1 && typeof conditions === 'function') {
1696 var msg = 'Model.findOneAndRemove(): First argument must not be a function.\n\n'
1697 + ' ' + this.modelName + '.findOneAndRemove(conditions, callback)\n'
1698 + ' ' + this.modelName + '.findOneAndRemove(conditions)\n'
1699 + ' ' + this.modelName + '.findOneAndRemove()\n';
1700 throw new TypeError(msg);
1701 }
1702
1703 if (typeof options === 'function') {
1704 callback = options;
1705 options = undefined;
1706 }
1707 if (callback) {
1708 callback = this.$wrapCallback(callback);
1709 }
1710
1711 var fields;
1712 if (options) {
1713 fields = options.select;
1714 options.select = undefined;
1715 }
1716
1717 var mq = new this.Query({}, {}, this, this.collection);
1718 mq.select(fields);
1719
1720 return mq.findOneAndRemove(conditions, options, callback);
1721};
1722
1723/**
1724 * Issue a mongodb findAndModify remove command by a document's _id field. `findByIdAndRemove(id, ...)` is equivalent to `findOneAndRemove({ _id: id }, ...)`.
1725 *
1726 * Finds a matching document, removes it, passing the found document (if any) to the callback.
1727 *
1728 * Executes immediately if `callback` is passed, else a `Query` object is returned.
1729 *
1730 * ####Options:
1731 *
1732 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1733 * - `select`: sets the document fields to return
1734 *
1735 * ####Examples:
1736 *
1737 * A.findByIdAndRemove(id, options, callback) // executes
1738 * A.findByIdAndRemove(id, options) // return Query
1739 * A.findByIdAndRemove(id, callback) // executes
1740 * A.findByIdAndRemove(id) // returns Query
1741 * A.findByIdAndRemove() // returns Query
1742 *
1743 * @param {Object|Number|String} id value of `_id` to query by
1744 * @param {Object} [options]
1745 * @param {Function} [callback]
1746 * @return {Query}
1747 * @see Model.findOneAndRemove #model_Model.findOneAndRemove
1748 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1749 */
1750
1751Model.findByIdAndRemove = function(id, options, callback) {
1752 if (arguments.length === 1 && typeof id === 'function') {
1753 var msg = 'Model.findByIdAndRemove(): First argument must not be a function.\n\n'
1754 + ' ' + this.modelName + '.findByIdAndRemove(id, callback)\n'
1755 + ' ' + this.modelName + '.findByIdAndRemove(id)\n'
1756 + ' ' + this.modelName + '.findByIdAndRemove()\n';
1757 throw new TypeError(msg);
1758 }
1759 if (callback) {
1760 callback = this.$wrapCallback(callback);
1761 }
1762
1763 return this.findOneAndRemove({_id: id}, options, callback);
1764};
1765
1766/**
1767 * Shortcut for saving one or more documents to the database.
1768 * `MyModel.create(docs)` does `new MyModel(doc).save()` for every doc in
1769 * docs.
1770 *
1771 * Hooks Triggered:
1772 * - `save()`
1773 *
1774 * ####Example:
1775 *
1776 * // pass individual docs
1777 * Candy.create({ type: 'jelly bean' }, { type: 'snickers' }, function (err, jellybean, snickers) {
1778 * if (err) // ...
1779 * });
1780 *
1781 * // pass an array
1782 * var array = [{ type: 'jelly bean' }, { type: 'snickers' }];
1783 * Candy.create(array, function (err, candies) {
1784 * if (err) // ...
1785 *
1786 * var jellybean = candies[0];
1787 * var snickers = candies[1];
1788 * // ...
1789 * });
1790 *
1791 * // callback is optional; use the returned promise if you like:
1792 * var promise = Candy.create({ type: 'jawbreaker' });
1793 * promise.then(function (jawbreaker) {
1794 * // ...
1795 * })
1796 *
1797 * @param {Array|Object|*} doc(s)
1798 * @param {Function} [callback] callback
1799 * @return {Promise}
1800 * @api public
1801 */
1802
1803Model.create = function create(doc, callback) {
1804 var args;
1805 var cb;
1806
1807 if (Array.isArray(doc)) {
1808 args = doc;
1809 cb = callback;
1810 } else {
1811 var last = arguments[arguments.length - 1];
1812 if (typeof last === 'function') {
1813 cb = last;
1814 args = utils.args(arguments, 0, arguments.length - 1);
1815 } else {
1816 args = utils.args(arguments);
1817 }
1818 }
1819
1820 var Promise = PromiseProvider.get();
1821 var _this = this;
1822 if (cb) {
1823 cb = this.$wrapCallback(cb);
1824 }
1825
1826 var promise = new Promise.ES6(function(resolve, reject) {
1827 if (args.length === 0) {
1828 setImmediate(function() {
1829 cb && cb(null);
1830 resolve(null);
1831 });
1832 return;
1833 }
1834
1835 var toExecute = [];
1836 args.forEach(function(doc) {
1837 toExecute.push(function(callback) {
1838 var toSave = new _this(doc);
1839 var callbackWrapper = function(error, doc) {
1840 if (error) {
1841 return callback(error);
1842 }
1843 callback(null, doc);
1844 };
1845
1846 // Hack to avoid getting a promise because of
1847 // $__registerHooksFromSchema
1848 if (toSave.$__original_save) {
1849 toSave.$__original_save({ __noPromise: true }, callbackWrapper);
1850 } else {
1851 toSave.save({ __noPromise: true }, callbackWrapper);
1852 }
1853 });
1854 });
1855
1856 async.parallel(toExecute, function(error, savedDocs) {
1857 if (error) {
1858 cb && cb(error);
1859 reject(error);
1860 return;
1861 }
1862
1863 if (doc instanceof Array) {
1864 resolve(savedDocs);
1865 cb && cb.call(_this, null, savedDocs);
1866 } else {
1867 resolve.apply(promise, savedDocs);
1868 if (cb) {
1869 savedDocs.unshift(null);
1870 cb.apply(_this, savedDocs);
1871 }
1872 }
1873 });
1874 });
1875
1876 return promise;
1877};
1878
1879/**
1880 * Shortcut for validating an array of documents and inserting them into
1881 * MongoDB if they're all valid. This function is faster than `.create()`
1882 * because it only sends one operation to the server, rather than one for each
1883 * document.
1884 *
1885 * This function does **not** trigger save middleware.
1886 *
1887 * ####Example:
1888 *
1889 * var arr = [{ name: 'Star Wars' }, { name: 'The Empire Strikes Back' }];
1890 * Movies.insertMany(arr, function(error, docs) {});
1891 *
1892 * @param {Array|Object|*} doc(s)
1893 * @param {Function} [callback] callback
1894 * @return {Promise}
1895 * @api public
1896 */
1897
1898Model.insertMany = function(arr, callback) {
1899 var _this = this;
1900 if (callback) {
1901 callback = this.$wrapCallback(callback);
1902 }
1903
1904 var toExecute = [];
1905 arr.forEach(function(doc) {
1906 toExecute.push(function(callback) {
1907 doc = new _this(doc);
1908 doc.validate({ __noPromise: true }, function(error) {
1909 if (error) {
1910 return callback(error);
1911 }
1912 callback(null, doc);
1913 });
1914 });
1915 });
1916
1917 async.parallel(toExecute, function(error, docs) {
1918 if (error) {
1919 callback && callback(error);
1920 return;
1921 }
1922 var docObjects = docs.map(function(doc) {
1923 if (doc.initializeTimestamps) {
1924 return doc.initializeTimestamps().toObject({ virtuals: false });
1925 }
1926 return doc.toObject({ virtuals: false });
1927 });
1928 _this.collection.insertMany(docObjects, function(error) {
1929 if (error) {
1930 callback && callback(error);
1931 return;
1932 }
1933 for (var i = 0; i < docs.length; ++i) {
1934 docs[i].isNew = false;
1935 docs[i].emit('isNew', false);
1936 }
1937 callback && callback(null, docs);
1938 });
1939 });
1940};
1941
1942/**
1943 * Shortcut for creating a new Document from existing raw data, pre-saved in the DB.
1944 * The document returned has no paths marked as modified initially.
1945 *
1946 * ####Example:
1947 *
1948 * // hydrate previous data into a Mongoose document
1949 * var mongooseCandy = Candy.hydrate({ _id: '54108337212ffb6d459f854c', type: 'jelly bean' });
1950 *
1951 * @param {Object} obj
1952 * @return {Document}
1953 * @api public
1954 */
1955
1956Model.hydrate = function(obj) {
1957 var model = require('./queryhelpers').createModel(this, obj);
1958 model.init(obj);
1959 return model;
1960};
1961
1962/**
1963 * Updates documents in the database without returning them.
1964 *
1965 * ####Examples:
1966 *
1967 * MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);
1968 * MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, function (err, raw) {
1969 * if (err) return handleError(err);
1970 * console.log('The raw response from Mongo was ', raw);
1971 * });
1972 *
1973 * ####Valid options:
1974 *
1975 * - `safe` (boolean) safe mode (defaults to value set in schema (true))
1976 * - `upsert` (boolean) whether to create the doc if it doesn't match (false)
1977 * - `multi` (boolean) whether multiple documents should be updated (false)
1978 * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
1979 * - `setDefaultsOnInsert`: if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/).
1980 * - `strict` (boolean) overrides the `strict` option for this update
1981 * - `overwrite` (boolean) disables update-only mode, allowing you to overwrite the doc (false)
1982 *
1983 * All `update` values are cast to their appropriate SchemaTypes before being sent.
1984 *
1985 * The `callback` function receives `(err, rawResponse)`.
1986 *
1987 * - `err` is the error if any occurred
1988 * - `rawResponse` is the full response from Mongo
1989 *
1990 * ####Note:
1991 *
1992 * All top level keys which are not `atomic` operation names are treated as set operations:
1993 *
1994 * ####Example:
1995 *
1996 * var query = { name: 'borne' };
1997 * Model.update(query, { name: 'jason borne' }, options, callback)
1998 *
1999 * // is sent as
2000 * Model.update(query, { $set: { name: 'jason borne' }}, options, callback)
2001 * // if overwrite option is false. If overwrite is true, sent without the $set wrapper.
2002 *
2003 * This helps prevent accidentally overwriting all documents in your collection with `{ name: 'jason borne' }`.
2004 *
2005 * ####Note:
2006 *
2007 * Be careful to not use an existing model instance for the update clause (this won't work and can cause weird behavior like infinite loops). Also, ensure that the update clause does not have an _id property, which causes Mongo to return a "Mod on _id not allowed" error.
2008 *
2009 * ####Note:
2010 *
2011 * To update documents without waiting for a response from MongoDB, do not pass a `callback`, then call `exec` on the returned [Query](#query-js):
2012 *
2013 * Comment.update({ _id: id }, { $set: { text: 'changed' }}).exec();
2014 *
2015 * ####Note:
2016 *
2017 * Although values are casted to their appropriate types when using update, the following are *not* applied:
2018 *
2019 * - defaults
2020 * - setters
2021 * - validators
2022 * - middleware
2023 *
2024 * If you need those features, use the traditional approach of first retrieving the document.
2025 *
2026 * Model.findOne({ name: 'borne' }, function (err, doc) {
2027 * if (err) ..
2028 * doc.name = 'jason borne';
2029 * doc.save(callback);
2030 * })
2031 *
2032 * @see strict http://mongoosejs.com/docs/guide.html#strict
2033 * @see response http://docs.mongodb.org/v2.6/reference/command/update/#output
2034 * @param {Object} conditions
2035 * @param {Object} doc
2036 * @param {Object} [options]
2037 * @param {Function} [callback]
2038 * @return {Query}
2039 * @api public
2040 */
2041
2042Model.update = function update(conditions, doc, options, callback) {
2043 var mq = new this.Query({}, {}, this, this.collection);
2044 if (callback) {
2045 callback = this.$wrapCallback(callback);
2046 }
2047 // gh-2406
2048 // make local deep copy of conditions
2049 if (conditions instanceof Document) {
2050 conditions = conditions.toObject();
2051 } else {
2052 conditions = utils.clone(conditions, {retainKeyOrder: true});
2053 }
2054 options = typeof options === 'function' ? options : utils.clone(options);
2055
2056 return mq.update(conditions, doc, options, callback);
2057};
2058
2059/**
2060 * Executes a mapReduce command.
2061 *
2062 * `o` is an object specifying all mapReduce options as well as the map and reduce functions. All options are delegated to the driver implementation. See [node-mongodb-native mapReduce() documentation](http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#mapreduce) for more detail about options.
2063 *
2064 * ####Example:
2065 *
2066 * var o = {};
2067 * o.map = function () { emit(this.name, 1) }
2068 * o.reduce = function (k, vals) { return vals.length }
2069 * User.mapReduce(o, function (err, results) {
2070 * console.log(results)
2071 * })
2072 *
2073 * ####Other options:
2074 *
2075 * - `query` {Object} query filter object.
2076 * - `sort` {Object} sort input objects using this key
2077 * - `limit` {Number} max number of documents
2078 * - `keeptemp` {Boolean, default:false} keep temporary data
2079 * - `finalize` {Function} finalize function
2080 * - `scope` {Object} scope variables exposed to map/reduce/finalize during execution
2081 * - `jsMode` {Boolean, default:false} it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X
2082 * - `verbose` {Boolean, default:false} provide statistics on job execution time.
2083 * - `readPreference` {String}
2084 * - `out*` {Object, default: {inline:1}} sets the output target for the map reduce job.
2085 *
2086 * ####* out options:
2087 *
2088 * - `{inline:1}` the results are returned in an array
2089 * - `{replace: 'collectionName'}` add the results to collectionName: the results replace the collection
2090 * - `{reduce: 'collectionName'}` add the results to collectionName: if dups are detected, uses the reducer / finalize functions
2091 * - `{merge: 'collectionName'}` add the results to collectionName: if dups exist the new docs overwrite the old
2092 *
2093 * If `options.out` is set to `replace`, `merge`, or `reduce`, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the `lean` option; meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc).
2094 *
2095 * ####Example:
2096 *
2097 * var o = {};
2098 * o.map = function () { emit(this.name, 1) }
2099 * o.reduce = function (k, vals) { return vals.length }
2100 * o.out = { replace: 'createdCollectionNameForResults' }
2101 * o.verbose = true;
2102 *
2103 * User.mapReduce(o, function (err, model, stats) {
2104 * console.log('map reduce took %d ms', stats.processtime)
2105 * model.find().where('value').gt(10).exec(function (err, docs) {
2106 * console.log(docs);
2107 * });
2108 * })
2109 *
2110 * // a promise is returned so you may instead write
2111 * var promise = User.mapReduce(o);
2112 * promise.then(function (model, stats) {
2113 * console.log('map reduce took %d ms', stats.processtime)
2114 * return model.find().where('value').gt(10).exec();
2115 * }).then(function (docs) {
2116 * console.log(docs);
2117 * }).then(null, handleError).end()
2118 *
2119 * @param {Object} o an object specifying map-reduce options
2120 * @param {Function} [callback] optional callback
2121 * @see http://www.mongodb.org/display/DOCS/MapReduce
2122 * @return {Promise}
2123 * @api public
2124 */
2125
2126Model.mapReduce = function mapReduce(o, callback) {
2127 var _this = this;
2128 if (callback) {
2129 callback = this.$wrapCallback(callback);
2130 }
2131 var Promise = PromiseProvider.get();
2132 return new Promise.ES6(function(resolve, reject) {
2133 if (!Model.mapReduce.schema) {
2134 var opts = {noId: true, noVirtualId: true, strict: false};
2135 Model.mapReduce.schema = new Schema({}, opts);
2136 }
2137
2138 if (!o.out) o.out = {inline: 1};
2139 if (o.verbose !== false) o.verbose = true;
2140
2141 o.map = String(o.map);
2142 o.reduce = String(o.reduce);
2143
2144 if (o.query) {
2145 var q = new _this.Query(o.query);
2146 q.cast(_this);
2147 o.query = q._conditions;
2148 q = undefined;
2149 }
2150
2151 _this.collection.mapReduce(null, null, o, function(err, ret, stats) {
2152 if (err) {
2153 callback && callback(err);
2154 reject(err);
2155 return;
2156 }
2157
2158 if (ret.findOne && ret.mapReduce) {
2159 // returned a collection, convert to Model
2160 var model = Model.compile(
2161 '_mapreduce_' + ret.collectionName
2162 , Model.mapReduce.schema
2163 , ret.collectionName
2164 , _this.db
2165 , _this.base);
2166
2167 model._mapreduce = true;
2168
2169 callback && callback(null, model, stats);
2170 return resolve(model, stats);
2171 }
2172
2173 callback && callback(null, ret, stats);
2174 resolve(ret, stats);
2175 });
2176 });
2177};
2178
2179/**
2180 * geoNear support for Mongoose
2181 *
2182 * ####Options:
2183 * - `lean` {Boolean} return the raw object
2184 * - All options supported by the driver are also supported
2185 *
2186 * ####Example:
2187 *
2188 * // Legacy point
2189 * Model.geoNear([1,3], { maxDistance : 5, spherical : true }, function(err, results, stats) {
2190 * console.log(results);
2191 * });
2192 *
2193 * // geoJson
2194 * var point = { type : "Point", coordinates : [9,9] };
2195 * Model.geoNear(point, { maxDistance : 5, spherical : true }, function(err, results, stats) {
2196 * console.log(results);
2197 * });
2198 *
2199 * @param {Object|Array} GeoJSON point or legacy coordinate pair [x,y] to search near
2200 * @param {Object} options for the qurery
2201 * @param {Function} [callback] optional callback for the query
2202 * @return {Promise}
2203 * @see http://docs.mongodb.org/manual/core/2dsphere/
2204 * @see http://mongodb.github.io/node-mongodb-native/api-generated/collection.html?highlight=geonear#geoNear
2205 * @api public
2206 */
2207
2208Model.geoNear = function(near, options, callback) {
2209 if (typeof options === 'function') {
2210 callback = options;
2211 options = {};
2212 }
2213
2214 if (callback) {
2215 callback = this.$wrapCallback(callback);
2216 }
2217
2218 var _this = this;
2219 var Promise = PromiseProvider.get();
2220 if (!near) {
2221 return new Promise.ES6(function(resolve, reject) {
2222 var error = new Error('Must pass a near option to geoNear');
2223 reject(error);
2224 callback && callback(error);
2225 });
2226 }
2227
2228 var x, y;
2229
2230 return new Promise.ES6(function(resolve, reject) {
2231 var handler = function(err, res) {
2232 if (err) {
2233 reject(err);
2234 callback && callback(err);
2235 return;
2236 }
2237 if (options.lean) {
2238 resolve(res.results, res.stats);
2239 callback && callback(null, res.results, res.stats);
2240 return;
2241 }
2242
2243 var count = res.results.length;
2244 // if there are no results, fulfill the promise now
2245 if (count === 0) {
2246 resolve(res.results, res.stats);
2247 callback && callback(null, res.results, res.stats);
2248 return;
2249 }
2250
2251 var errSeen = false;
2252
2253 function init(err) {
2254 if (err && !errSeen) {
2255 errSeen = true;
2256 reject(err);
2257 callback && callback(err);
2258 return;
2259 }
2260 if (--count <= 0) {
2261 resolve(res.results, res.stats);
2262 callback && callback(null, res.results, res.stats);
2263 }
2264 }
2265
2266 for (var i = 0; i < res.results.length; i++) {
2267 var temp = res.results[i].obj;
2268 res.results[i].obj = new _this();
2269 res.results[i].obj.init(temp, init);
2270 }
2271 };
2272
2273 if (Array.isArray(near)) {
2274 if (near.length !== 2) {
2275 var error = new Error('If using legacy coordinates, must be an array ' +
2276 'of size 2 for geoNear');
2277 reject(error);
2278 callback && callback(error);
2279 return;
2280 }
2281 x = near[0];
2282 y = near[1];
2283 _this.collection.geoNear(x, y, options, handler);
2284 } else {
2285 if (near.type !== 'Point' || !Array.isArray(near.coordinates)) {
2286 error = new Error('Must pass either a legacy coordinate array or ' +
2287 'GeoJSON Point to geoNear');
2288 reject(error);
2289 callback && callback(error);
2290 return;
2291 }
2292
2293 _this.collection.geoNear(near, options, handler);
2294 }
2295 });
2296};
2297
2298/**
2299 * Performs [aggregations](http://docs.mongodb.org/manual/applications/aggregation/) on the models collection.
2300 *
2301 * If a `callback` is passed, the `aggregate` is executed and a `Promise` is returned. If a callback is not passed, the `aggregate` itself is returned.
2302 *
2303 * ####Example:
2304 *
2305 * // Find the max balance of all accounts
2306 * Users.aggregate(
2307 * { $group: { _id: null, maxBalance: { $max: '$balance' }}}
2308 * , { $project: { _id: 0, maxBalance: 1 }}
2309 * , function (err, res) {
2310 * if (err) return handleError(err);
2311 * console.log(res); // [ { maxBalance: 98000 } ]
2312 * });
2313 *
2314 * // Or use the aggregation pipeline builder.
2315 * Users.aggregate()
2316 * .group({ _id: null, maxBalance: { $max: '$balance' } })
2317 * .select('-id maxBalance')
2318 * .exec(function (err, res) {
2319 * if (err) return handleError(err);
2320 * console.log(res); // [ { maxBalance: 98 } ]
2321 * });
2322 *
2323 * ####NOTE:
2324 *
2325 * - Arguments are not cast to the model's schema because `$project` operators allow redefining the "shape" of the documents at any stage of the pipeline, which may leave documents in an incompatible format.
2326 * - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
2327 * - Requires MongoDB >= 2.1
2328 *
2329 * @see Aggregate #aggregate_Aggregate
2330 * @see MongoDB http://docs.mongodb.org/manual/applications/aggregation/
2331 * @param {Object|Array} [...] aggregation pipeline operator(s) or operator array
2332 * @param {Function} [callback]
2333 * @return {Aggregate|Promise}
2334 * @api public
2335 */
2336
2337Model.aggregate = function aggregate() {
2338 var args = [].slice.call(arguments),
2339 aggregate,
2340 callback;
2341
2342 if (typeof args[args.length - 1] === 'function') {
2343 callback = args.pop();
2344 }
2345
2346 if (args.length === 1 && util.isArray(args[0])) {
2347 aggregate = new Aggregate(args[0]);
2348 } else {
2349 aggregate = new Aggregate(args);
2350 }
2351
2352 aggregate.model(this);
2353
2354 if (typeof callback === 'undefined') {
2355 return aggregate;
2356 }
2357
2358 if (callback) {
2359 callback = this.$wrapCallback(callback);
2360 }
2361
2362 aggregate.exec(callback);
2363};
2364
2365/**
2366 * Implements `$geoSearch` functionality for Mongoose
2367 *
2368 * ####Example:
2369 *
2370 * var options = { near: [10, 10], maxDistance: 5 };
2371 * Locations.geoSearch({ type : "house" }, options, function(err, res) {
2372 * console.log(res);
2373 * });
2374 *
2375 * ####Options:
2376 * - `near` {Array} x,y point to search for
2377 * - `maxDistance` {Number} the maximum distance from the point near that a result can be
2378 * - `limit` {Number} The maximum number of results to return
2379 * - `lean` {Boolean} return the raw object instead of the Mongoose Model
2380 *
2381 * @param {Object} conditions an object that specifies the match condition (required)
2382 * @param {Object} options for the geoSearch, some (near, maxDistance) are required
2383 * @param {Function} [callback] optional callback
2384 * @return {Promise}
2385 * @see http://docs.mongodb.org/manual/reference/command/geoSearch/
2386 * @see http://docs.mongodb.org/manual/core/geohaystack/
2387 * @api public
2388 */
2389
2390Model.geoSearch = function(conditions, options, callback) {
2391 if (typeof options === 'function') {
2392 callback = options;
2393 options = {};
2394 }
2395 if (callback) {
2396 callback = this.$wrapCallback(callback);
2397 }
2398
2399 var _this = this;
2400 var Promise = PromiseProvider.get();
2401 return new Promise.ES6(function(resolve, reject) {
2402 var error;
2403 if (conditions === undefined || !utils.isObject(conditions)) {
2404 error = new Error('Must pass conditions to geoSearch');
2405 } else if (!options.near) {
2406 error = new Error('Must specify the near option in geoSearch');
2407 } else if (!Array.isArray(options.near)) {
2408 error = new Error('near option must be an array [x, y]');
2409 }
2410
2411 if (error) {
2412 callback && callback(error);
2413 reject(error);
2414 return;
2415 }
2416
2417 // send the conditions in the options object
2418 options.search = conditions;
2419
2420 _this.collection.geoHaystackSearch(options.near[0], options.near[1], options, function(err, res) {
2421 // have to deal with driver problem. Should be fixed in a soon-ish release
2422 // (7/8/2013)
2423 if (err) {
2424 callback && callback(err);
2425 reject(err);
2426 return;
2427 }
2428
2429 var count = res.results.length;
2430 if (options.lean || count === 0) {
2431 callback && callback(null, res.results, res.stats);
2432 resolve(res.results, res.stats);
2433 return;
2434 }
2435
2436 var errSeen = false;
2437
2438 function init(err) {
2439 if (err && !errSeen) {
2440 callback && callback(err);
2441 reject(err);
2442 return;
2443 }
2444
2445 if (!--count && !errSeen) {
2446 callback && callback(null, res.results, res.stats);
2447 resolve(res.results, res.stats);
2448 }
2449 }
2450
2451 for (var i = 0; i < res.results.length; i++) {
2452 var temp = res.results[i];
2453 res.results[i] = new _this();
2454 res.results[i].init(temp, {}, init);
2455 }
2456 });
2457 });
2458};
2459
2460/**
2461 * Populates document references.
2462 *
2463 * ####Available options:
2464 *
2465 * - path: space delimited path(s) to populate
2466 * - select: optional fields to select
2467 * - match: optional query conditions to match
2468 * - model: optional name of the model to use for population
2469 * - options: optional query options like sort, limit, etc
2470 *
2471 * ####Examples:
2472 *
2473 * // populates a single object
2474 * User.findById(id, function (err, user) {
2475 * var opts = [
2476 * { path: 'company', match: { x: 1 }, select: 'name' }
2477 * , { path: 'notes', options: { limit: 10 }, model: 'override' }
2478 * ]
2479 *
2480 * User.populate(user, opts, function (err, user) {
2481 * console.log(user);
2482 * })
2483 * })
2484 *
2485 * // populates an array of objects
2486 * User.find(match, function (err, users) {
2487 * var opts = [{ path: 'company', match: { x: 1 }, select: 'name' }]
2488 *
2489 * var promise = User.populate(users, opts);
2490 * promise.then(console.log).end();
2491 * })
2492 *
2493 * // imagine a Weapon model exists with two saved documents:
2494 * // { _id: 389, name: 'whip' }
2495 * // { _id: 8921, name: 'boomerang' }
2496 *
2497 * var user = { name: 'Indiana Jones', weapon: 389 }
2498 * Weapon.populate(user, { path: 'weapon', model: 'Weapon' }, function (err, user) {
2499 * console.log(user.weapon.name) // whip
2500 * })
2501 *
2502 * // populate many plain objects
2503 * var users = [{ name: 'Indiana Jones', weapon: 389 }]
2504 * users.push({ name: 'Batman', weapon: 8921 })
2505 * Weapon.populate(users, { path: 'weapon' }, function (err, users) {
2506 * users.forEach(function (user) {
2507 * console.log('%s uses a %s', users.name, user.weapon.name)
2508 * // Indiana Jones uses a whip
2509 * // Batman uses a boomerang
2510 * })
2511 * })
2512 * // Note that we didn't need to specify the Weapon model because
2513 * // we were already using it's populate() method.
2514 *
2515 * @param {Document|Array} docs Either a single document or array of documents to populate.
2516 * @param {Object} options A hash of key/val (path, options) used for population.
2517 * @param {Function} [callback(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
2518 * @return {Promise}
2519 * @api public
2520 */
2521
2522Model.populate = function(docs, paths, callback) {
2523 var _this = this;
2524 if (callback) {
2525 callback = this.$wrapCallback(callback);
2526 }
2527
2528 // normalized paths
2529 var noPromise = paths && !!paths.__noPromise;
2530 paths = utils.populate(paths);
2531
2532 // data that should persist across subPopulate calls
2533 var cache = {};
2534
2535 if (noPromise) {
2536 _populate(this, docs, paths, cache, callback);
2537 } else {
2538 var Promise = PromiseProvider.get();
2539 return new Promise.ES6(function(resolve, reject) {
2540 _populate(_this, docs, paths, cache, function(error, docs) {
2541 if (error) {
2542 callback && callback(error);
2543 reject(error);
2544 } else {
2545 callback && callback(null, docs);
2546 resolve(docs);
2547 }
2548 });
2549 });
2550 }
2551};
2552
2553/*!
2554 * Populate helper
2555 *
2556 * @param {Model} model the model to use
2557 * @param {Document|Array} docs Either a single document or array of documents to populate.
2558 * @param {Object} paths
2559 * @param {Function} [cb(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
2560 * @return {Function}
2561 * @api private
2562 */
2563
2564function _populate(model, docs, paths, cache, callback) {
2565 var pending = paths.length;
2566
2567 if (pending === 0) {
2568 return callback(null, docs);
2569 }
2570
2571 // each path has its own query options and must be executed separately
2572 var i = pending;
2573 var path;
2574 while (i--) {
2575 path = paths[i];
2576 populate(model, docs, path, next);
2577 }
2578
2579 function next(err) {
2580 if (err) {
2581 return callback(err);
2582 }
2583 if (--pending) {
2584 return;
2585 }
2586 callback(null, docs);
2587 }
2588}
2589
2590/*!
2591 * Populates `docs`
2592 */
2593var excludeIdReg = /\s?-_id\s?/,
2594 excludeIdRegGlobal = /\s?-_id\s?/g;
2595
2596function populate(model, docs, options, callback) {
2597 var modelsMap;
2598
2599 // normalize single / multiple docs passed
2600 if (!Array.isArray(docs)) {
2601 docs = [docs];
2602 }
2603
2604 if (docs.length === 0 || docs.every(utils.isNullOrUndefined)) {
2605 return callback();
2606 }
2607
2608 modelsMap = getModelsMapForPopulate(model, docs, options);
2609
2610 var i, len = modelsMap.length,
2611 mod, match, select, vals = [];
2612
2613 function flatten(item) {
2614 // no need to include undefined values in our query
2615 return undefined !== item;
2616 }
2617
2618 var _remaining = len;
2619 var hasOne = false;
2620 for (i = 0; i < len; i++) {
2621 mod = modelsMap[i];
2622 select = mod.options.select;
2623
2624 if (mod.options.match) {
2625 match = utils.object.shallowCopy(mod.options.match);
2626 } else {
2627 match = {};
2628 }
2629
2630 var ids = utils.array.flatten(mod.ids, flatten);
2631 ids = utils.array.unique(ids);
2632
2633 if (ids.length === 0 || ids.every(utils.isNullOrUndefined)) {
2634 --_remaining;
2635 continue;
2636 }
2637
2638 hasOne = true;
2639 if (mod.foreignField !== '_id' || !match['_id']) {
2640 match[mod.foreignField] = { $in: ids };
2641 }
2642
2643 var assignmentOpts = {};
2644 assignmentOpts.sort = mod.options.options && mod.options.options.sort || undefined;
2645 assignmentOpts.excludeId = excludeIdReg.test(select) || (select && select._id === 0);
2646
2647 if (assignmentOpts.excludeId) {
2648 // override the exclusion from the query so we can use the _id
2649 // for document matching during assignment. we'll delete the
2650 // _id back off before returning the result.
2651 if (typeof select === 'string') {
2652 select = select.replace(excludeIdRegGlobal, ' ');
2653 } else {
2654 // preserve original select conditions by copying
2655 select = utils.object.shallowCopy(select);
2656 delete select._id;
2657 }
2658 }
2659
2660 if (mod.options.options && mod.options.options.limit) {
2661 assignmentOpts.originalLimit = mod.options.options.limit;
2662 mod.options.options.limit = mod.options.options.limit * ids.length;
2663 }
2664
2665 var subPopulate = mod.options.populate;
2666 var query = mod.Model.find(match, select, mod.options.options);
2667 if (subPopulate) {
2668 query.populate(subPopulate);
2669 }
2670 query.exec(next.bind(this, mod, assignmentOpts));
2671 }
2672
2673 if (!hasOne) {
2674 return callback();
2675 }
2676
2677 function next(options, assignmentOpts, err, valsFromDb) {
2678 if (err) return callback(err);
2679 vals = vals.concat(valsFromDb);
2680 _assign(null, vals, options, assignmentOpts);
2681 if (--_remaining === 0) {
2682 callback();
2683 }
2684 }
2685
2686 function _assign(err, vals, mod, assignmentOpts) {
2687 if (err) return callback(err);
2688
2689 var options = mod.options;
2690 var _val;
2691 var lean = options.options && options.options.lean,
2692 len = vals.length,
2693 rawOrder = {}, rawDocs = {}, key, val;
2694
2695 // optimization:
2696 // record the document positions as returned by
2697 // the query result.
2698 for (var i = 0; i < len; i++) {
2699 val = vals[i];
2700 if (val) {
2701 _val = utils.getValue(mod.foreignField, val);
2702 if (Array.isArray(_val)) {
2703 var _valLength = _val.length;
2704 for (var j = 0; j < _valLength; ++j) {
2705 key = String(_val[j]);
2706 if (rawDocs[key]) {
2707 if (Array.isArray(rawDocs[key])) {
2708 rawDocs[key].push(val);
2709 rawOrder[key].push(i);
2710 } else {
2711 rawDocs[key] = [rawDocs[key], val];
2712 rawOrder[key] = [rawOrder[key], i];
2713 }
2714 } else {
2715 rawDocs[key] = val;
2716 rawOrder[key] = i;
2717 }
2718 }
2719 } else {
2720 key = String(utils.getValue(mod.foreignField, val));
2721 if (rawDocs[key]) {
2722 if (Array.isArray(rawDocs[key])) {
2723 rawDocs[key].push(val);
2724 rawOrder[key].push(i);
2725 } else {
2726 rawDocs[key] = [rawDocs[key], val];
2727 rawOrder[key] = [rawOrder[key], i];
2728 }
2729 } else {
2730 rawDocs[key] = val;
2731 rawOrder[key] = i;
2732 }
2733 }
2734 // flag each as result of population
2735 if (!lean) {
2736 val.$__.wasPopulated = true;
2737 }
2738 }
2739 }
2740
2741 assignVals({
2742 originalModel: model,
2743 rawIds: mod.ids,
2744 localField: mod.localField,
2745 foreignField: mod.foreignField,
2746 rawDocs: rawDocs,
2747 rawOrder: rawOrder,
2748 docs: mod.docs,
2749 path: options.path,
2750 options: assignmentOpts,
2751 justOne: mod.justOne,
2752 isVirtual: mod.isVirtual
2753 });
2754 }
2755}
2756
2757/*!
2758 * Assigns documents returned from a population query back
2759 * to the original document path.
2760 */
2761
2762function assignVals(o) {
2763 // replace the original ids in our intermediate _ids structure
2764 // with the documents found by query
2765 assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, o.options,
2766 o.localField, o.foreignField);
2767
2768 // now update the original documents being populated using the
2769 // result structure that contains real documents.
2770
2771 var docs = o.docs;
2772 var rawIds = o.rawIds;
2773 var options = o.options;
2774
2775 function setValue(val) {
2776 return valueFilter(val, options);
2777 }
2778
2779 for (var i = 0; i < docs.length; ++i) {
2780 if (utils.getValue(o.path, docs[i]) == null &&
2781 !o.originalModel.schema.virtuals[o.path]) {
2782 continue;
2783 }
2784
2785 if (o.isVirtual && !o.justOne && !Array.isArray(rawIds[i])) {
2786 rawIds[i] = [rawIds[i]];
2787 }
2788 utils.setValue(o.path, rawIds[i], docs[i], setValue);
2789 }
2790}
2791
2792/*!
2793 * Assign `vals` returned by mongo query to the `rawIds`
2794 * structure returned from utils.getVals() honoring
2795 * query sort order if specified by user.
2796 *
2797 * This can be optimized.
2798 *
2799 * Rules:
2800 *
2801 * if the value of the path is not an array, use findOne rules, else find.
2802 * for findOne the results are assigned directly to doc path (including null results).
2803 * for find, if user specified sort order, results are assigned directly
2804 * else documents are put back in original order of array if found in results
2805 *
2806 * @param {Array} rawIds
2807 * @param {Array} vals
2808 * @param {Boolean} sort
2809 * @api private
2810 */
2811
2812function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, localFields, foreignFields, recursed) {
2813 // honor user specified sort order
2814 var newOrder = [];
2815 var sorting = options.sort && rawIds.length > 1;
2816 var doc;
2817 var sid;
2818 var id;
2819
2820 for (var i = 0; i < rawIds.length; ++i) {
2821 id = rawIds[i];
2822
2823 if (Array.isArray(id)) {
2824 // handle [ [id0, id2], [id3] ]
2825 assignRawDocsToIdStructure(id, resultDocs, resultOrder, options, localFields, foreignFields, true);
2826 newOrder.push(id);
2827 continue;
2828 }
2829
2830 if (id === null && !sorting) {
2831 // keep nulls for findOne unless sorting, which always
2832 // removes them (backward compat)
2833 newOrder.push(id);
2834 continue;
2835 }
2836
2837 sid = String(id);
2838
2839 if (recursed) {
2840 // apply find behavior
2841
2842 // assign matching documents in original order unless sorting
2843 doc = resultDocs[sid];
2844 if (doc) {
2845 if (sorting) {
2846 newOrder[resultOrder[sid]] = doc;
2847 } else {
2848 newOrder.push(doc);
2849 }
2850 } else {
2851 newOrder.push(id);
2852 }
2853 } else {
2854 // apply findOne behavior - if document in results, assign, else assign null
2855 newOrder[i] = doc = resultDocs[sid] || null;
2856 }
2857 }
2858
2859 rawIds.length = 0;
2860 if (newOrder.length) {
2861 // reassign the documents based on corrected order
2862
2863 // forEach skips over sparse entries in arrays so we
2864 // can safely use this to our advantage dealing with sorted
2865 // result sets too.
2866 newOrder.forEach(function(doc, i) {
2867 if (!doc) {
2868 return;
2869 }
2870 rawIds[i] = doc;
2871 });
2872 }
2873}
2874
2875function getModelsMapForPopulate(model, docs, options) {
2876 var i, doc, len = docs.length,
2877 available = {},
2878 map = [],
2879 modelNameFromQuery = options.model && options.model.modelName || options.model,
2880 schema, refPath, Model, currentOptions, modelNames, modelName, discriminatorKey, modelForFindSchema;
2881
2882 var originalOptions = utils.clone(options);
2883 var isVirtual = false;
2884
2885 schema = model._getSchema(options.path);
2886
2887 if (schema && schema.caster) {
2888 schema = schema.caster;
2889 }
2890
2891 if (!schema && model.discriminators) {
2892 discriminatorKey = model.schema.discriminatorMapping.key;
2893 }
2894
2895 refPath = schema && schema.options && schema.options.refPath;
2896
2897 for (i = 0; i < len; i++) {
2898 doc = docs[i];
2899
2900 if (refPath) {
2901 modelNames = utils.getValue(refPath, doc);
2902 } else {
2903 if (!modelNameFromQuery) {
2904 var modelForCurrentDoc = model;
2905 var schemaForCurrentDoc;
2906
2907 if (!schema && discriminatorKey) {
2908 modelForFindSchema = utils.getValue(discriminatorKey, doc);
2909
2910 if (modelForFindSchema) {
2911 modelForCurrentDoc = model.db.model(modelForFindSchema);
2912 schemaForCurrentDoc = modelForCurrentDoc._getSchema(options.path);
2913
2914 if (schemaForCurrentDoc && schemaForCurrentDoc.caster) {
2915 schemaForCurrentDoc = schemaForCurrentDoc.caster;
2916 }
2917 }
2918 } else {
2919 schemaForCurrentDoc = schema;
2920 }
2921 var virtual = modelForCurrentDoc.schema.virtuals[options.path];
2922
2923 if (schemaForCurrentDoc && schemaForCurrentDoc.options && schemaForCurrentDoc.options.ref) {
2924 modelNames = [schemaForCurrentDoc.options.ref];
2925 } else if (virtual && virtual.options && virtual.options.ref) {
2926 modelNames = [virtual && virtual.options && virtual.options.ref];
2927 isVirtual = true;
2928 } else {
2929 modelNames = [model.modelName];
2930 }
2931 } else {
2932 modelNames = [modelNameFromQuery]; // query options
2933 }
2934 }
2935
2936 if (!modelNames) {
2937 continue;
2938 }
2939
2940 if (!Array.isArray(modelNames)) {
2941 modelNames = [modelNames];
2942 }
2943
2944 virtual = model.schema.virtuals[options.path];
2945 var localField = virtual && virtual.options ?
2946 virtual.options.localField :
2947 options.path;
2948 var foreignField = virtual && virtual.options ?
2949 virtual.options.foreignField :
2950 '_id';
2951 var justOne = virtual && virtual.options && virtual.options.justOne;
2952 if (virtual && virtual.options && virtual.options.ref) {
2953 isVirtual = true;
2954 }
2955
2956 var ret = convertTo_id(utils.getValue(localField, doc));
2957 var id = String(utils.getValue(foreignField, doc));
2958 options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
2959 if (doc.$__) {
2960 doc.populated(options.path, options._docs[id], options);
2961 }
2962
2963 var k = modelNames.length;
2964 while (k--) {
2965 modelName = modelNames[k];
2966 Model = originalOptions.model && originalOptions.model.modelName ?
2967 originalOptions.model :
2968 model.db.model(modelName);
2969
2970 if (!available[modelName]) {
2971 currentOptions = {
2972 model: Model
2973 };
2974
2975 utils.merge(currentOptions, options);
2976 if (schema && !discriminatorKey) {
2977 currentOptions.model = Model;
2978 }
2979 options.model = Model;
2980
2981 available[modelName] = {
2982 Model: Model,
2983 options: currentOptions,
2984 docs: [doc],
2985 ids: [ret],
2986 // Assume only 1 localField + foreignField
2987 localField: localField,
2988 foreignField: foreignField,
2989 justOne: justOne,
2990 isVirtual: isVirtual
2991 };
2992 map.push(available[modelName]);
2993 } else {
2994 available[modelName].docs.push(doc);
2995 available[modelName].ids.push(ret);
2996 }
2997 }
2998 }
2999
3000 return map;
3001}
3002
3003/*!
3004 * Retrieve the _id of `val` if a Document or Array of Documents.
3005 *
3006 * @param {Array|Document|Any} val
3007 * @return {Array|Document|Any}
3008 */
3009
3010function convertTo_id(val) {
3011 if (val instanceof Model) return val._id;
3012
3013 if (Array.isArray(val)) {
3014 for (var i = 0; i < val.length; ++i) {
3015 if (val[i] instanceof Model) {
3016 val[i] = val[i]._id;
3017 }
3018 }
3019 return val;
3020 }
3021
3022 return val;
3023}
3024
3025/*!
3026 * 1) Apply backwards compatible find/findOne behavior to sub documents
3027 *
3028 * find logic:
3029 * a) filter out non-documents
3030 * b) remove _id from sub docs when user specified
3031 *
3032 * findOne
3033 * a) if no doc found, set to null
3034 * b) remove _id from sub docs when user specified
3035 *
3036 * 2) Remove _ids when specified by users query.
3037 *
3038 * background:
3039 * _ids are left in the query even when user excludes them so
3040 * that population mapping can occur.
3041 */
3042
3043function valueFilter(val, assignmentOpts) {
3044 if (Array.isArray(val)) {
3045 // find logic
3046 var ret = [];
3047 var numValues = val.length;
3048 for (var i = 0; i < numValues; ++i) {
3049 var subdoc = val[i];
3050 if (!isDoc(subdoc)) continue;
3051 maybeRemoveId(subdoc, assignmentOpts);
3052 ret.push(subdoc);
3053 if (assignmentOpts.originalLimit &&
3054 ret.length >= assignmentOpts.originalLimit) {
3055 break;
3056 }
3057 }
3058
3059 // Since we don't want to have to create a new mongoosearray, make sure to
3060 // modify the array in place
3061 while (val.length > ret.length) {
3062 Array.prototype.pop.apply(val, []);
3063 }
3064 for (i = 0; i < ret.length; ++i) {
3065 val[i] = ret[i];
3066 }
3067 return val;
3068 }
3069
3070 // findOne
3071 if (isDoc(val)) {
3072 maybeRemoveId(val, assignmentOpts);
3073 return val;
3074 }
3075
3076 return null;
3077}
3078
3079/*!
3080 * Remove _id from `subdoc` if user specified "lean" query option
3081 */
3082
3083function maybeRemoveId(subdoc, assignmentOpts) {
3084 if (assignmentOpts.excludeId) {
3085 if (typeof subdoc.setValue === 'function') {
3086 delete subdoc._doc._id;
3087 } else {
3088 delete subdoc._id;
3089 }
3090 }
3091}
3092
3093/*!
3094 * Determine if `doc` is a document returned
3095 * by a populate query.
3096 */
3097
3098function isDoc(doc) {
3099 if (doc == null) {
3100 return false;
3101 }
3102
3103 var type = typeof doc;
3104 if (type === 'string') {
3105 return false;
3106 }
3107
3108 if (type === 'number') {
3109 return false;
3110 }
3111
3112 if (Buffer.isBuffer(doc)) {
3113 return false;
3114 }
3115
3116 if (doc.constructor.name === 'ObjectID') {
3117 return false;
3118 }
3119
3120 // only docs
3121 return true;
3122}
3123
3124/**
3125 * Finds the schema for `path`. This is different than
3126 * calling `schema.path` as it also resolves paths with
3127 * positional selectors (something.$.another.$.path).
3128 *
3129 * @param {String} path
3130 * @return {Schema}
3131 * @api private
3132 */
3133
3134Model._getSchema = function _getSchema(path) {
3135 return this.schema._getSchema(path);
3136};
3137
3138/*!
3139 * Compiler utility.
3140 *
3141 * @param {String} name model name
3142 * @param {Schema} schema
3143 * @param {String} collectionName
3144 * @param {Connection} connection
3145 * @param {Mongoose} base mongoose instance
3146 */
3147
3148Model.compile = function compile(name, schema, collectionName, connection, base) {
3149 var versioningEnabled = schema.options.versionKey !== false;
3150
3151 if (versioningEnabled && !schema.paths[schema.options.versionKey]) {
3152 // add versioning to top level documents only
3153 var o = {};
3154 o[schema.options.versionKey] = Number;
3155 schema.add(o);
3156 }
3157
3158 // generate new class
3159 function model(doc, fields, skipId) {
3160 if (!(this instanceof model)) {
3161 return new model(doc, fields, skipId);
3162 }
3163 Model.call(this, doc, fields, skipId);
3164 }
3165
3166 model.hooks = schema.s.hooks.clone();
3167 model.base = base;
3168 model.modelName = name;
3169 model.__proto__ = Model;
3170 model.prototype.__proto__ = Model.prototype;
3171 model.model = Model.prototype.model;
3172 model.db = model.prototype.db = connection;
3173 model.discriminators = model.prototype.discriminators = undefined;
3174
3175 model.prototype.$__setSchema(schema);
3176
3177 var collectionOptions = {
3178 bufferCommands: schema.options.bufferCommands,
3179 capped: schema.options.capped
3180 };
3181
3182 model.prototype.collection = connection.collection(
3183 collectionName
3184 , collectionOptions
3185 );
3186
3187 // apply methods and statics
3188 applyMethods(model, schema);
3189 applyStatics(model, schema);
3190
3191 model.schema = model.prototype.schema;
3192 model.collection = model.prototype.collection;
3193
3194 // Create custom query constructor
3195 model.Query = function() {
3196 Query.apply(this, arguments);
3197 };
3198 model.Query.prototype = Object.create(Query.prototype);
3199 model.Query.base = Query.base;
3200 applyQueryMethods(model, schema.query);
3201
3202 var kareemOptions = { useErrorHandlers: true };
3203 model.$__insertMany = model.hooks.createWrapper('insertMany',
3204 model.insertMany, model, kareemOptions);
3205 model.insertMany = function(arr, callback) {
3206 var Promise = PromiseProvider.get();
3207 return new Promise.ES6(function(resolve, reject) {
3208 model.$__insertMany(arr, function(error, result) {
3209 if (error) {
3210 callback && callback(error);
3211 return reject(error);
3212 }
3213 callback && callback(null, result);
3214 resolve(result);
3215 });
3216 });
3217 };
3218
3219 return model;
3220};
3221
3222/*!
3223 * Register methods for this model
3224 *
3225 * @param {Model} model
3226 * @param {Schema} schema
3227 */
3228var applyMethods = function(model, schema) {
3229 function apply(method, schema) {
3230 Object.defineProperty(model.prototype, method, {
3231 get: function() {
3232 var h = {};
3233 for (var k in schema.methods[method]) {
3234 h[k] = schema.methods[method][k].bind(this);
3235 }
3236 return h;
3237 },
3238 configurable: true
3239 });
3240 }
3241 for (var method in schema.methods) {
3242 if (typeof schema.methods[method] === 'function') {
3243 model.prototype[method] = schema.methods[method];
3244 } else {
3245 apply(method, schema);
3246 }
3247 }
3248};
3249
3250/*!
3251 * Register statics for this model
3252 * @param {Model} model
3253 * @param {Schema} schema
3254 */
3255var applyStatics = function(model, schema) {
3256 for (var i in schema.statics) {
3257 model[i] = schema.statics[i];
3258 }
3259};
3260
3261/*!
3262 * Register custom query methods for this model
3263 *
3264 * @param {Model} model
3265 * @param {Schema} schema
3266 */
3267
3268function applyQueryMethods(model, methods) {
3269 for (var i in methods) {
3270 model.Query.prototype[i] = methods[i];
3271 }
3272}
3273
3274/*!
3275 * Subclass this model with `conn`, `schema`, and `collection` settings.
3276 *
3277 * @param {Connection} conn
3278 * @param {Schema} [schema]
3279 * @param {String} [collection]
3280 * @return {Model}
3281 */
3282
3283Model.__subclass = function subclass(conn, schema, collection) {
3284 // subclass model using this connection and collection name
3285 var _this = this;
3286
3287 var Model = function Model(doc, fields, skipId) {
3288 if (!(this instanceof Model)) {
3289 return new Model(doc, fields, skipId);
3290 }
3291 _this.call(this, doc, fields, skipId);
3292 };
3293
3294 Model.__proto__ = _this;
3295 Model.prototype.__proto__ = _this.prototype;
3296 Model.db = Model.prototype.db = conn;
3297
3298 var s = schema && typeof schema !== 'string'
3299 ? schema
3300 : _this.prototype.schema;
3301
3302 var options = s.options || {};
3303
3304 if (!collection) {
3305 collection = _this.prototype.schema.get('collection')
3306 || utils.toCollectionName(_this.modelName, options);
3307 }
3308
3309 var collectionOptions = {
3310 bufferCommands: s ? options.bufferCommands : true,
3311 capped: s && options.capped
3312 };
3313
3314 Model.prototype.collection = conn.collection(collection, collectionOptions);
3315 Model.collection = Model.prototype.collection;
3316 Model.init();
3317 return Model;
3318};
3319
3320Model.$wrapCallback = function(callback) {
3321 var _this = this;
3322 return function() {
3323 try {
3324 callback.apply(null, arguments);
3325 } catch (error) {
3326 _this.emit('error', error);
3327 }
3328 };
3329};
3330
3331/*!
3332 * Module exports.
3333 */
3334
3335module.exports = exports = Model;