model.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. 'use strict';
  2. var EventEmitter = require('events').EventEmitter;
  3. var _ = require('lodash');
  4. var Promise = require('bluebird');
  5. var util = require('./util');
  6. var Document = require('./document');
  7. var Query = require('./query');
  8. var Schema = require('./schema');
  9. var Types = require('./types');
  10. var WarehouseError = require('./error');
  11. var PopulationError = require('./error/population');
  12. var Mutex = require('./mutex');
  13. var parseArgs = util.parseArgs;
  14. var reverse = util.reverse;
  15. var shuffle = _.shuffle;
  16. var getProp = util.getProp;
  17. var setGetter = util.setGetter;
  18. var extend = _.assign;
  19. var isArray = Array.isArray;
  20. /**
  21. * Model constructor.
  22. *
  23. * @class
  24. * @param {string} name Model name
  25. * @param {Schema|object} [schema] Schema
  26. * @extends EventEmitter
  27. */
  28. function Model(name, schema_) {
  29. EventEmitter.call(this);
  30. var schema, i, len, key;
  31. // Define schema
  32. if (schema_ instanceof Schema) {
  33. schema = schema_;
  34. } else if (typeof schema_ === 'object') {
  35. schema = new Schema(schema_);
  36. } else {
  37. schema = new Schema();
  38. }
  39. // Set `_id` path for schema
  40. if (!schema.path('_id')) {
  41. schema.path('_id', {type: Types.CUID, required: true});
  42. }
  43. this.name = name;
  44. this.data = {};
  45. this._mutex = new Mutex();
  46. this.schema = schema;
  47. this.length = 0;
  48. var _Document = this.Document = function(data) {
  49. Document.call(this, data);
  50. // Apply getters
  51. schema._applyGetters(this);
  52. };
  53. util.inherits(_Document, Document);
  54. _Document.prototype._model = this;
  55. _Document.prototype._schema = schema;
  56. var _Query = this.Query = function(data) {
  57. Query.call(this, data);
  58. };
  59. util.inherits(_Query, Query);
  60. _Query.prototype._model = this;
  61. _Query.prototype._schema = schema;
  62. // Apply static methods
  63. var statics = schema.statics;
  64. var staticKeys = Object.keys(statics);
  65. for (i = 0, len = staticKeys.length; i < len; i++) {
  66. key = staticKeys[i];
  67. this[key] = statics[key];
  68. }
  69. // Apply instance methods
  70. var methods = schema.methods;
  71. var methodKeys = Object.keys(methods);
  72. for (i = 0, len = methodKeys.length; i < len; i++) {
  73. key = methodKeys[i];
  74. _Document.prototype[key] = methods[key];
  75. }
  76. }
  77. util.inherits(Model, EventEmitter);
  78. /**
  79. * Creates a new document.
  80. *
  81. * @param {object} data
  82. * @return {Document}
  83. */
  84. Model.prototype.new = function(data) {
  85. return new this.Document(data);
  86. };
  87. /**
  88. * Finds a document by its identifier.
  89. *
  90. * @param {*} id
  91. * @param {object} options
  92. * @param {boolean} [options.lean=false] Returns a plain JavaScript object
  93. * @return {Document|object}
  94. */
  95. Model.prototype.findById = function(id, options_) {
  96. var raw = this.data[id];
  97. if (!raw) return;
  98. var options = extend({
  99. lean: false
  100. }, options_);
  101. var data = _.cloneDeep(raw);
  102. return options.lean ? data : this.new(data);
  103. };
  104. Model.prototype.get = Model.prototype.findById;
  105. /**
  106. * Checks if the model contains a document with the specified id.
  107. *
  108. * @param {*} id
  109. * @return {boolean}
  110. */
  111. Model.prototype.has = function(id) {
  112. return Boolean(this.data[id]);
  113. };
  114. function execHooks(schema, type, event, data) {
  115. var hooks = schema.hooks[type][event];
  116. if (!hooks.length) return Promise.resolve(data);
  117. return Promise.each(hooks, function(hook) {
  118. return hook(data);
  119. }).thenReturn(data);
  120. }
  121. /**
  122. * Acquires write lock.
  123. *
  124. * @param {*} id
  125. * @return {Promise}
  126. * @private
  127. */
  128. Model.prototype._acquireWriteLock = function(id) {
  129. var mutex = this._mutex;
  130. return new Promise(function(resolve, reject) {
  131. mutex.lock(resolve);
  132. }).disposer(function() {
  133. mutex.unlock();
  134. });
  135. };
  136. /**
  137. * Inserts a document.
  138. *
  139. * @param {Document|object} data
  140. * @return {Promise}
  141. * @private
  142. */
  143. Model.prototype._insertOne = function(data_) {
  144. var self = this;
  145. var schema = this.schema;
  146. // Apply getters
  147. var data = data_ instanceof self.Document ? data_ : self.new(data_);
  148. var id = data._id;
  149. // Check ID
  150. if (!id) {
  151. return Promise.reject(new WarehouseError('ID is not defined', WarehouseError.ID_UNDEFINED));
  152. }
  153. if (this.has(id)) {
  154. return Promise.reject(new WarehouseError('ID `' + id + '` has been used', WarehouseError.ID_EXIST));
  155. }
  156. // Apply setters
  157. var result = data.toObject();
  158. schema._applySetters(result);
  159. // Pre-hooks
  160. return execHooks(schema, 'pre', 'save', data).then(function(data) {
  161. // Insert data
  162. self.data[id] = result;
  163. self.length++;
  164. self.emit('insert', data);
  165. return execHooks(schema, 'post', 'save', data);
  166. });
  167. };
  168. /**
  169. * Inserts a document.
  170. *
  171. * @param {object} data
  172. * @param {function} [callback]
  173. * @return {Promise}
  174. */
  175. Model.prototype.insertOne = function(data, callback) {
  176. var self = this;
  177. return Promise.using(this._acquireWriteLock(), function() {
  178. return self._insertOne(data);
  179. }).asCallback(callback);
  180. };
  181. /**
  182. * Inserts documents.
  183. *
  184. * @param {object|array} data
  185. * @param {function} [callback]
  186. * @return {Promise}
  187. */
  188. Model.prototype.insert = function(data, callback) {
  189. if (isArray(data)) {
  190. var self = this;
  191. return Promise.mapSeries(data, function(item) {
  192. return self.insertOne(item);
  193. }).asCallback(callback);
  194. }
  195. return this.insertOne(data, callback);
  196. };
  197. /**
  198. * Inserts the document if it does not exist; otherwise updates it.
  199. *
  200. * @param {object} data
  201. * @param {function} [callback]
  202. * @return {Promise}
  203. */
  204. Model.prototype.save = function(data, callback) {
  205. var id = data._id;
  206. var self = this;
  207. if (!id) return this.insertOne(data, callback);
  208. return Promise.using(this._acquireWriteLock(), function() {
  209. if (self.has(id)) {
  210. return self._replaceById(id, data);
  211. }
  212. return self._insertOne(data);
  213. }).asCallback(callback);
  214. };
  215. /**
  216. * Updates a document with a compiled stack.
  217. *
  218. * @param {*} id
  219. * @param {array} stack
  220. * @return {Promise}
  221. * @private
  222. */
  223. Model.prototype._updateWithStack = function(id, stack) {
  224. var self = this;
  225. var schema = self.schema;
  226. var data = self.data[id];
  227. if (!data) {
  228. return Promise.reject(new WarehouseError('ID `' + id + '` does not exist', WarehouseError.ID_NOT_EXIST));
  229. }
  230. // Clone data
  231. var result = _.cloneDeep(data);
  232. // Update
  233. for (var i = 0, len = stack.length; i < len; i++) {
  234. stack[i](result);
  235. }
  236. // Apply getters
  237. var doc = self.new(result);
  238. // Apply setters
  239. result = doc.toObject();
  240. schema._applySetters(result);
  241. // Pre-hooks
  242. return execHooks(schema, 'pre', 'save', doc).then(function(data) {
  243. // Update data
  244. self.data[id] = result;
  245. self.emit('update', data);
  246. return execHooks(schema, 'post', 'save', data);
  247. });
  248. };
  249. /**
  250. * Finds a document by its identifier and update it.
  251. *
  252. * @param {*} id
  253. * @param {object} update
  254. * @param {function} [callback]
  255. * @return {Promise}
  256. */
  257. Model.prototype.updateById = function(id, update, callback) {
  258. var self = this;
  259. return Promise.using(this._acquireWriteLock(), function() {
  260. var stack = self.schema._parseUpdate(update);
  261. return self._updateWithStack(id, stack);
  262. }).asCallback(callback);
  263. };
  264. /**
  265. * Updates matching documents.
  266. *
  267. * @param {object} query
  268. * @param {object} data
  269. * @param {function} [callback]
  270. * @return {Promise}
  271. */
  272. Model.prototype.update = function(query, data, callback) {
  273. return this.find(query).update(data, callback);
  274. };
  275. /**
  276. * Finds a document by its identifier and replace it.
  277. *
  278. * @param {*} id
  279. * @param {object} data
  280. * @return {Promise}
  281. * @private
  282. */
  283. Model.prototype._replaceById = function(id, data_) {
  284. var self = this;
  285. var schema = this.schema;
  286. if (!this.has(id)) {
  287. return Promise.reject(new WarehouseError('ID `' + id + '` does not exist', WarehouseError.ID_NOT_EXIST));
  288. }
  289. data_._id = id;
  290. // Apply getters
  291. var data = data instanceof self.Document ? data_ : self.new(data_);
  292. // Apply setters
  293. var result = data.toObject();
  294. schema._applySetters(result);
  295. // Pre-hooks
  296. return execHooks(schema, 'pre', 'save', data).then(function(data) {
  297. // Replace data
  298. self.data[id] = result;
  299. self.emit('update', data);
  300. return execHooks(schema, 'post', 'save', data);
  301. });
  302. };
  303. /**
  304. * Finds a document by its identifier and replace it.
  305. *
  306. * @param {*} id
  307. * @param {object} data
  308. * @param {function} [callback]
  309. * @return {Promise}
  310. */
  311. Model.prototype.replaceById = function(id, data, callback) {
  312. var self = this;
  313. return Promise.using(this._acquireWriteLock(), function() {
  314. return self._replaceById(id, data);
  315. }).asCallback(callback);
  316. };
  317. /**
  318. * Replaces matching documents.
  319. *
  320. * @param {object} query
  321. * @param {object} data
  322. * @param {function} [callback]
  323. * @return {Promise}
  324. */
  325. Model.prototype.replace = function(query, data, callback) {
  326. return this.find(query).replace(data, callback);
  327. };
  328. /**
  329. * Finds a document by its identifier and remove it.
  330. *
  331. * @param {*} id
  332. * @param {function} [callback]
  333. * @return {Promise}
  334. * @private
  335. */
  336. Model.prototype._removeById = function(id) {
  337. var self = this;
  338. var schema = this.schema;
  339. var data = this.data[id];
  340. if (!data) {
  341. return Promise.reject(new WarehouseError('ID `' + id + '` does not exist', WarehouseError.ID_NOT_EXIST));
  342. }
  343. // Pre-hooks
  344. return execHooks(schema, 'pre', 'remove', data).then(function(data) {
  345. // Remove data
  346. self.data[id] = null;
  347. self.length--;
  348. self.emit('remove', data);
  349. return execHooks(schema, 'post', 'remove', data);
  350. });
  351. };
  352. /**
  353. * Finds a document by its identifier and remove it.
  354. *
  355. * @param {*} id
  356. * @param {function} [callback]
  357. * @return {Promise}
  358. */
  359. Model.prototype.removeById = function(id, callback) {
  360. var self = this;
  361. return Promise.using(this._acquireWriteLock(), function() {
  362. return self._removeById(id);
  363. }).asCallback(callback);
  364. };
  365. /**
  366. * Removes matching documents.
  367. *
  368. * @param {object} query
  369. * @param {object} [callback]
  370. * @return {Promise}
  371. */
  372. Model.prototype.remove = function(query, callback) {
  373. return this.find(query).remove(callback);
  374. };
  375. /**
  376. * Deletes a model.
  377. */
  378. Model.prototype.destroy = function() {
  379. this._database._models[this.name] = null;
  380. };
  381. /**
  382. * Returns the number of elements.
  383. *
  384. * @return {number}
  385. */
  386. Model.prototype.count = function() {
  387. return this.length;
  388. };
  389. Model.prototype.size = Model.prototype.count;
  390. /**
  391. * Iterates over all documents.
  392. *
  393. * @param {function} iterator
  394. * @param {object} [options] See {@link Model#findById}.
  395. */
  396. Model.prototype.forEach = function(iterator, options) {
  397. var keys = Object.keys(this.data);
  398. var num = 0;
  399. var data;
  400. for (var i = 0, len = keys.length; i < len; i++) {
  401. data = this.findById(keys[i], options);
  402. if (data) iterator(data, num++);
  403. }
  404. };
  405. Model.prototype.each = Model.prototype.forEach;
  406. /**
  407. * Returns an array containing all documents.
  408. *
  409. * @param {Object} [options] See {@link Model#findById}.
  410. * @return {Array}
  411. */
  412. Model.prototype.toArray = function(options) {
  413. var result = new Array(this.length);
  414. this.forEach(function(item, i) {
  415. result[i] = item;
  416. }, options);
  417. return result;
  418. };
  419. /**
  420. * Finds matching documents.
  421. *
  422. * @param {Object} query
  423. * @param {Object} [options]
  424. * @param {Number} [options.limit=0] Limits the number of documents returned.
  425. * @param {Number} [options.skip=0] Skips the first elements.
  426. * @param {Boolean} [options.lean=false] Returns a plain JavaScript object.
  427. * @return {Query|Array}
  428. */
  429. Model.prototype.find = function(query, options_) {
  430. var options = options_ || {};
  431. var filter = this.schema._execQuery(query);
  432. var keys = Object.keys(this.data);
  433. var len = keys.length;
  434. var limit = options.limit || this.length;
  435. var skip = options.skip;
  436. var data = this.data;
  437. var arr = [];
  438. var key, item;
  439. for (var i = 0; limit && i < len; i++) {
  440. key = keys[i];
  441. item = data[key];
  442. if (item && filter(item)) {
  443. if (skip) {
  444. skip--;
  445. } else {
  446. arr.push(this.findById(key, options));
  447. limit--;
  448. }
  449. }
  450. }
  451. return options.lean ? arr : new this.Query(arr);
  452. };
  453. /**
  454. * Finds the first matching documents.
  455. *
  456. * @param {Object} query
  457. * @param {Object} [options]
  458. * @param {Number} [options.skip=0] Skips the first elements.
  459. * @param {Boolean} [options.lean=false] Returns a plain JavaScript object.
  460. * @return {Document|Object}
  461. */
  462. Model.prototype.findOne = function(query, options_) {
  463. var options = options_ || {};
  464. options.limit = 1;
  465. var result = this.find(query, options);
  466. return options.lean ? result[0] : result.data[0];
  467. };
  468. /**
  469. * Sorts documents. See {@link Query#sort}.
  470. *
  471. * @param {String|Object} orderby
  472. * @param {String|Number} [order]
  473. * @return {Query}
  474. */
  475. Model.prototype.sort = function(orderby, order) {
  476. var sort = parseArgs(orderby, order);
  477. var fn = this.schema._execSort(sort);
  478. return new this.Query(this.toArray().sort(fn));
  479. };
  480. /**
  481. * Returns the document at the specified index. `num` can be a positive or
  482. * negative number.
  483. *
  484. * @param {Number} i
  485. * @param {Object} [options] See {@link Model#findById}.
  486. * @return {Document|Object}
  487. */
  488. Model.prototype.eq = function(i_, options) {
  489. var index = i_ < 0 ? this.length + i_ : i_;
  490. var data = this.data;
  491. var keys = Object.keys(data);
  492. var key, item;
  493. for (var i = 0, len = keys.length; i < len; i++) {
  494. key = keys[i];
  495. item = data[key];
  496. if (!item) continue;
  497. if (index) {
  498. index--;
  499. } else {
  500. return this.findById(key, options);
  501. }
  502. }
  503. };
  504. /**
  505. * Returns the first document.
  506. *
  507. * @param {Object} [options] See {@link Model#findById}.
  508. * @return {Document|Object}
  509. */
  510. Model.prototype.first = function(options) {
  511. return this.eq(0, options);
  512. };
  513. /**
  514. * Returns the last document.
  515. *
  516. * @param {Object} [options] See {@link Model#findById}.
  517. * @return {Document|Object}
  518. */
  519. Model.prototype.last = function(options) {
  520. return this.eq(-1, options);
  521. };
  522. /**
  523. * Returns the specified range of documents.
  524. *
  525. * @param {Number} start
  526. * @param {Number} [end]
  527. * @return {Query}
  528. */
  529. Model.prototype.slice = function(start_, end_) {
  530. var total = this.length;
  531. var start = start_ | 0;
  532. if (start < 0) start += total;
  533. if (start > total - 1) return new this.Query([]);
  534. var end = end_ | 0 || total;
  535. if (end < 0) end += total;
  536. var len = start > end ? 0 : end - start;
  537. if (len > total) len = total - start;
  538. if (!len) return new this.Query([]);
  539. var arr = new Array(len);
  540. var keys = Object.keys(this.data);
  541. var keysLen = keys.length;
  542. var num = 0;
  543. var data;
  544. for (var i = 0; num < len && i < keysLen; i++) {
  545. data = this.findById(keys[i]);
  546. if (!data) continue;
  547. if (start) {
  548. start--;
  549. } else {
  550. arr[num++] = data;
  551. }
  552. }
  553. return new this.Query(arr);
  554. };
  555. /**
  556. * Limits the number of documents returned.
  557. *
  558. * @param {Number} i
  559. * @return {Query}
  560. */
  561. Model.prototype.limit = function(i) {
  562. return this.slice(0, i);
  563. };
  564. /**
  565. * Specifies the number of items to skip.
  566. *
  567. * @param {Number} i
  568. * @return {Query}
  569. */
  570. Model.prototype.skip = function(i) {
  571. return this.slice(i);
  572. };
  573. /**
  574. * Returns documents in a reversed order.
  575. *
  576. * @return {Query}
  577. */
  578. Model.prototype.reverse = function() {
  579. return new this.Query(reverse(this.toArray()));
  580. };
  581. /**
  582. * Returns documents in random order.
  583. *
  584. * @return {Query}
  585. */
  586. Model.prototype.shuffle = function() {
  587. return new this.Query(shuffle(this.toArray()));
  588. };
  589. Model.prototype.random = Model.prototype.shuffle;
  590. /**
  591. * Creates an array of values by iterating each element in the collection.
  592. *
  593. * @param {Function} iterator
  594. * @param {Object} [options]
  595. * @return {Array}
  596. */
  597. Model.prototype.map = function(iterator, options) {
  598. var result = new Array(this.length);
  599. this.forEach(function(item, i) {
  600. result[i] = iterator(item, i);
  601. }, options);
  602. return result;
  603. };
  604. /**
  605. * Reduces a collection to a value which is the accumulated result of iterating
  606. * each element in the collection.
  607. *
  608. * @param {Function} iterator
  609. * @param {*} [initial] By default, the initial value is the first document.
  610. * @return {*}
  611. */
  612. Model.prototype.reduce = function(iterator, initial) {
  613. var arr = this.toArray();
  614. var len = this.length;
  615. var i, result;
  616. if (initial === undefined) {
  617. i = 1;
  618. result = arr[0];
  619. } else {
  620. i = 0;
  621. result = initial;
  622. }
  623. for (; i < len; i++) {
  624. result = iterator(result, arr[i], i);
  625. }
  626. return result;
  627. };
  628. /**
  629. * Reduces a collection to a value which is the accumulated result of iterating
  630. * each element in the collection from right to left.
  631. *
  632. * @param {Function} iterator
  633. * @param {*} [initial] By default, the initial value is the last document.
  634. * @return {*}
  635. */
  636. Model.prototype.reduceRight = function(iterator, initial) {
  637. var arr = this.toArray();
  638. var len = this.length;
  639. var i, result;
  640. if (initial === undefined) {
  641. i = len - 2;
  642. result = arr[len - 1];
  643. } else {
  644. i = len - 1;
  645. result = initial;
  646. }
  647. for (; i >= 0; i--) {
  648. result = iterator(result, arr[i], i);
  649. }
  650. return result;
  651. };
  652. /**
  653. * Creates a new array with all documents that pass the test implemented by the
  654. * provided function.
  655. *
  656. * @param {Function} iterator
  657. * @param {Object} [options]
  658. * @return {Query}
  659. */
  660. Model.prototype.filter = function(iterator, options) {
  661. var arr = [];
  662. this.forEach(function(item, i) {
  663. if (iterator(item, i)) arr.push(item);
  664. }, options);
  665. return new this.Query(arr);
  666. };
  667. /**
  668. * Tests whether all documents pass the test implemented by the provided
  669. * function.
  670. *
  671. * @param {Function} iterator
  672. * @return {Boolean}
  673. */
  674. Model.prototype.every = function(iterator) {
  675. var keys = Object.keys(this.data);
  676. var len = keys.length;
  677. var num = 0;
  678. var data;
  679. if (!len) return true;
  680. for (var i = 0; i < len; i++) {
  681. data = this.findById(keys[i]);
  682. if (data) {
  683. if (!iterator(data, num++)) return false;
  684. }
  685. }
  686. return true;
  687. };
  688. /**
  689. * Tests whether some documents pass the test implemented by the provided
  690. * function.
  691. *
  692. * @param {Function} iterator
  693. * @return {Boolean}
  694. */
  695. Model.prototype.some = function(iterator) {
  696. var keys = Object.keys(this.data);
  697. var len = keys.length;
  698. var num = 0;
  699. var data;
  700. if (!len) return false;
  701. for (var i = 0; i < len; i++) {
  702. data = this.findById(keys[i]);
  703. if (data) {
  704. if (iterator(data, num++)) return true;
  705. }
  706. }
  707. return false;
  708. };
  709. /**
  710. * Returns a getter function for normal population.
  711. *
  712. * @param {Object} data
  713. * @param {Model} model
  714. * @param {Object} options
  715. * @return {Function}
  716. * @private
  717. */
  718. Model.prototype._populateGetter = function(data, model, options) {
  719. var hasCache = false;
  720. var cache;
  721. return function() {
  722. if (!hasCache) {
  723. cache = model.findById(data);
  724. hasCache = true;
  725. }
  726. return cache;
  727. };
  728. };
  729. /**
  730. * Returns a getter function for array population.
  731. *
  732. * @param {Object} data
  733. * @param {Model} model
  734. * @param {Object} options
  735. * @return {Function}
  736. * @private
  737. */
  738. Model.prototype._populateGetterArray = function(data, model, options) {
  739. var Query = model.Query;
  740. var hasCache = false;
  741. var cache;
  742. return function() {
  743. if (!hasCache) {
  744. var arr = [];
  745. for (var i = 0, len = data.length; i < len; i++) {
  746. arr.push(model.findById(data[i]));
  747. }
  748. if (options.match) {
  749. cache = new Query(arr).find(options.match, options);
  750. } else if (options.skip) {
  751. if (options.limit) {
  752. arr = arr.slice(options.skip, options.skip + options.limit);
  753. } else {
  754. arr = arr.slice(options.skip);
  755. }
  756. cache = new Query(arr);
  757. } else if (options.limit) {
  758. cache = new Query(arr.slice(0, options.limit));
  759. } else {
  760. cache = new Query(arr);
  761. }
  762. if (options.sort) {
  763. cache = cache.sort(options.sort);
  764. }
  765. hasCache = true;
  766. }
  767. return cache;
  768. };
  769. };
  770. /**
  771. * Populates document references with a compiled stack.
  772. *
  773. * @param {Object} data
  774. * @param {Array} stack
  775. * @return {Object}
  776. * @private
  777. */
  778. Model.prototype._populate = function(data, stack) {
  779. var models = this._database._models;
  780. var item, model, path, prop;
  781. for (var i = 0, len = stack.length; i < len; i++) {
  782. item = stack[i];
  783. model = models[item.model];
  784. if (!model) {
  785. throw new PopulationError('Model `' + item.model + '` does not exist');
  786. }
  787. path = item.path;
  788. prop = getProp(data, path);
  789. if (isArray(prop)) {
  790. setGetter(data, path, this._populateGetterArray(prop, model, item));
  791. } else {
  792. setGetter(data, path, this._populateGetter(prop, model, item));
  793. }
  794. }
  795. return data;
  796. };
  797. /**
  798. * Populates document references.
  799. *
  800. * @param {String|Object} path
  801. * @return {Query}
  802. */
  803. Model.prototype.populate = function(path) {
  804. if (!path) throw new TypeError('path is required');
  805. var stack = this.schema._parsePopulate(path);
  806. var arr = new Array(this.length);
  807. var self = this;
  808. this.forEach(function(item, i) {
  809. arr[i] = self._populate(item, stack);
  810. });
  811. return new Query(arr);
  812. };
  813. /**
  814. * Imports data.
  815. *
  816. * @param {Array} arr
  817. * @private
  818. */
  819. Model.prototype._import = function(arr) {
  820. var len = arr.length;
  821. var data = this.data;
  822. var schema = this.schema;
  823. var item;
  824. for (var i = 0; i < len; i++) {
  825. item = arr[i];
  826. data[item._id] = schema._parseDatabase(item);
  827. }
  828. this.length = len;
  829. };
  830. /**
  831. * Exports data.
  832. *
  833. * @return {String}
  834. * @private
  835. */
  836. Model.prototype._export = function() {
  837. var arr = new Array(this.length);
  838. var schema = this.schema;
  839. this.forEach(function(item, i) {
  840. arr[i] = schema._exportDatabase(item);
  841. }, {lean: true});
  842. return JSON.stringify(arr);
  843. };
  844. module.exports = Model;