model.js 80 KB

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