You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2288 lines
63 KiB

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