thoughts/node_modules/mongoose/lib/model.js
node_modules/mongoose/lib/model.jsRaw
1/*!
2 * Module dependencies.
3 */
4
5var Document = require('./document')
6 , MongooseArray = require('./types/array')
7 , MongooseBuffer = require('./types/buffer')
8 , MongooseError = require('./error')
9 , VersionError = MongooseError.VersionError
10 , DivergentArrayError = MongooseError.DivergentArrayError
11 , Query = require('./query')
12 , Schema = require('./schema')
13 , Types = require('./schema/index')
14 , utils = require('./utils')
15 , hasOwnProperty = utils.object.hasOwnProperty
16 , isMongooseObject = utils.isMongooseObject
17 , EventEmitter = require('events').EventEmitter
18 , merge = utils.merge
19 , Promise = require('./promise')
20 , assert = require('assert')
21 , tick = utils.tick
22
23var VERSION_WHERE = 1
24 , VERSION_INC = 2
25 , VERSION_ALL = VERSION_WHERE | VERSION_INC;
26
27/**
28 * Model constructor
29 *
30 * Provides the interface to MongoDB collections as well as creates document instances.
31 *
32 * @param {Object} doc values with which to create the document
33 * @inherits Document
34 * @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.
35 * @event `index`: Emitted after `Model#ensureIndexes` completes. If an error occurred it is passed with the event.
36 * @api public
37 */
38
39function Model (doc, fields, skipId) {
40 Document.call(this, doc, fields, skipId);
41};
42
43/*!
44 * Inherits from Document.
45 *
46 * All Model.prototype features are available on
47 * top level (non-sub) documents.
48 */
49
50Model.prototype.__proto__ = Document.prototype;
51
52/**
53 * Connection the model uses.
54 *
55 * @api public
56 * @property db
57 */
58
59Model.prototype.db;
60
61/**
62 * Collection the model uses.
63 *
64 * @api public
65 * @property collection
66 */
67
68Model.prototype.collection;
69
70/**
71 * The name of the model
72 *
73 * @api public
74 * @property modelName
75 */
76
77Model.prototype.modelName;
78
79/*!
80 * Handles doc.save() callbacks
81 */
82
83function handleSave (promise, self) {
84 return tick(function handleSave (err, result) {
85 if (err) {
86 // If the initial insert fails provide a second chance.
87 // (If we did this all the time we would break updates)
88 if (self.$__.inserting) {
89 self.isNew = true;
90 self.emit('isNew', true);
91 }
92 promise.error(err);
93 promise = self = null;
94 return;
95 }
96
97 self.$__storeShard();
98
99 var numAffected;
100 if (result) {
101 // when inserting, the array of created docs is returned
102 numAffected = result.length
103 ? result.length
104 : result;
105 } else {
106 numAffected = 0;
107 }
108
109 // was this an update that required a version bump?
110 if (self.$__.version && !self.$__.inserting) {
111 var doIncrement = VERSION_INC === (VERSION_INC & self.$__.version);
112 self.$__.version = undefined;
113
114 // increment version if was successful
115 if (numAffected > 0) {
116 if (doIncrement) {
117 var key = self.schema.options.versionKey;
118 var version = self.getValue(key) | 0;
119 self.setValue(key, version + 1);
120 }
121 } else {
122 // the update failed. pass an error back
123 promise.error(new VersionError);
124 promise = self = null;
125 return;
126 }
127 }
128
129 self.emit('save', self, numAffected);
130 promise.complete(self, numAffected);
131 promise = self = null;
132 });
133}
134
135/**
136 * Saves this document.
137 *
138 * ####Example:
139 *
140 * product.sold = Date.now();
141 * product.save(function (err, product, numberAffected) {
142 * if (err) ..
143 * })
144 *
145 * The callback will receive three parameters, `err` if an error occurred, `product` which is the saved `product`, and `numberAffected` which will be 1 when the document was found and updated in the database, otherwise 0.
146 *
147 * The `fn` callback is optional. If no `fn` is passed and validation fails, the validation error will be emitted on the connection used to create this model.
148 *
149 * var db = mongoose.createConnection(..);
150 * var schema = new Schema(..);
151 * var Product = db.model('Product', schema);
152 *
153 * db.on('error', handleError);
154 *
155 * However, if you desire more local error handling you can add an `error` listener to the model and handle errors there instead.
156 *
157 * Product.on('error', handleError);
158 *
159 * @param {Function} [fn] optional callback
160 * @api public
161 * @see middleware http://mongoosejs.com/docs/middleware.html
162 */
163
164Model.prototype.save = function save (fn) {
165 var promise = new Promise(fn)
166 , complete = handleSave(promise, this)
167 , options = {}
168
169 if (this.schema.options.safe) {
170 options.safe = this.schema.options.safe;
171 }
172
173 if (this.isNew) {
174 // send entire doc
175 var obj = this.toObject({ depopulate: 1 });
176 this.$__version(true, obj);
177 this.collection.insert(obj, options, complete);
178 this.$__reset();
179 this.isNew = false;
180 this.emit('isNew', false);
181 // Make it possible to retry the insert
182 this.$__.inserting = true;
183
184 } else {
185 // Make sure we don't treat it as a new object on error,
186 // since it already exists
187 this.$__.inserting = false;
188
189 var delta = this.$__delta();
190 if (delta) {
191 if (delta instanceof Error) return complete(delta);
192 var where = this.$__where(delta[0]);
193 this.$__reset();
194 this.collection.update(where, delta[1], options, complete);
195 } else {
196 this.$__reset();
197 complete(null);
198 }
199
200 this.emit('isNew', false);
201 }
202};
203
204/*!
205 * Apply the operation to the delta (update) clause as
206 * well as track versioning for our where clause.
207 *
208 * @param {Document} self
209 * @param {Object} where
210 * @param {Object} delta
211 * @param {Object} data
212 * @param {Mixed} val
213 * @param {String} [operation]
214 */
215
216function operand (self, where, delta, data, val, op) {
217 // delta
218 op || (op = '$set');
219 if (!delta[op]) delta[op] = {};
220 delta[op][data.path] = val;
221
222 // disabled versioning?
223 if (false === self.schema.options.versionKey) return;
224
225 // already marked for versioning?
226 if (VERSION_ALL === (VERSION_ALL & self.$__.version)) return;
227
228 switch (op) {
229 case '$set':
230 case '$unset':
231 case '$pop':
232 case '$pull':
233 case '$pullAll':
234 case '$push':
235 case '$pushAll':
236 case '$addToSet':
237 break;
238 default:
239 // nothing to do
240 return;
241 }
242
243 // ensure updates sent with positional notation are
244 // editing the correct array element.
245 // only increment the version if an array position changes.
246 // modifying elements of an array is ok if position does not change.
247
248 if ('$push' == op || '$pushAll' == op || '$addToSet' == op) {
249 self.$__.version = VERSION_INC;
250 }
251 else if (/^\$p/.test(op)) {
252 // potentially changing array positions
253 self.increment();
254 }
255 else if (Array.isArray(val)) {
256 // $set an array
257 self.increment();
258 }
259 // now handling $set, $unset
260 else if (/\.\d+\.|\.\d+$/.test(data.path)) {
261 // subpath of array
262 self.$__.version = VERSION_WHERE;
263 }
264}
265
266/*!
267 * Compiles an update and where clause for a `val` with _atomics.
268 *
269 * @param {Document} self
270 * @param {Object} where
271 * @param {Object} delta
272 * @param {Object} data
273 * @param {Array} value
274 */
275
276function handleAtomics (self, where, delta, data, value) {
277 if (delta.$set && delta.$set[data.path]) {
278 // $set has precedence over other atomics
279 return;
280 }
281
282 if ('function' == typeof value.$__getAtomics) {
283 value.$__getAtomics().forEach(function (atomic) {
284 var op = atomic[0];
285 var val = atomic[1];
286 operand(self, where, delta, data, val, op);
287 })
288 return;
289 }
290
291 // legacy support for plugins
292
293 var atomics = value._atomics
294 , ops = Object.keys(atomics)
295 , i = ops.length
296 , val
297 , op;
298
299 if (0 === i) {
300 // $set
301
302 if (isMongooseObject(value)) {
303 value = value.toObject({ depopulate: 1 });
304 } else if (value.valueOf) {
305 value = value.valueOf();
306 }
307
308 return operand(self, where, delta, data, value);
309 }
310
311 while (i--) {
312 op = ops[i];
313 val = atomics[op];
314
315 if (isMongooseObject(val)) {
316 val = val.toObject({ depopulate: 1 })
317 } else if (Array.isArray(val)) {
318 val = val.map(function (mem) {
319 return isMongooseObject(mem)
320 ? mem.toObject({ depopulate: 1 })
321 : mem;
322 })
323 } else if (val.valueOf) {
324 val = val.valueOf()
325 }
326
327 if ('$addToSet' === op)
328 val = { $each: val };
329
330 operand(self, where, delta, data, val, op);
331 }
332}
333
334/**
335 * Produces a special query document of the modified properties used in updates.
336 *
337 * @api private
338 * @method $__delta
339 * @memberOf Model
340 */
341
342Model.prototype.$__delta = function () {
343 var dirty = this.$__dirty();
344 if (!dirty.length && VERSION_ALL != this.$__.version) return;
345
346 var where = {}
347 , delta = {}
348 , len = dirty.length
349 , divergent = []
350 , d = 0
351 , val
352 , obj
353
354 for (; d < len; ++d) {
355 var data = dirty[d]
356 var value = data.value
357 var schema = data.schema
358
359 var match = checkDivergentArray(this, data.path, value);
360 if (match) {
361 divergent.push(match);
362 continue;
363 }
364
365 if (divergent.length) continue;
366
367 if (undefined === value) {
368 operand(this, where, delta, data, 1, '$unset');
369
370 } else if (null === value) {
371 operand(this, where, delta, data, null);
372
373 } else if (value._path && value._atomics) {
374 // arrays and other custom types (support plugins etc)
375 handleAtomics(this, where, delta, data, value);
376
377 } else if (value._path && Buffer.isBuffer(value)) {
378 // MongooseBuffer
379 value = value.toObject();
380 operand(this, where, delta, data, value);
381
382 } else {
383 value = utils.clone(value, { depopulate: 1 });
384 operand(this, where, delta, data, value);
385 }
386 }
387
388 if (divergent.length) {
389 return new DivergentArrayError(divergent);
390 }
391
392 if (this.$__.version) {
393 this.$__version(where, delta);
394 }
395
396 return [where, delta];
397}
398
399/*!
400 * Determine if array was populated with some form of filter and is now
401 * being updated in a manner which could overwrite data unintentionally.
402 *
403 * @see https://github.com/LearnBoost/mongoose/issues/1334
404 * @param {Document} doc
405 * @param {String} path
406 * @return {String|undefined}
407 */
408
409function checkDivergentArray (doc, path, array) {
410 // see if we populated this path
411 var pop = doc.populated(path, true);
412
413 if (!pop && doc.$__.selected) {
414 // If any array was selected using an $elemMatch projection, we deny the update.
415 // NOTE: MongoDB only supports projected $elemMatch on top level array.
416 var top = path.split('.')[0];
417 if (doc.$__.selected[top] && doc.$__.selected[top].$elemMatch) {
418 return top;
419 }
420 }
421
422 if (!(pop && array instanceof MongooseArray)) return;
423
424 // If the array was populated using options that prevented all
425 // documents from being returned (match, skip, limit) or they
426 // deselected the _id field, $pop and $set of the array are
427 // not safe operations. If _id was deselected, we do not know
428 // how to remove elements. $pop will pop off the _id from the end
429 // of the array in the db which is not guaranteed to be the
430 // same as the last element we have here. $set of the entire array
431 // would be similarily destructive as we never received all
432 // elements of the array and potentially would overwrite data.
433 var check = pop.options.match ||
434 pop.options.options && hasOwnProperty(pop.options.options, 'limit') || // 0 is not permitted
435 pop.options.options && pop.options.options.skip || // 0 is permitted
436 pop.options.select && // deselected _id?
437 (0 === pop.options.select._id ||
438 /\s?-_id\s?/.test(pop.options.select))
439
440 if (check) {
441 var atomics = array._atomics;
442 if (0 === Object.keys(atomics).length || atomics.$set || atomics.$pop) {
443 return path;
444 }
445 }
446}
447
448/**
449 * Appends versioning to the where and update clauses.
450 *
451 * @api private
452 * @method $__version
453 * @memberOf Model
454 */
455
456Model.prototype.$__version = function (where, delta) {
457 var key = this.schema.options.versionKey;
458
459 if (true === where) {
460 // this is an insert
461 if (key) this.setValue(key, delta[key] = 0);
462 return;
463 }
464
465 // updates
466
467 // only apply versioning if our versionKey was selected. else
468 // there is no way to select the correct version. we could fail
469 // fast here and force them to include the versionKey but
470 // thats a bit intrusive. can we do this automatically?
471 if (!this.isSelected(key)) {
472 return;
473 }
474
475 // $push $addToSet don't need the where clause set
476 if (VERSION_WHERE === (VERSION_WHERE & this.$__.version)) {
477 where[key] = this.getValue(key);
478 }
479
480 if (VERSION_INC === (VERSION_INC & this.$__.version)) {
481 delta.$inc || (delta.$inc = {});
482 delta.$inc[key] = 1;
483 }
484}
485
486/**
487 * Signal that we desire an increment of this documents version.
488 *
489 * ####Example:
490 *
491 * Model.findById(id, function (err, doc) {
492 * doc.increment();
493 * doc.save(function (err) { .. })
494 * })
495 *
496 * @see versionKeys http://mongoosejs.com/docs/guide.html#versionKey
497 * @api public
498 */
499
500Model.prototype.increment = function increment () {
501 this.$__.version = VERSION_ALL;
502 return this;
503}
504
505/**
506 * Returns a query object which applies shardkeys if they exist.
507 *
508 * @api private
509 * @method $__where
510 * @memberOf Model
511 */
512
513Model.prototype.$__where = function _where (where) {
514 where || (where = {});
515
516 var paths
517 , len
518
519 if (this.$__.shardval) {
520 paths = Object.keys(this.$__.shardval)
521 len = paths.length
522
523 for (var i = 0; i < len; ++i) {
524 where[paths[i]] = this.$__.shardval[paths[i]];
525 }
526 }
527
528 where._id = this._doc._id;
529 return where;
530}
531
532/**
533 * Removes this document from the db.
534 *
535 * ####Example:
536 *
537 * product.remove(function (err) {
538 * if (err) return handleError(err);
539 * Product.findById(product._id, function (err, product) {
540 * console.log(product) // null
541 * })
542 * })
543 *
544 * @param {Function} [fn] optional callback
545 * @api public
546 */
547
548Model.prototype.remove = function remove (fn) {
549 if (this.$__.removing) {
550 this.$__.removing.addBack(fn);
551 return this;
552 }
553
554 var promise = this.$__.removing = new Promise(fn)
555 , where = this.$__where()
556 , self = this
557 , options = {}
558
559 if (this.schema.options.safe) {
560 options.safe = this.schema.options.safe;
561 }
562
563 this.collection.remove(where, options, tick(function (err) {
564 if (err) {
565 promise.error(err);
566 promise = self = self.$__.removing = where = options = null;
567 return;
568 }
569 self.emit('remove', self);
570 promise.complete();
571 promise = self = where = options = null;
572 }));
573
574 return this;
575};
576
577/**
578 * Returns another Model instance.
579 *
580 * ####Example:
581 *
582 * var doc = new Tank;
583 * doc.model('User').findById(id, callback);
584 *
585 * @param {String} name model name
586 * @api public
587 */
588
589Model.prototype.model = function model (name) {
590 return this.db.model(name);
591};
592
593// Model (class) features
594
595/*!
596 * Give the constructor the ability to emit events.
597 */
598
599for (var i in EventEmitter.prototype)
600 Model[i] = EventEmitter.prototype[i];
601
602/**
603 * Called when the model compiles.
604 *
605 * @api private
606 */
607
608Model.init = function init () {
609 if (this.schema.options.autoIndex) {
610 this.ensureIndexes();
611 }
612
613 this.schema.emit('init', this);
614};
615
616/**
617 * Sends `ensureIndex` commands to mongo for each index declared in the schema.
618 *
619 * ####Example:
620 *
621 * Event.ensureIndexes(function (err) {
622 * if (err) return handleError(err);
623 * });
624 *
625 * After completion, an `index` event is emitted on this `Model` passing an error if one occurred.
626 *
627 * ####Example:
628 *
629 * var eventSchema = new Schema({ thing: { type: 'string', unique: true }})
630 * var Event = mongoose.model('Event', eventSchema);
631 *
632 * Event.on('index', function (err) {
633 * if (err) console.error(err); // error occurred during index creation
634 * })
635 *
636 * _NOTE: It is not recommended that you run this in production. Index creation may impact database performance depending on your load. Use with caution._
637 *
638 * 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/LearnBoost/mongoose/issues/1365) for more information.
639 *
640 * @param {Function} [cb] optional callback
641 * @api public
642 */
643
644Model.ensureIndexes = function ensureIndexes (cb) {
645 var indexes = this.schema.indexes();
646 if (!indexes.length) {
647 return cb && process.nextTick(cb);
648 }
649
650 // Indexes are created one-by-one to support how MongoDB < 2.4 deals
651 // with background indexes.
652
653 var self = this
654 , safe = self.schema.options.safe
655
656 function done (err) {
657 self.emit('index', err);
658 cb && cb(err);
659 }
660
661 function create () {
662 var index = indexes.shift();
663 if (!index) return done();
664
665 var options = index[1];
666 options.safe = safe;
667 self.collection.ensureIndex(index[0], options, tick(function (err) {
668 if (err) return done(err);
669 create();
670 }));
671 }
672
673 create();
674}
675
676/**
677 * Schema the model uses.
678 *
679 * @property schema
680 * @receiver Model
681 * @api public
682 */
683
684Model.schema;
685
686/*!
687 * Connection instance the model uses.
688 *
689 * @property db
690 * @receiver Model
691 * @api public
692 */
693
694Model.db;
695
696/*!
697 * Collection the model uses.
698 *
699 * @property collection
700 * @receiver Model
701 * @api public
702 */
703
704Model.collection;
705
706/**
707 * Base Mongoose instance the model uses.
708 *
709 * @property base
710 * @receiver Model
711 * @api public
712 */
713
714Model.base;
715
716/**
717 * Removes documents from the collection.
718 *
719 * ####Example:
720 *
721 * Comment.remove({ title: 'baby born from alien father' }, function (err) {
722 *
723 * });
724 *
725 * ####Note:
726 *
727 * To remove documents without waiting for a response from MongoDB, do not pass a `callback`, then call `exec` on the returned [Query](#query-js):
728 *
729 * var query = Comment.remove({ _id: id });
730 * query.exec();
731 *
732 * ####Note:
733 *
734 * 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_.
735 *
736 * @param {Object} conditions
737 * @param {Function} [callback]
738 * @return {Query}
739 * @api public
740 */
741
742Model.remove = function remove (conditions, callback) {
743 if ('function' === typeof conditions) {
744 callback = conditions;
745 conditions = {};
746 }
747
748 var query = new Query(conditions).bind(this, 'remove');
749
750 if ('undefined' === typeof callback)
751 return query;
752
753 this._applyNamedScope(query);
754 return query.remove(callback);
755};
756
757/**
758 * Finds documents
759 *
760 * The `conditions` are cast to their respective SchemaTypes before the command is sent.
761 *
762 * ####Examples:
763 *
764 * // named john and at least 18
765 * MyModel.find({ name: 'john', age: { $gte: 18 }});
766 *
767 * // executes immediately, passing results to callback
768 * MyModel.find({ name: 'john', age: { $gte: 18 }}, function (err, docs) {});
769 *
770 * // name LIKE john and only selecting the "name" and "friends" fields, executing immediately
771 * MyModel.find({ name: /john/i }, 'name friends', function (err, docs) { })
772 *
773 * // passing options
774 * MyModel.find({ name: /john/i }, null, { skip: 10 })
775 *
776 * // passing options and executing immediately
777 * MyModel.find({ name: /john/i }, null, { skip: 10 }, function (err, docs) {});
778 *
779 * // executing a query explicitly
780 * var query = MyModel.find({ name: /john/i }, null, { skip: 10 })
781 * query.exec(function (err, docs) {});
782 *
783 * // using the promise returned from executing a query
784 * var query = MyModel.find({ name: /john/i }, null, { skip: 10 });
785 * var promise = query.exec();
786 * promise.addBack(function (err, docs) {});
787 *
788 * @param {Object} conditions
789 * @param {Object} [fields] optional fields to select
790 * @param {Object} [options] optional
791 * @param {Function} [callback]
792 * @return {Query}
793 * @see field selection #query_Query-select
794 * @see promise #promise-js
795 * @api public
796 */
797
798Model.find = function find (conditions, fields, options, callback) {
799 if ('function' == typeof conditions) {
800 callback = conditions;
801 conditions = {};
802 fields = null;
803 options = null;
804 } else if ('function' == typeof fields) {
805 callback = fields;
806 fields = null;
807 options = null;
808 } else if ('function' == typeof options) {
809 callback = options;
810 options = null;
811 }
812
813 var query = new Query(conditions, options);
814 query.bind(this, 'find');
815 query.select(fields);
816
817 if ('undefined' === typeof callback)
818 return query;
819
820 this._applyNamedScope(query);
821 return query.find(callback);
822};
823
824/**
825 * Merges the current named scope query into `query`.
826 *
827 * @param {Query} query
828 * @return {Query}
829 * @api private
830 */
831
832Model._applyNamedScope = function _applyNamedScope (query) {
833 var cQuery = this._cumulativeQuery;
834
835 if (cQuery) {
836 merge(query._conditions, cQuery._conditions);
837 if (query._fields && cQuery._fields)
838 merge(query._fields, cQuery._fields);
839 if (query.options && cQuery.options)
840 merge(query.options, cQuery.options);
841 delete this._cumulativeQuery;
842 }
843
844 return query;
845}
846
847/**
848 * Finds a single document by id.
849 *
850 * The `id` is cast based on the Schema before sending the command.
851 *
852 * ####Example:
853 *
854 * // find adventure by id and execute immediately
855 * Adventure.findById(id, function (err, adventure) {});
856 *
857 * // same as above
858 * Adventure.findById(id).exec(callback);
859 *
860 * // select only the adventures name and length
861 * Adventure.findById(id, 'name length', function (err, adventure) {});
862 *
863 * // same as above
864 * Adventure.findById(id, 'name length').exec(callback);
865 *
866 * // include all properties except for `length`
867 * Adventure.findById(id, '-length').exec(function (err, adventure) {});
868 *
869 * // passing options (in this case return the raw js objects, not mongoose documents by passing `lean`
870 * Adventure.findById(id, 'name', { lean: true }, function (err, doc) {});
871 *
872 * // same as above
873 * Adventure.findById(id, 'name').lean().exec(function (err, doc) {});
874 *
875 * @param {ObjectId|HexId} id objectid, or a value that can be casted to one
876 * @param {Object} [fields] optional fields to select
877 * @param {Object} [options] optional
878 * @param {Function} [callback]
879 * @return {Query}
880 * @see field selection #query_Query-select
881 * @see lean queries #query_Query-lean
882 * @api public
883 */
884
885Model.findById = function findById (id, fields, options, callback) {
886 return this.findOne({ _id: id }, fields, options, callback);
887};
888
889/**
890 * Finds one document.
891 *
892 * The `conditions` are cast to their respective SchemaTypes before the command is sent.
893 *
894 * ####Example:
895 *
896 * // find one iphone adventures - iphone adventures??
897 * Adventure.findOne({ type: 'iphone' }, function (err, adventure) {});
898 *
899 * // same as above
900 * Adventure.findOne({ type: 'iphone' }).exec(function (err, adventure) {});
901 *
902 * // select only the adventures name
903 * Adventure.findOne({ type: 'iphone' }, 'name', function (err, adventure) {});
904 *
905 * // same as above
906 * Adventure.findOne({ type: 'iphone' }, 'name').exec(function (err, adventure) {});
907 *
908 * // specify options, in this case lean
909 * Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }, callback);
910 *
911 * // same as above
912 * Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }).exec(callback);
913 *
914 * // chaining findOne queries (same as above)
915 * Adventure.findOne({ type: 'iphone' }).select('name').lean().exec(callback);
916 *
917 * @param {Object} conditions
918 * @param {Object} [fields] optional fields to select
919 * @param {Object} [options] optional
920 * @param {Function} [callback]
921 * @return {Query}
922 * @see field selection #query_Query-select
923 * @see lean queries #query_Query-lean
924 * @api public
925 */
926
927Model.findOne = function findOne (conditions, fields, options, callback) {
928 if ('function' == typeof options) {
929 callback = options;
930 options = null;
931 } else if ('function' == typeof fields) {
932 callback = fields;
933 fields = null;
934 options = null;
935 } else if ('function' == typeof conditions) {
936 callback = conditions;
937 conditions = {};
938 fields = null;
939 options = null;
940 }
941
942 var query = new Query(conditions, options).select(fields).bind(this, 'findOne');
943
944 if ('undefined' == typeof callback)
945 return query;
946
947 this._applyNamedScope(query);
948 return query.findOne(callback);
949};
950
951/**
952 * Counts number of matching documents in a database collection.
953 *
954 * ####Example:
955 *
956 * Adventure.count({ type: 'jungle' }, function (err, count) {
957 * if (err) ..
958 * console.log('there are %d jungle adventures', count);
959 * });
960 *
961 * @param {Object} conditions
962 * @param {Function} [callback]
963 * @return {Query}
964 * @api public
965 */
966
967Model.count = function count (conditions, callback) {
968 if ('function' === typeof conditions)
969 callback = conditions, conditions = {};
970
971 var query = new Query(conditions).bind(this, 'count');
972 if ('undefined' == typeof callback)
973 return query;
974
975 this._applyNamedScope(query);
976 return query.count(callback);
977};
978
979/**
980 * Executes a DISTINCT command
981 *
982 * ####Example
983 *
984 * Link.distinct('url', { clicks: {$gt: 100}}, function (err, result) {
985 * if (err) return handleError(err);
986 *
987 * assert(Array.isArray(result));
988 * console.log('unique urls with more than 100 clicks', result);
989 * })
990 *
991 * @param {String} field
992 * @param {Object} [conditions] optional
993 * @param {Function} [callback]
994 * @return {Query}
995 * @api public
996 */
997
998Model.distinct = function distinct (field, conditions, callback) {
999 if ('function' == typeof conditions) {
1000 callback = conditions;
1001 conditions = {};
1002 }
1003
1004 var query = new Query(conditions).bind(this, 'distinct');
1005 if ('undefined' == typeof callback) {
1006 query._distinctArg = field;
1007 return query;
1008 }
1009
1010 this._applyNamedScope(query);
1011 return query.distinct(field, callback);
1012};
1013
1014/**
1015 * Creates a Query, applies the passed conditions, and returns the Query.
1016 *
1017 * For example, instead of writing:
1018 *
1019 * User.find({age: {$gte: 21, $lte: 65}}, callback);
1020 *
1021 * we can instead write:
1022 *
1023 * User.where('age').gte(21).lte(65).exec(callback);
1024 *
1025 * Since the Query class also supports `where` you can continue chaining
1026 *
1027 * User
1028 * .where('age').gte(21).lte(65)
1029 * .where('name', /^b/i)
1030 * ... etc
1031 *
1032 * @param {String} path
1033 * @param {Object} [val] optional value
1034 * @return {Query}
1035 * @api public
1036 */
1037
1038Model.where = function where (path, val) {
1039 var q = new Query().bind(this, 'find');
1040 return q.where.apply(q, arguments);
1041};
1042
1043/**
1044 * Creates a `Query` and specifies a `$where` condition.
1045 *
1046 * 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.
1047 *
1048 * Blog.$where('this.comments.length > 5').exec(function (err, docs) {});
1049 *
1050 * @param {String|Function} argument is a javascript string or anonymous function
1051 * @method $where
1052 * @memberOf Model
1053 * @return {Query}
1054 * @see Query.$where #query_Query-%24where
1055 * @api public
1056 */
1057
1058Model.$where = function $where () {
1059 var q = new Query().bind(this, 'find');
1060 return q.$where.apply(q, arguments);
1061};
1062
1063/**
1064 * Issues a mongodb findAndModify update command.
1065 *
1066 * 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.
1067 *
1068 * ####Options:
1069 *
1070 * - `new`: bool - true to return the modified document rather than the original. defaults to true
1071 * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
1072 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1073 * - `select`: sets the document fields to return
1074 *
1075 * ####Examples:
1076 *
1077 * A.findOneAndUpdate(conditions, update, options, callback) // executes
1078 * A.findOneAndUpdate(conditions, update, options) // returns Query
1079 * A.findOneAndUpdate(conditions, update, callback) // executes
1080 * A.findOneAndUpdate(conditions, update) // returns Query
1081 * A.findOneAndUpdate() // returns Query
1082 *
1083 * ####Note:
1084 *
1085 * All top level update keys which are not `atomic` operation names are treated as set operations:
1086 *
1087 * ####Example:
1088 *
1089 * var query = { name: 'borne' };
1090 * Model.findOneAndUpdate(query, { name: 'jason borne' }, options, callback)
1091 *
1092 * // is sent as
1093 * Model.findOneAndUpdate(query, { $set: { name: 'jason borne' }}, options, callback)
1094 *
1095 * This helps prevent accidentally overwriting your document with `{ name: 'jason borne' }`.
1096 *
1097 * ####Note:
1098 *
1099 * Although values are cast to their appropriate types when using the findAndModify helpers, the following are *not* applied:
1100 *
1101 * - defaults
1102 * - setters
1103 * - validators
1104 * - middleware
1105 *
1106 * If you need those features, use the traditional approach of first retrieving the document.
1107 *
1108 * Model.findOne({ name: 'borne' }, function (err, doc) {
1109 * if (err) ..
1110 * doc.name = 'jason borne';
1111 * doc.save(callback);
1112 * })
1113 *
1114 * @param {Object} [conditions]
1115 * @param {Object} [update]
1116 * @param {Object} [options]
1117 * @param {Function} [callback]
1118 * @return {Query}
1119 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1120 * @api public
1121 */
1122
1123Model.findOneAndUpdate = function (conditions, update, options, callback) {
1124 if ('function' == typeof options) {
1125 callback = options;
1126 options = null;
1127 }
1128 else if (1 === arguments.length) {
1129 if ('function' == typeof conditions) {
1130 var msg = 'Model.findOneAndUpdate(): First argument must not be a function.\n\n'
1131 + ' ' + this.modelName + '.findOneAndUpdate(conditions, update, options, callback)\n'
1132 + ' ' + this.modelName + '.findOneAndUpdate(conditions, update, options)\n'
1133 + ' ' + this.modelName + '.findOneAndUpdate(conditions, update)\n'
1134 + ' ' + this.modelName + '.findOneAndUpdate(update)\n'
1135 + ' ' + this.modelName + '.findOneAndUpdate()\n';
1136 throw new TypeError(msg)
1137 }
1138 update = conditions;
1139 conditions = undefined;
1140 }
1141
1142 var fields;
1143 if (options && options.fields) {
1144 fields = options.fields;
1145 options.fields = undefined;
1146 }
1147
1148 var query = new Query(conditions);
1149 query.setOptions(options);
1150 query.select(fields);
1151 query.bind(this, 'findOneAndUpdate', update);
1152
1153 if ('undefined' == typeof callback)
1154 return query;
1155
1156 this._applyNamedScope(query);
1157 return query.findOneAndUpdate(callback);
1158}
1159
1160/**
1161 * Issues a mongodb findAndModify update command by a documents id.
1162 *
1163 * 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.
1164 *
1165 * ####Options:
1166 *
1167 * - `new`: bool - true to return the modified document rather than the original. defaults to true
1168 * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
1169 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1170 * - `select`: sets the document fields to return
1171 *
1172 * ####Examples:
1173 *
1174 * A.findByIdAndUpdate(id, update, options, callback) // executes
1175 * A.findByIdAndUpdate(id, update, options) // returns Query
1176 * A.findByIdAndUpdate(id, update, callback) // executes
1177 * A.findByIdAndUpdate(id, update) // returns Query
1178 * A.findByIdAndUpdate() // returns Query
1179 *
1180 * 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.
1181 *
1182 * ####Options:
1183 *
1184 * - `new`: bool - true to return the modified document rather than the original. defaults to true
1185 * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
1186 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1187 *
1188 * ####Note:
1189 *
1190 * All top level update keys which are not `atomic` operation names are treated as set operations:
1191 *
1192 * ####Example:
1193 *
1194 * Model.findByIdAndUpdate(id, { name: 'jason borne' }, options, callback)
1195 *
1196 * // is sent as
1197 * Model.findByIdAndUpdate(id, { $set: { name: 'jason borne' }}, options, callback)
1198 *
1199 * This helps prevent accidentally overwriting your document with `{ name: 'jason borne' }`.
1200 *
1201 * ####Note:
1202 *
1203 * Although values are cast to their appropriate types when using the findAndModify helpers, the following are *not* applied:
1204 *
1205 * - defaults
1206 * - setters
1207 * - validators
1208 * - middleware
1209 *
1210 * If you need those features, use the traditional approach of first retrieving the document.
1211 *
1212 * Model.findById(id, function (err, doc) {
1213 * if (err) ..
1214 * doc.name = 'jason borne';
1215 * doc.save(callback);
1216 * })
1217 *
1218 * @param {ObjectId|HexId} id an ObjectId or string that can be cast to one.
1219 * @param {Object} [update]
1220 * @param {Object} [options]
1221 * @param {Function} [callback]
1222 * @return {Query}
1223 * @see Model.findOneAndUpdate #model_Model.findOneAndUpdate
1224 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1225 * @api public
1226 */
1227
1228Model.findByIdAndUpdate = function (id, update, options, callback) {
1229 var args;
1230
1231 if (1 === arguments.length) {
1232 if ('function' == typeof id) {
1233 var msg = 'Model.findByIdAndUpdate(): First argument must not be a function.\n\n'
1234 + ' ' + this.modelName + '.findByIdAndUpdate(id, callback)\n'
1235 + ' ' + this.modelName + '.findByIdAndUpdate(id)\n'
1236 + ' ' + this.modelName + '.findByIdAndUpdate()\n';
1237 throw new TypeError(msg)
1238 }
1239 return this.findOneAndUpdate({_id: id }, undefined);
1240 }
1241
1242 args = utils.args(arguments, 1);
1243 args.unshift({ _id: id });
1244 return this.findOneAndUpdate.apply(this, args);
1245}
1246
1247/**
1248 * Issue a mongodb findAndModify remove command.
1249 *
1250 * Finds a matching document, removes it, passing the found document (if any) to the callback.
1251 *
1252 * Executes immediately if `callback` is passed else a Query object is returned.
1253 *
1254 * ####Options:
1255 *
1256 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1257 * - `select`: sets the document fields to return
1258 *
1259 * ####Examples:
1260 *
1261 * A.findOneAndRemove(conditions, options, callback) // executes
1262 * A.findOneAndRemove(conditions, options) // return Query
1263 * A.findOneAndRemove(conditions, callback) // executes
1264 * A.findOneAndRemove(conditions) // returns Query
1265 * A.findOneAndRemove() // returns Query
1266 *
1267 * Although values are cast to their appropriate types when using the findAndModify helpers, the following are *not* applied:
1268 *
1269 * - defaults
1270 * - setters
1271 * - validators
1272 * - middleware
1273 *
1274 * If you need those features, use the traditional approach of first retrieving the document.
1275 *
1276 * Model.findById(id, function (err, doc) {
1277 * if (err) ..
1278 * doc.remove(callback);
1279 * })
1280 *
1281 * @param {Object} conditions
1282 * @param {Object} [options]
1283 * @param {Function} [callback]
1284 * @return {Query}
1285 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1286 * @api public
1287 */
1288
1289Model.findOneAndRemove = function (conditions, options, callback) {
1290 if (1 === arguments.length && 'function' == typeof conditions) {
1291 var msg = 'Model.findOneAndRemove(): First argument must not be a function.\n\n'
1292 + ' ' + this.modelName + '.findOneAndRemove(conditions, callback)\n'
1293 + ' ' + this.modelName + '.findOneAndRemove(conditions)\n'
1294 + ' ' + this.modelName + '.findOneAndRemove()\n';
1295 throw new TypeError(msg)
1296 }
1297
1298 if ('function' == typeof options) {
1299 callback = options;
1300 options = undefined;
1301 }
1302
1303 var fields;
1304 if (options) {
1305 fields = options.select;
1306 options.select = undefined;
1307 }
1308
1309 var query = new Query(conditions);
1310 query.setOptions(options);
1311 query.select(fields);
1312 query.bind(this, 'findOneAndRemove');
1313
1314 if ('undefined' == typeof callback)
1315 return query;
1316
1317 this._applyNamedScope(query);
1318 return query.findOneAndRemove(callback);
1319}
1320
1321/**
1322 * Issue a mongodb findAndModify remove command by a documents id.
1323 *
1324 * Finds a matching document, removes it, passing the found document (if any) to the callback.
1325 *
1326 * Executes immediately if `callback` is passed, else a `Query` object is returned.
1327 *
1328 * ####Options:
1329 *
1330 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1331 * - `select`: sets the document fields to return
1332 *
1333 * ####Examples:
1334 *
1335 * A.findByIdAndRemove(id, options, callback) // executes
1336 * A.findByIdAndRemove(id, options) // return Query
1337 * A.findByIdAndRemove(id, callback) // executes
1338 * A.findByIdAndRemove(id) // returns Query
1339 * A.findByIdAndRemove() // returns Query
1340 *
1341 * @param {ObjectId|HexString} id ObjectId or string that can be cast to one
1342 * @param {Object} [options]
1343 * @param {Function} [callback]
1344 * @return {Query}
1345 * @see Model.findOneAndRemove #model_Model.findOneAndRemove
1346 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1347 */
1348
1349Model.findByIdAndRemove = function (id, options, callback) {
1350 if (1 === arguments.length && 'function' == typeof id) {
1351 var msg = 'Model.findByIdAndRemove(): First argument must not be a function.\n\n'
1352 + ' ' + this.modelName + '.findByIdAndRemove(id, callback)\n'
1353 + ' ' + this.modelName + '.findByIdAndRemove(id)\n'
1354 + ' ' + this.modelName + '.findByIdAndRemove()\n';
1355 throw new TypeError(msg)
1356 }
1357
1358 return this.findOneAndRemove({ _id: id }, options, callback);
1359}
1360
1361/**
1362 * Shortcut for creating a new Document that is automatically saved to the db if valid.
1363 *
1364 * ####Example:
1365 *
1366 * Candy.create({ type: 'jelly bean' }, { type: 'snickers' }, function (err, jellybean, snickers) {
1367 * if (err) // ...
1368 * });
1369 *
1370 * var array = [{ type: 'jelly bean' }, { type: 'snickers' }];
1371 * Candy.create(array, function (err, jellybean, snickers) {
1372 * if (err) // ...
1373 * });
1374 *
1375 * @param {Array|Object...} doc
1376 * @param {Function} fn callback
1377 * @api public
1378 */
1379
1380Model.create = function create (doc, fn) {
1381 if (1 === arguments.length) {
1382 return 'function' === typeof doc && doc(null);
1383 }
1384
1385 var self = this
1386 , docs = [null]
1387 , promise
1388 , count
1389 , args
1390
1391 if (Array.isArray(doc)) {
1392 args = doc;
1393 } else {
1394 args = utils.args(arguments, 0, arguments.length - 1);
1395 fn = arguments[arguments.length - 1];
1396 }
1397
1398 if (0 === args.length) return fn(null);
1399
1400 promise = new Promise(fn);
1401 count = args.length;
1402
1403 args.forEach(function (arg, i) {
1404 var doc = new self(arg);
1405 docs[i+1] = doc;
1406 doc.save(function (err) {
1407 if (err) return promise.error(err);
1408 --count || fn.apply(null, docs);
1409 });
1410 });
1411};
1412
1413/**
1414 * Updates documents in the database without returning them.
1415 *
1416 * ####Examples:
1417 *
1418 * MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);
1419 * MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, function (err, numberAffected, raw) {
1420 * if (err) return handleError(err);
1421 * console.log('The number of updated documents was %d', numberAffected);
1422 * console.log('The raw response from Mongo was ', raw);
1423 * });
1424 *
1425 * ####Valid options:
1426 *
1427 * - `safe` (boolean) safe mode (defaults to value set in schema (true))
1428 * - `upsert` (boolean) whether to create the doc if it doesn't match (false)
1429 * - `multi` (boolean) whether multiple documents should be updated (false)
1430 * - `strict` (boolean) overrides the `strict` option for this update
1431 *
1432 * All `update` values are cast to their appropriate SchemaTypes before being sent.
1433 *
1434 * The `callback` function receives `(err, numberAffected, rawResponse)`.
1435 *
1436 * - `err` is the error if any occurred
1437 * - `numberAffected` is the count of updated documents Mongo reported
1438 * - `rawResponse` is the full response from Mongo
1439 *
1440 * ####Note:
1441 *
1442 * All top level keys which are not `atomic` operation names are treated as set operations:
1443 *
1444 * ####Example:
1445 *
1446 * var query = { name: 'borne' };
1447 * Model.update(query, { name: 'jason borne' }, options, callback)
1448 *
1449 * // is sent as
1450 * Model.update(query, { $set: { name: 'jason borne' }}, options, callback)
1451 *
1452 * This helps prevent accidentally overwriting all documents in your collection with `{ name: 'jason borne' }`.
1453 *
1454 * ####Note:
1455 *
1456 * 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.
1457 *
1458 * ####Note:
1459 *
1460 * To update documents without waiting for a response from MongoDB, do not pass a `callback`, then call `exec` on the returned [Query](#query-js):
1461 *
1462 * Comment.update({ _id: id }, { $set: { text: 'changed' }}).exec();
1463 *
1464 * ####Note:
1465 *
1466 * Although values are casted to their appropriate types when using update, the following are *not* applied:
1467 *
1468 * - defaults
1469 * - setters
1470 * - validators
1471 * - middleware
1472 *
1473 * If you need those features, use the traditional approach of first retrieving the document.
1474 *
1475 * Model.findOne({ name: 'borne' }, function (err, doc) {
1476 * if (err) ..
1477 * doc.name = 'jason borne';
1478 * doc.save(callback);
1479 * })
1480 *
1481 * @see strict schemas http://mongoosejs.com/docs/guide.html#strict
1482 * @param {Object} conditions
1483 * @param {Object} update
1484 * @param {Object} [options]
1485 * @param {Function} [callback]
1486 * @return {Query}
1487 * @api public
1488 */
1489
1490Model.update = function update (conditions, doc, options, callback) {
1491 if (arguments.length < 4) {
1492 if ('function' === typeof options) {
1493 // Scenario: update(conditions, doc, callback)
1494 callback = options;
1495 options = null;
1496 } else if ('function' === typeof doc) {
1497 // Scenario: update(doc, callback);
1498 callback = doc;
1499 doc = conditions;
1500 conditions = {};
1501 options = null;
1502 }
1503 }
1504
1505 var query = new Query(conditions, options).bind(this, 'update', doc);
1506
1507 if ('undefined' == typeof callback)
1508 return query;
1509
1510 this._applyNamedScope(query);
1511 return query.update(doc, callback);
1512};
1513
1514/**
1515 * Executes a mapReduce command.
1516 *
1517 * `o` is an object specifying all mapReduce options as well as the map and reduce functions. All options are delegated to the driver implementation.
1518 *
1519 * ####Example:
1520 *
1521 * var o = {};
1522 * o.map = function () { emit(this.name, 1) }
1523 * o.reduce = function (k, vals) { return vals.length }
1524 * User.mapReduce(o, function (err, results) {
1525 * console.log(results)
1526 * })
1527 *
1528 * ####Other options:
1529 *
1530 * - `query` {Object} query filter object.
1531 * - `limit` {Number} max number of documents
1532 * - `keeptemp` {Boolean, default:false} keep temporary data
1533 * - `finalize` {Function} finalize function
1534 * - `scope` {Object} scope variables exposed to map/reduce/finalize during execution
1535 * - `jsMode` {Boolean, default:false} it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X
1536 * - `verbose` {Boolean, default:false} provide statistics on job execution time.
1537 * - `out*` {Object, default: {inline:1}} sets the output target for the map reduce job.
1538 *
1539 * ####* out options:
1540 *
1541 * - `{inline:1}` the results are returned in an array
1542 * - `{replace: 'collectionName'}` add the results to collectionName: the results replace the collection
1543 * - `{reduce: 'collectionName'}` add the results to collectionName: if dups are detected, uses the reducer / finalize functions
1544 * - `{merge: 'collectionName'}` add the results to collectionName: if dups exist the new docs overwrite the old
1545 *
1546 * 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).
1547 *
1548 * ####Example:
1549 *
1550 * var o = {};
1551 * o.map = function () { emit(this.name, 1) }
1552 * o.reduce = function (k, vals) { return vals.length }
1553 * o.out = { replace: 'createdCollectionNameForResults' }
1554 * o.verbose = true;
1555 * User.mapReduce(o, function (err, model, stats) {
1556 * console.log('map reduce took %d ms', stats.processtime)
1557 * model.find().where('value').gt(10).exec(function (err, docs) {
1558 * console.log(docs);
1559 * });
1560 * })
1561 *
1562 * @param {Object} o an object specifying map-reduce options
1563 * @param {Function} callback
1564 * @see http://www.mongodb.org/display/DOCS/MapReduce
1565 * @api public
1566 */
1567
1568Model.mapReduce = function mapReduce (o, callback) {
1569 if ('function' != typeof callback) throw new Error('missing callback');
1570
1571 var self = this;
1572
1573 if (!Model.mapReduce.schema) {
1574 var opts = { noId: true, noVirtualId: true, strict: false }
1575 Model.mapReduce.schema = new Schema({}, opts);
1576 }
1577
1578 if (!o.out) o.out = { inline: 1 };
1579
1580 o.map = String(o.map);
1581 o.reduce = String(o.reduce);
1582
1583 if (o.query) {
1584 var q = new Query(o.query);
1585 q.cast(this);
1586 o.query = q._conditions;
1587 q = undefined;
1588 }
1589
1590 this.collection.mapReduce(null, null, o, function (err, ret, stats) {
1591 if (err) return callback(err);
1592
1593 if (ret.findOne && ret.mapReduce) {
1594 // returned a collection, convert to Model
1595 var model = Model.compile(
1596 '_mapreduce_' + ret.collectionName
1597 , Model.mapReduce.schema
1598 , ret.collectionName
1599 , self.db
1600 , self.base);
1601
1602 model._mapreduce = true;
1603
1604 return callback(err, model, stats);
1605 }
1606
1607 callback(err, ret, stats);
1608 });
1609}
1610
1611/**
1612 * Executes an aggregate command on this models collection.
1613 *
1614 * ####Example:
1615 *
1616 * // find the max age of all users
1617 * Users.aggregate(
1618 * { $group: { _id: null, maxAge: { $max: '$age' }}}
1619 * , { $project: { _id: 0, maxAge: 1 }}
1620 * , function (err, res) {
1621 * if (err) return handleError(err);
1622 * console.log(res); // [ { maxAge: 98 } ]
1623 * });
1624 *
1625 * ####NOTE:
1626 *
1627 * - At this time, arguments are not cast to the schema because $project operators allow redefining the "shape" of the documents at any stage of the pipeline.
1628 * - The documents returned are plain javascript objects, not mongoose documents cast to this models schema definition (since any shape of document can be returned).
1629 * - Requires MongoDB >= 2.1
1630 *
1631 * @param {Array} array an array of pipeline commands
1632 * @param {Object} [options]
1633 * @param {Function} callback
1634 * @see aggregation http://docs.mongodb.org/manual/applications/aggregation/
1635 * @see driver http://mongodb.github.com/node-mongodb-native/api-generated/collection.html#aggregate
1636 * @api public
1637 */
1638
1639Model.aggregate = function aggregate () {
1640 return this.collection.aggregate.apply(this.collection, arguments);
1641}
1642
1643/**
1644 * Populates document references.
1645 *
1646 * ####Available options:
1647 *
1648 * - path: space delimited path(s) to populate
1649 * - select: optional fields to select
1650 * - match: optional query conditions to match
1651 * - model: optional name of the model to use for population
1652 * - options: optional query options like sort, limit, etc
1653 *
1654 * ####Examples:
1655 *
1656 * // populates a single object
1657 * User.findById(id, function (err, user) {
1658 * var opts = [
1659 * { path: 'company', match: { x: 1 }, select: 'name' }
1660 * , { path: 'notes', options: { limit: 10 }, model: 'override' }
1661 * ]
1662 *
1663 * User.populate(user, opts, function (err, user) {
1664 * console.log(user);
1665 * })
1666 * })
1667 *
1668 * // populates an array of objects
1669 * User.find(match, function (err, users) {
1670 * var opts = [{ path: 'company', match: { x: 1 }, select: 'name' }]
1671 *
1672 * User.populate(users, opts, function (err, user) {
1673 * console.log(user);
1674 * })
1675 * })
1676 *
1677 * // imagine a Weapon model exists with two saved documents:
1678 * // { _id: 389, name: 'whip' }
1679 * // { _id: 8921, name: 'boomerang' }
1680 *
1681 * var user = { name: 'Indiana Jones', weapon: 389 }
1682 * Weapon.populate(user, { path: 'weapon', model: 'Weapon' }, function (err, user) {
1683 * console.log(user.weapon.name) // whip
1684 * })
1685 *
1686 * // populate many plain objects
1687 * var users = [{ name: 'Indiana Jones', weapon: 389 }]
1688 * users.push({ name: 'Batman', weapon: 8921 })
1689 * Weapon.populate(users, { path: 'weapon' }, function (err, users) {
1690 * users.forEach(function (user) {
1691 * console.log('%s uses a %s', users.name, user.weapon.name)
1692 * // Indiana Jones uses a whip
1693 * // Batman uses a boomerang
1694 * })
1695 * })
1696 * // Note that we didn't need to specify the Weapon model because
1697 * // we were already using it's populate() method.
1698 *
1699 * @param {Document|Array} docs Either a single document or array of documents to populate.
1700 * @param {Object} options A hash of key/val (path, options) used for population.
1701 * @param {Function} cb(err,doc) A callback, executed upon completion. Receives `err` and the `doc(s)`.
1702 * @api public
1703 */
1704
1705Model.populate = function (docs, paths, cb) {
1706 assert.equal('function', typeof cb);
1707
1708 // always callback on nextTick for consistent async behavior
1709 function callback () {
1710 var args = utils.args(arguments);
1711 process.nextTick(function () {
1712 cb.apply(null, args);
1713 });
1714 }
1715
1716 // normalized paths
1717 var paths = utils.populate(paths);
1718 var pending = paths.length;
1719
1720 if (0 === pending) {
1721 return callback(null, docs);
1722 }
1723
1724 // each path has its own query options and must be executed separately
1725 var i = pending;
1726 var path;
1727 while (i--) {
1728 path = paths[i];
1729 populate(this, docs, path, next);
1730 }
1731
1732 function next (err) {
1733 if (next.err) return;
1734 if (err) return callback(next.err = err);
1735 if (--pending) return;
1736 callback(null, docs);
1737 }
1738}
1739
1740/*!
1741 * Prepare population options
1742 */
1743
1744function populate (model, docs, options, cb) {
1745 var path = options.path
1746 var schema = model._getSchema(path);
1747
1748 // handle document arrays
1749 if (schema && schema.caster) {
1750 schema = schema.caster;
1751 }
1752
1753 // model name for the populate query
1754 var modelName = options.model && options.model.modelName
1755 || options.model // query options
1756 || schema && schema.options.ref // declared in schema
1757 || model.modelName // an ad-hoc structure
1758
1759 var Model = model.db.model(modelName);
1760
1761 // expose the model used
1762 options.model = Model;
1763
1764 // normalize single / multiple docs passed
1765 if (!Array.isArray(docs)) {
1766 docs = [docs];
1767 }
1768
1769 if (0 === docs.length || docs.every(utils.isNullOrUndefined)) {
1770 return cb();
1771 }
1772
1773 populateDocs(docs, options, cb);
1774}
1775
1776/*!
1777 * Populates `docs`
1778 */
1779
1780function populateDocs (docs, options, cb) {
1781 var select = options.select;
1782 var match = options.match;
1783 var path = options.path;
1784 var Model = options.model;
1785
1786 var rawIds = [];
1787 var i, doc, id;
1788 var len = docs.length;
1789 var found = 0;
1790 var isDocument;
1791 var subpath;
1792 var ret;
1793
1794 for (i = 0; i < len; i++) {
1795 ret = undefined;
1796 doc = docs[i];
1797 id = String(utils.getValue("_id", doc));
1798 isDocument = !! doc.$__;
1799
1800 if (isDocument && !doc.isModified(path)) {
1801 // it is possible a previously populated path is being
1802 // populated again. Because users can specify matcher
1803 // clauses in their populate arguments we use the cached
1804 // _ids from the original populate call to ensure all _ids
1805 // are looked up, but only if the path wasn't modified which
1806 // signifies the users intent of the state of the path.
1807 ret = doc.populated(path);
1808 }
1809
1810 if (!ret || Array.isArray(ret) && 0 === ret.length) {
1811 ret = utils.getValue(path, doc);
1812 }
1813
1814 if (ret) {
1815 ret = convertTo_id(ret);
1816
1817 // previously we always assigned this even if the document had no _id
1818 options._docs[id] = Array.isArray(ret)
1819 ? ret.slice()
1820 : ret;
1821 }
1822
1823 // always retain original values, even empty values. these are
1824 // used to map the query results back to the correct position.
1825 rawIds.push(ret);
1826
1827 if (isDocument) {
1828 // cache original populated _ids and model used
1829 doc.populated(path, options._docs[id], options);
1830 }
1831 }
1832
1833 var ids = utils.array.flatten(rawIds, function (item) {
1834 // no need to include undefined values in our query
1835 return undefined !== item;
1836 });
1837
1838 if (0 === ids.length || ids.every(utils.isNullOrUndefined)) {
1839 return cb();
1840 }
1841
1842 // preserve original match conditions by copying
1843 if (match) {
1844 match = utils.object.shallowCopy(match);
1845 } else {
1846 match = {};
1847 }
1848
1849 match._id || (match._id = { $in: ids });
1850
1851 var assignmentOpts = {};
1852 assignmentOpts.sort = options.options && options.options.sort || undefined;
1853 assignmentOpts.excludeId = /\s?-_id\s?/.test(select) || (select && 0 === select._id);
1854
1855 if (assignmentOpts.excludeId) {
1856 // override the exclusion from the query so we can use the _id
1857 // for document matching during assignment. we'll delete the
1858 // _id back off before returning the result.
1859 if ('string' == typeof select) {
1860 select = select.replace(/\s?-_id\s?/g, ' ');
1861 } else {
1862 // preserve original select conditions by copying
1863 select = utils.object.shallowCopy(select);
1864 delete select._id;
1865 }
1866 }
1867
1868 // if a limit option is passed, we should have the limit apply to *each*
1869 // document, not apply in the aggregate
1870 if (options.options && options.options.limit) {
1871 options.options.limit = options.options.limit * len;
1872 }
1873
1874 Model.find(match, select, options.options, function (err, vals) {
1875 if (err) return cb(err);
1876
1877 var lean = options.options && options.options.lean;
1878 var len = vals.length;
1879 var rawOrder = {};
1880 var rawDocs = {}
1881 var key;
1882 var val;
1883
1884 // optimization:
1885 // record the document positions as returned by
1886 // the query result.
1887 for (var i = 0; i < len; i++) {
1888 val = vals[i];
1889 key = String(utils.getValue('_id', val));
1890 rawDocs[key] = val;
1891 rawOrder[key] = i;
1892
1893 // flag each as result of population
1894 if (!lean) val.$__.wasPopulated = true;
1895 }
1896
1897 assignVals({
1898 rawIds: rawIds,
1899 rawDocs: rawDocs,
1900 rawOrder: rawOrder,
1901 docs: docs,
1902 path: path,
1903 options: assignmentOpts
1904 });
1905
1906 cb();
1907 });
1908}
1909
1910/*!
1911 * Retrieve the _id of `val` if a Document or Array of Documents.
1912 *
1913 * @param {Array|Document|Any} val
1914 * @return {Array|Document|Any}
1915 */
1916
1917function convertTo_id (val) {
1918 if (val instanceof Model) return val._id;
1919
1920 if (Array.isArray(val)) {
1921 for (var i = 0; i < val.length; ++i) {
1922 if (val[i] instanceof Model) {
1923 val[i] = val[i]._id;
1924 }
1925 }
1926 return val;
1927 }
1928
1929 return val;
1930}
1931
1932/*!
1933 * Assigns documents returned from a population query back
1934 * to the original document path.
1935 */
1936
1937function assignVals (o) {
1938 // replace the original ids in our intermediate _ids structure
1939 // with the documents found by query
1940
1941 assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, o.options);
1942
1943 // now update the original documents being populated using the
1944 // result structure that contains real documents.
1945
1946 var docs = o.docs;
1947 var path = o.path;
1948 var rawIds = o.rawIds;
1949 var options = o.options;
1950
1951 for (var i = 0; i < docs.length; ++i) {
1952 utils.setValue(path, rawIds[i], docs[i], function (val) {
1953 return valueFilter(val, options);
1954 });
1955 }
1956}
1957
1958/*!
1959 * 1) Apply backwards compatible find/findOne behavior to sub documents
1960 *
1961 * find logic:
1962 * a) filter out non-documents
1963 * b) remove _id from sub docs when user specified
1964 *
1965 * findOne
1966 * a) if no doc found, set to null
1967 * b) remove _id from sub docs when user specified
1968 *
1969 * 2) Remove _ids when specified by users query.
1970 *
1971 * background:
1972 * _ids are left in the query even when user excludes them so
1973 * that population mapping can occur.
1974 */
1975
1976function valueFilter (val, assignmentOpts) {
1977 if (Array.isArray(val)) {
1978 // find logic
1979 var ret = [];
1980 for (var i = 0; i < val.length; ++i) {
1981 var subdoc = val[i];
1982 if (!isDoc(subdoc)) continue;
1983 maybeRemoveId(subdoc, assignmentOpts);
1984 ret.push(subdoc);
1985 }
1986 return ret;
1987 }
1988
1989 // findOne
1990 if (isDoc(val)) {
1991 maybeRemoveId(val, assignmentOpts);
1992 return val;
1993 }
1994
1995 return null;
1996}
1997
1998/*!
1999 * Remove _id from `subdoc` if user specified "lean" query option
2000 */
2001
2002function maybeRemoveId (subdoc, assignmentOpts) {
2003 if (assignmentOpts.excludeId) {
2004 if ('function' == typeof subdoc.setValue) {
2005 subdoc.setValue('_id', undefined);
2006 } else {
2007 delete subdoc._id;
2008 }
2009 }
2010}
2011
2012/*!
2013 * Determine if `doc` is a document returned
2014 * by a populate query.
2015 */
2016
2017function isDoc (doc) {
2018 if (null == doc)
2019 return false;
2020
2021 var type = typeof doc;
2022 if ('string' == type)
2023 return false;
2024
2025 if ('number' == type)
2026 return false;
2027
2028 if (Buffer.isBuffer(doc))
2029 return false;
2030
2031 if ('ObjectID' == doc.constructor.name)
2032 return false;
2033
2034 // only docs
2035 return true;
2036}
2037
2038/*!
2039 * Assign `vals` returned by mongo query to the `rawIds`
2040 * structure returned from utils.getVals() honoring
2041 * query sort order if specified by user.
2042 *
2043 * This can be optimized.
2044 *
2045 * Rules:
2046 *
2047 * if the value of the path is not an array, use findOne rules, else find.
2048 * for findOne the results are assigned directly to doc path (including null results).
2049 * for find, if user specified sort order, results are assigned directly
2050 * else documents are put back in original order of array if found in results
2051 *
2052 * @param {Array} rawIds
2053 * @param {Array} vals
2054 * @param {Boolean} sort
2055 * @api private
2056 */
2057
2058function assignRawDocsToIdStructure (rawIds, resultDocs, resultOrder, options, recursed) {
2059 // honor user specified sort order
2060 var newOrder = [];
2061 var sorting = options.sort && rawIds.length > 1;
2062 var found;
2063 var doc;
2064 var sid;
2065 var id;
2066
2067 for (var i = 0; i < rawIds.length; ++i) {
2068 id = rawIds[i];
2069
2070 if (Array.isArray(id)) {
2071 // handle [ [id0, id2], [id3] ]
2072 assignRawDocsToIdStructure(id, resultDocs, resultOrder, options, true);
2073 newOrder.push(id);
2074 continue;
2075 }
2076
2077 if (null === id && !sorting) {
2078 // keep nulls for findOne unless sorting, which always
2079 // removes them (backward compat)
2080 newOrder.push(id);
2081 continue;
2082 }
2083
2084 sid = String(id);
2085 found = false;
2086
2087 if (recursed) {
2088 // apply find behavior
2089
2090 // assign matching documents in original order unless sorting
2091 doc = resultDocs[sid];
2092 if (doc) {
2093 if (sorting) {
2094 newOrder[resultOrder[sid]] = doc;
2095 } else {
2096 newOrder.push(doc);
2097 }
2098 } else {
2099 newOrder.push(id);
2100 }
2101 } else {
2102 // apply findOne behavior - if document in results, assign, else assign null
2103 newOrder[i] = doc = resultDocs[sid] || null;
2104 }
2105 }
2106
2107 rawIds.length = 0;
2108 if (newOrder.length) {
2109 // reassign the documents based on corrected order
2110
2111 // forEach skips over sparse entries in arrays so we
2112 // can safely use this to our advantage dealing with sorted
2113 // result sets too.
2114 newOrder.forEach(function (doc, i) {
2115 rawIds[i] = doc;
2116 });
2117 }
2118}
2119
2120/**
2121 * Finds the schema for `path`. This is different than
2122 * calling `schema.path` as it also resolves paths with
2123 * positional selectors (something.$.another.$.path).
2124 *
2125 * @param {String} path
2126 * @return {Schema}
2127 * @api private
2128 */
2129
2130Model._getSchema = function _getSchema (path) {
2131 var schema = this.schema
2132 , pathschema = schema.path(path);
2133
2134 if (pathschema)
2135 return pathschema;
2136
2137 // look for arrays
2138 return (function search (parts, schema) {
2139 var p = parts.length + 1
2140 , foundschema
2141 , trypath
2142
2143 while (p--) {
2144 trypath = parts.slice(0, p).join('.');
2145 foundschema = schema.path(trypath);
2146 if (foundschema) {
2147 if (foundschema.caster) {
2148
2149 // array of Mixed?
2150 if (foundschema.caster instanceof Types.Mixed) {
2151 return foundschema.caster;
2152 }
2153
2154 // Now that we found the array, we need to check if there
2155 // are remaining document paths to look up for casting.
2156 // Also we need to handle array.$.path since schema.path
2157 // doesn't work for that.
2158 // If there is no foundschema.schema we are dealing with
2159 // a path like array.$
2160 if (p !== parts.length && foundschema.schema) {
2161 if ('$' === parts[p]) {
2162 // comments.$.comments.$.title
2163 return search(parts.slice(p+1), foundschema.schema);
2164 } else {
2165 // this is the last path of the selector
2166 return search(parts.slice(p), foundschema.schema);
2167 }
2168 }
2169 }
2170 return foundschema;
2171 }
2172 }
2173 })(path.split('.'), schema)
2174}
2175
2176/*!
2177 * Compiler utility.
2178 *
2179 * @param {String} name model name
2180 * @param {Schema} schema
2181 * @param {String} collectionName
2182 * @param {Connection} connection
2183 * @param {Mongoose} base mongoose instance
2184 */
2185
2186Model.compile = function compile (name, schema, collectionName, connection, base) {
2187 var versioningEnabled = false !== schema.options.versionKey;
2188
2189 if (versioningEnabled && !schema.paths[schema.options.versionKey]) {
2190 // add versioning to top level documents only
2191 var o = {};
2192 o[schema.options.versionKey] = Number;
2193 schema.add(o);
2194 }
2195
2196 // generate new class
2197 function model (doc, fields, skipId) {
2198 if (!(this instanceof model))
2199 return new model(doc, fields, skipId);
2200 Model.call(this, doc, fields, skipId);
2201 };
2202
2203 model.base = base;
2204 model.modelName = name;
2205 model.__proto__ = Model;
2206 model.prototype.__proto__ = Model.prototype;
2207 model.model = Model.prototype.model;
2208 model.db = model.prototype.db = connection;
2209
2210 model.prototype.$__setSchema(schema);
2211
2212 var collectionOptions = {
2213 bufferCommands: schema.options.bufferCommands
2214 , capped: schema.options.capped
2215 };
2216
2217 model.prototype.collection = connection.collection(
2218 collectionName
2219 , collectionOptions
2220 );
2221
2222 // apply methods
2223 for (var i in schema.methods)
2224 model.prototype[i] = schema.methods[i];
2225
2226 // apply statics
2227 for (var i in schema.statics)
2228 model[i] = schema.statics[i];
2229
2230 // apply named scopes
2231 if (schema.namedScopes) schema.namedScopes.compile(model);
2232
2233 model.schema = model.prototype.schema;
2234 model.options = model.prototype.options;
2235 model.collection = model.prototype.collection;
2236
2237 return model;
2238};
2239
2240/*!
2241 * Subclass this model with `conn`, `schema`, and `collection` settings.
2242 *
2243 * @param {Connection} conn
2244 * @param {Schema} [schema]
2245 * @param {String} [collection]
2246 * @return {Model}
2247 */
2248
2249Model.__subclass = function subclass (conn, schema, collection) {
2250 // subclass model using this connection and collection name
2251 var model = this;
2252
2253 var Model = function Model (doc, fields, skipId) {
2254 if (!(this instanceof Model)) {
2255 return new Model(doc, fields, skipId);
2256 }
2257 model.call(this, doc, fields, skipId);
2258 }
2259
2260 Model.__proto__ = model;
2261 Model.prototype.__proto__ = model.prototype;
2262 Model.db = Model.prototype.db = conn;
2263
2264 var s = schema && 'string' != typeof schema
2265 ? schema
2266 : model.prototype.schema;
2267
2268 if (!collection) {
2269 collection = model.prototype.schema.get('collection')
2270 || utils.toCollectionName(model.modelName);
2271 }
2272
2273 var collectionOptions = {
2274 bufferCommands: s ? s.options.bufferCommands : true
2275 , capped: s && s.options.capped
2276 };
2277
2278 Model.prototype.collection = conn.collection(collection, collectionOptions);
2279 Model.collection = Model.prototype.collection;
2280 Model.init();
2281 return Model;
2282}
2283
2284/*!
2285 * Module exports.
2286 */
2287
2288module.exports = exports = Model;