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

array.js 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  1. /*!
  2. * Module dependencies.
  3. */
  4. 'use strict';
  5. const CoreMongooseArray = require('./core_array');
  6. const EmbeddedDocument = require('./embedded');
  7. const Document = require('../document');
  8. const ObjectId = require('./objectid');
  9. const cleanModifiedSubpaths = require('../helpers/document/cleanModifiedSubpaths');
  10. const get = require('../helpers/get');
  11. const internalToObjectOptions = require('../options').internalToObjectOptions;
  12. const utils = require('../utils');
  13. const util = require('util');
  14. const arrayAtomicsSymbol = require('../helpers/symbols').arrayAtomicsSymbol;
  15. const arrayParentSymbol = require('../helpers/symbols').arrayParentSymbol;
  16. const arrayPathSymbol = require('../helpers/symbols').arrayPathSymbol;
  17. const arraySchemaSymbol = require('../helpers/symbols').arraySchemaSymbol;
  18. const isMongooseObject = utils.isMongooseObject;
  19. /**
  20. * Mongoose Array constructor.
  21. *
  22. * ####NOTE:
  23. *
  24. * _Values always have to be passed to the constructor to initialize, otherwise `MongooseArray#push` will mark the array as modified._
  25. *
  26. * @param {Array} values
  27. * @param {String} path
  28. * @param {Document} doc parent document
  29. * @api private
  30. * @inherits Array
  31. * @see http://bit.ly/f6CnZU
  32. */
  33. function MongooseArray(values, path, doc) {
  34. // TODO: replace this with `new CoreMongooseArray().concat()` when we remove
  35. // support for node 4.x and 5.x, see https://i.imgur.com/UAAHk4S.png
  36. const arr = new CoreMongooseArray();
  37. if (Array.isArray(values)) {
  38. values.forEach(v => { arr.push(v); });
  39. }
  40. const keysMA = Object.keys(MongooseArray.mixin);
  41. const numKeys = keysMA.length;
  42. for (let i = 0; i < numKeys; ++i) {
  43. arr[keysMA[i]] = MongooseArray.mixin[keysMA[i]];
  44. }
  45. arr[arrayPathSymbol] = path;
  46. arr.validators = [];
  47. arr[arrayAtomicsSymbol] = {};
  48. arr[arraySchemaSymbol] = void 0;
  49. if (util.inspect.custom) {
  50. arr[util.inspect.custom] = arr.inspect;
  51. }
  52. // Because doc comes from the context of another function, doc === global
  53. // can happen if there was a null somewhere up the chain (see #3020)
  54. // RB Jun 17, 2015 updated to check for presence of expected paths instead
  55. // to make more proof against unusual node environments
  56. if (doc && doc instanceof Document) {
  57. arr[arrayParentSymbol] = doc;
  58. arr[arraySchemaSymbol] = doc.schema.path(path);
  59. }
  60. return arr;
  61. }
  62. MongooseArray.mixin = {
  63. /*!
  64. * ignore
  65. */
  66. toBSON: function() {
  67. return this.toObject(internalToObjectOptions);
  68. },
  69. /*!
  70. * ignore
  71. */
  72. $parent: function() {
  73. return this[arrayParentSymbol];
  74. },
  75. /*!
  76. * ignore
  77. */
  78. $schema: function() {
  79. return this[arraySchemaSymbol];
  80. },
  81. /*!
  82. * ignore
  83. */
  84. $path: function() {
  85. return this[arrayPathSymbol];
  86. },
  87. /*!
  88. * ignore
  89. */
  90. $atomics: function() {
  91. return this[arrayAtomicsSymbol];
  92. },
  93. /**
  94. * Casts a member based on this arrays schema.
  95. *
  96. * @param {any} value
  97. * @return value the casted value
  98. * @method _cast
  99. * @api private
  100. * @memberOf MongooseArray
  101. */
  102. _cast: function(value) {
  103. let populated = false;
  104. let Model;
  105. if (this[arrayParentSymbol]) {
  106. populated = this[arrayParentSymbol].populated(this[arrayPathSymbol], true);
  107. }
  108. if (populated && value !== null && value !== undefined) {
  109. // cast to the populated Models schema
  110. Model = populated.options.model || populated.options.Model;
  111. // only objects are permitted so we can safely assume that
  112. // non-objects are to be interpreted as _id
  113. if (Buffer.isBuffer(value) ||
  114. value instanceof ObjectId || !utils.isObject(value)) {
  115. value = {_id: value};
  116. }
  117. // gh-2399
  118. // we should cast model only when it's not a discriminator
  119. const isDisc = value.schema && value.schema.discriminatorMapping &&
  120. value.schema.discriminatorMapping.key !== undefined;
  121. if (!isDisc) {
  122. value = new Model(value);
  123. }
  124. return this[arraySchemaSymbol].caster.applySetters(value, this[arrayParentSymbol], true);
  125. }
  126. return this[arraySchemaSymbol].caster.applySetters(value, this[arrayParentSymbol], false);
  127. },
  128. /**
  129. * Marks this array as modified.
  130. *
  131. * If it bubbles up from an embedded document change, then it takes the following arguments (otherwise, takes 0 arguments)
  132. *
  133. * @param {EmbeddedDocument} embeddedDoc the embedded doc that invoked this method on the Array
  134. * @param {String} embeddedPath the path which changed in the embeddedDoc
  135. * @method _markModified
  136. * @api private
  137. * @memberOf MongooseArray
  138. */
  139. _markModified: function(elem, embeddedPath) {
  140. const parent = this[arrayParentSymbol];
  141. let dirtyPath;
  142. if (parent) {
  143. dirtyPath = this[arrayPathSymbol];
  144. if (arguments.length) {
  145. if (embeddedPath != null) {
  146. // an embedded doc bubbled up the change
  147. dirtyPath = dirtyPath + '.' + this.indexOf(elem) + '.' + embeddedPath;
  148. } else {
  149. // directly set an index
  150. dirtyPath = dirtyPath + '.' + elem;
  151. }
  152. }
  153. parent.markModified(dirtyPath, arguments.length > 0 ? elem : parent);
  154. }
  155. return this;
  156. },
  157. /**
  158. * Register an atomic operation with the parent.
  159. *
  160. * @param {Array} op operation
  161. * @param {any} val
  162. * @method _registerAtomic
  163. * @api private
  164. * @memberOf MongooseArray
  165. */
  166. _registerAtomic: function(op, val) {
  167. if (op === '$set') {
  168. // $set takes precedence over all other ops.
  169. // mark entire array modified.
  170. this[arrayAtomicsSymbol] = {$set: val};
  171. cleanModifiedSubpaths(this[arrayParentSymbol], this[arrayPathSymbol]);
  172. this._markModified();
  173. return this;
  174. }
  175. const atomics = this[arrayAtomicsSymbol];
  176. // reset pop/shift after save
  177. if (op === '$pop' && !('$pop' in atomics)) {
  178. const _this = this;
  179. this[arrayParentSymbol].once('save', function() {
  180. _this._popped = _this._shifted = null;
  181. });
  182. }
  183. // check for impossible $atomic combos (Mongo denies more than one
  184. // $atomic op on a single path
  185. if (this[arrayAtomicsSymbol].$set ||
  186. Object.keys(atomics).length && !(op in atomics)) {
  187. // a different op was previously registered.
  188. // save the entire thing.
  189. this[arrayAtomicsSymbol] = {$set: this};
  190. return this;
  191. }
  192. let selector;
  193. if (op === '$pullAll' || op === '$addToSet') {
  194. atomics[op] || (atomics[op] = []);
  195. atomics[op] = atomics[op].concat(val);
  196. } else if (op === '$pullDocs') {
  197. const pullOp = atomics['$pull'] || (atomics['$pull'] = {});
  198. if (val[0] instanceof EmbeddedDocument) {
  199. selector = pullOp['$or'] || (pullOp['$or'] = []);
  200. Array.prototype.push.apply(selector, val.map(function(v) {
  201. return v.toObject({transform: false, virtuals: false});
  202. }));
  203. } else {
  204. selector = pullOp['_id'] || (pullOp['_id'] = {$in: []});
  205. selector['$in'] = selector['$in'].concat(val);
  206. }
  207. } else if (op === '$push') {
  208. atomics.$push = atomics.$push || { $each: [] };
  209. atomics.$push.$each = atomics.$push.$each.concat(val);
  210. } else {
  211. atomics[op] = val;
  212. }
  213. return this;
  214. },
  215. /**
  216. * Depopulates stored atomic operation values as necessary for direct insertion to MongoDB.
  217. *
  218. * If no atomics exist, we return all array values after conversion.
  219. *
  220. * @return {Array}
  221. * @method $__getAtomics
  222. * @memberOf MongooseArray
  223. * @instance
  224. * @api private
  225. */
  226. $__getAtomics: function() {
  227. const ret = [];
  228. const keys = Object.keys(this[arrayAtomicsSymbol]);
  229. let i = keys.length;
  230. const opts = Object.assign({}, internalToObjectOptions, { _isNested: true });
  231. if (i === 0) {
  232. ret[0] = ['$set', this.toObject(opts)];
  233. return ret;
  234. }
  235. while (i--) {
  236. const op = keys[i];
  237. let val = this[arrayAtomicsSymbol][op];
  238. // the atomic values which are arrays are not MongooseArrays. we
  239. // need to convert their elements as if they were MongooseArrays
  240. // to handle populated arrays versus DocumentArrays properly.
  241. if (isMongooseObject(val)) {
  242. val = val.toObject(opts);
  243. } else if (Array.isArray(val)) {
  244. val = this.toObject.call(val, opts);
  245. } else if (val != null && Array.isArray(val.$each)) {
  246. val.$each = this.toObject.call(val.$each, opts);
  247. } else if (val != null && typeof val.valueOf === 'function') {
  248. val = val.valueOf();
  249. }
  250. if (op === '$addToSet') {
  251. val = {$each: val};
  252. }
  253. ret.push([op, val]);
  254. }
  255. return ret;
  256. },
  257. /**
  258. * Returns the number of pending atomic operations to send to the db for this array.
  259. *
  260. * @api private
  261. * @return {Number}
  262. * @method hasAtomics
  263. * @memberOf MongooseArray
  264. */
  265. hasAtomics: function hasAtomics() {
  266. if (!(this[arrayAtomicsSymbol] && this[arrayAtomicsSymbol].constructor.name === 'Object')) {
  267. return 0;
  268. }
  269. return Object.keys(this[arrayAtomicsSymbol]).length;
  270. },
  271. /**
  272. * Internal helper for .map()
  273. *
  274. * @api private
  275. * @return {Number}
  276. * @method _mapCast
  277. * @memberOf MongooseArray
  278. */
  279. _mapCast: function(val, index) {
  280. return this._cast(val, this.length + index);
  281. },
  282. /**
  283. * Wraps [`Array#push`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push) with proper change tracking.
  284. *
  285. * @param {Object} [args...]
  286. * @api public
  287. * @method push
  288. * @memberOf MongooseArray
  289. */
  290. push: function() {
  291. _checkManualPopulation(this, arguments);
  292. let values = [].map.call(arguments, this._mapCast, this);
  293. values = this[arraySchemaSymbol].applySetters(values, this[arrayParentSymbol], undefined,
  294. undefined, { skipDocumentArrayCast: true });
  295. const ret = [].push.apply(this, values);
  296. this._registerAtomic('$push', values);
  297. this._markModified();
  298. return ret;
  299. },
  300. /**
  301. * Pushes items to the array non-atomically.
  302. *
  303. * ####NOTE:
  304. *
  305. * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._
  306. *
  307. * @param {any} [args...]
  308. * @api public
  309. * @method nonAtomicPush
  310. * @memberOf MongooseArray
  311. */
  312. nonAtomicPush: function() {
  313. const values = [].map.call(arguments, this._mapCast, this);
  314. const ret = [].push.apply(this, values);
  315. this._registerAtomic('$set', this);
  316. this._markModified();
  317. return ret;
  318. },
  319. /**
  320. * Pops the array atomically at most one time per document `save()`.
  321. *
  322. * #### NOTE:
  323. *
  324. * _Calling this mulitple times on an array before saving sends the same command as calling it once._
  325. * _This update is implemented using the MongoDB [$pop](http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop) method which enforces this restriction._
  326. *
  327. * doc.array = [1,2,3];
  328. *
  329. * var popped = doc.array.$pop();
  330. * console.log(popped); // 3
  331. * console.log(doc.array); // [1,2]
  332. *
  333. * // no affect
  334. * popped = doc.array.$pop();
  335. * console.log(doc.array); // [1,2]
  336. *
  337. * doc.save(function (err) {
  338. * if (err) return handleError(err);
  339. *
  340. * // we saved, now $pop works again
  341. * popped = doc.array.$pop();
  342. * console.log(popped); // 2
  343. * console.log(doc.array); // [1]
  344. * })
  345. *
  346. * @api public
  347. * @method $pop
  348. * @memberOf MongooseArray
  349. * @instance
  350. * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop
  351. * @method $pop
  352. * @memberOf MongooseArray
  353. */
  354. $pop: function() {
  355. this._registerAtomic('$pop', 1);
  356. this._markModified();
  357. // only allow popping once
  358. if (this._popped) {
  359. return;
  360. }
  361. this._popped = true;
  362. return [].pop.call(this);
  363. },
  364. /**
  365. * Wraps [`Array#pop`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/pop) with proper change tracking.
  366. *
  367. * ####Note:
  368. *
  369. * _marks the entire array as modified which will pass the entire thing to $set potentially overwritting any changes that happen between when you retrieved the object and when you save it._
  370. *
  371. * @see MongooseArray#$pop #types_array_MongooseArray-%24pop
  372. * @api public
  373. * @method pop
  374. * @memberOf MongooseArray
  375. */
  376. pop: function() {
  377. const ret = [].pop.call(this);
  378. this._registerAtomic('$set', this);
  379. this._markModified();
  380. return ret;
  381. },
  382. /**
  383. * Atomically shifts the array at most one time per document `save()`.
  384. *
  385. * ####NOTE:
  386. *
  387. * _Calling this mulitple times on an array before saving sends the same command as calling it once._
  388. * _This update is implemented using the MongoDB [$pop](http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop) method which enforces this restriction._
  389. *
  390. * doc.array = [1,2,3];
  391. *
  392. * var shifted = doc.array.$shift();
  393. * console.log(shifted); // 1
  394. * console.log(doc.array); // [2,3]
  395. *
  396. * // no affect
  397. * shifted = doc.array.$shift();
  398. * console.log(doc.array); // [2,3]
  399. *
  400. * doc.save(function (err) {
  401. * if (err) return handleError(err);
  402. *
  403. * // we saved, now $shift works again
  404. * shifted = doc.array.$shift();
  405. * console.log(shifted ); // 2
  406. * console.log(doc.array); // [3]
  407. * })
  408. *
  409. * @api public
  410. * @memberOf MongooseArray
  411. * @instance
  412. * @method $shift
  413. * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop
  414. */
  415. $shift: function $shift() {
  416. this._registerAtomic('$pop', -1);
  417. this._markModified();
  418. // only allow shifting once
  419. if (this._shifted) {
  420. return;
  421. }
  422. this._shifted = true;
  423. return [].shift.call(this);
  424. },
  425. /**
  426. * Wraps [`Array#shift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking.
  427. *
  428. * ####Example:
  429. *
  430. * doc.array = [2,3];
  431. * var res = doc.array.shift();
  432. * console.log(res) // 2
  433. * console.log(doc.array) // [3]
  434. *
  435. * ####Note:
  436. *
  437. * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._
  438. *
  439. * @api public
  440. * @method shift
  441. * @memberOf MongooseArray
  442. */
  443. shift: function() {
  444. const ret = [].shift.call(this);
  445. this._registerAtomic('$set', this);
  446. this._markModified();
  447. return ret;
  448. },
  449. /**
  450. * Pulls items from the array atomically. Equality is determined by casting
  451. * the provided value to an embedded document and comparing using
  452. * [the `Document.equals()` function.](./api.html#document_Document-equals)
  453. *
  454. * ####Examples:
  455. *
  456. * doc.array.pull(ObjectId)
  457. * doc.array.pull({ _id: 'someId' })
  458. * doc.array.pull(36)
  459. * doc.array.pull('tag 1', 'tag 2')
  460. *
  461. * To remove a document from a subdocument array we may pass an object with a matching `_id`.
  462. *
  463. * doc.subdocs.push({ _id: 4815162342 })
  464. * doc.subdocs.pull({ _id: 4815162342 }) // removed
  465. *
  466. * Or we may passing the _id directly and let mongoose take care of it.
  467. *
  468. * doc.subdocs.push({ _id: 4815162342 })
  469. * doc.subdocs.pull(4815162342); // works
  470. *
  471. * The first pull call will result in a atomic operation on the database, if pull is called repeatedly without saving the document, a $set operation is used on the complete array instead, overwriting possible changes that happened on the database in the meantime.
  472. *
  473. * @param {any} [args...]
  474. * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull
  475. * @api public
  476. * @method pull
  477. * @memberOf MongooseArray
  478. */
  479. pull: function() {
  480. const values = [].map.call(arguments, this._cast, this);
  481. const cur = this[arrayParentSymbol].get(this[arrayPathSymbol]);
  482. let i = cur.length;
  483. let mem;
  484. while (i--) {
  485. mem = cur[i];
  486. if (mem instanceof Document) {
  487. const some = values.some(function(v) {
  488. return mem.equals(v);
  489. });
  490. if (some) {
  491. [].splice.call(cur, i, 1);
  492. }
  493. } else if (~cur.indexOf.call(values, mem)) {
  494. [].splice.call(cur, i, 1);
  495. }
  496. }
  497. if (values[0] instanceof EmbeddedDocument) {
  498. this._registerAtomic('$pullDocs', values.map(function(v) {
  499. return v._id || v;
  500. }));
  501. } else {
  502. this._registerAtomic('$pullAll', values);
  503. }
  504. this._markModified();
  505. // Might have modified child paths and then pulled, like
  506. // `doc.children[1].name = 'test';` followed by
  507. // `doc.children.remove(doc.children[0]);`. In this case we fall back
  508. // to a `$set` on the whole array. See #3511
  509. if (cleanModifiedSubpaths(this[arrayParentSymbol], this[arrayPathSymbol]) > 0) {
  510. this._registerAtomic('$set', this);
  511. }
  512. return this;
  513. },
  514. /**
  515. * Wraps [`Array#splice`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice) with proper change tracking and casting.
  516. *
  517. * ####Note:
  518. *
  519. * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._
  520. *
  521. * @api public
  522. * @method splice
  523. * @memberOf MongooseArray
  524. */
  525. splice: function splice() {
  526. let ret;
  527. _checkManualPopulation(this, Array.prototype.slice.call(arguments, 2));
  528. if (arguments.length) {
  529. const vals = [];
  530. for (let i = 0; i < arguments.length; ++i) {
  531. vals[i] = i < 2 ?
  532. arguments[i] :
  533. this._cast(arguments[i], arguments[0] + (i - 2));
  534. }
  535. ret = [].splice.apply(this, vals);
  536. this._registerAtomic('$set', this);
  537. }
  538. return ret;
  539. },
  540. /**
  541. * Wraps [`Array#unshift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking.
  542. *
  543. * ####Note:
  544. *
  545. * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._
  546. *
  547. * @api public
  548. * @method unshift
  549. * @memberOf MongooseArray
  550. */
  551. unshift: function() {
  552. _checkManualPopulation(this, arguments);
  553. let values = [].map.call(arguments, this._cast, this);
  554. values = this[arraySchemaSymbol].applySetters(values, this[arrayParentSymbol]);
  555. [].unshift.apply(this, values);
  556. this._registerAtomic('$set', this);
  557. this._markModified();
  558. return this.length;
  559. },
  560. /**
  561. * Wraps [`Array#sort`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort) with proper change tracking.
  562. *
  563. * ####NOTE:
  564. *
  565. * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._
  566. *
  567. * @api public
  568. * @method sort
  569. * @memberOf MongooseArray
  570. */
  571. sort: function() {
  572. const ret = [].sort.apply(this, arguments);
  573. this._registerAtomic('$set', this);
  574. return ret;
  575. },
  576. /**
  577. * Adds values to the array if not already present.
  578. *
  579. * ####Example:
  580. *
  581. * console.log(doc.array) // [2,3,4]
  582. * var added = doc.array.addToSet(4,5);
  583. * console.log(doc.array) // [2,3,4,5]
  584. * console.log(added) // [5]
  585. *
  586. * @param {any} [args...]
  587. * @return {Array} the values that were added
  588. * @memberOf MongooseArray
  589. * @api public
  590. * @method addToSet
  591. */
  592. addToSet: function addToSet() {
  593. _checkManualPopulation(this, arguments);
  594. let values = [].map.call(arguments, this._mapCast, this);
  595. values = this[arraySchemaSymbol].applySetters(values, this[arrayParentSymbol]);
  596. const added = [];
  597. let type = '';
  598. if (values[0] instanceof EmbeddedDocument) {
  599. type = 'doc';
  600. } else if (values[0] instanceof Date) {
  601. type = 'date';
  602. }
  603. values.forEach(function(v) {
  604. let found;
  605. const val = +v;
  606. switch (type) {
  607. case 'doc':
  608. found = this.some(function(doc) {
  609. return doc.equals(v);
  610. });
  611. break;
  612. case 'date':
  613. found = this.some(function(d) {
  614. return +d === val;
  615. });
  616. break;
  617. default:
  618. found = ~this.indexOf(v);
  619. }
  620. if (!found) {
  621. [].push.call(this, v);
  622. this._registerAtomic('$addToSet', v);
  623. this._markModified();
  624. [].push.call(added, v);
  625. }
  626. }, this);
  627. return added;
  628. },
  629. /**
  630. * Sets the casted `val` at index `i` and marks the array modified.
  631. *
  632. * ####Example:
  633. *
  634. * // given documents based on the following
  635. * var Doc = mongoose.model('Doc', new Schema({ array: [Number] }));
  636. *
  637. * var doc = new Doc({ array: [2,3,4] })
  638. *
  639. * console.log(doc.array) // [2,3,4]
  640. *
  641. * doc.array.set(1,"5");
  642. * console.log(doc.array); // [2,5,4] // properly cast to number
  643. * doc.save() // the change is saved
  644. *
  645. * // VS not using array#set
  646. * doc.array[1] = "5";
  647. * console.log(doc.array); // [2,"5",4] // no casting
  648. * doc.save() // change is not saved
  649. *
  650. * @return {Array} this
  651. * @api public
  652. * @method set
  653. * @memberOf MongooseArray
  654. */
  655. set: function set(i, val) {
  656. const value = this._cast(val, i);
  657. this[i] = value;
  658. this._markModified(i);
  659. return this;
  660. },
  661. /**
  662. * Returns a native js Array.
  663. *
  664. * @param {Object} options
  665. * @return {Array}
  666. * @api public
  667. * @method toObject
  668. * @memberOf MongooseArray
  669. */
  670. toObject: function(options) {
  671. if (options && options.depopulate) {
  672. options = utils.clone(options);
  673. options._isNested = true;
  674. return this.map(function(doc) {
  675. return doc instanceof Document
  676. ? doc.toObject(options)
  677. : doc;
  678. });
  679. }
  680. return this.slice();
  681. },
  682. /**
  683. * Helper for console.log
  684. *
  685. * @api public
  686. * @method inspect
  687. * @memberOf MongooseArray
  688. */
  689. inspect: function() {
  690. return JSON.stringify(this);
  691. },
  692. /**
  693. * Return the index of `obj` or `-1` if not found.
  694. *
  695. * @param {Object} obj the item to look for
  696. * @return {Number}
  697. * @api public
  698. * @method indexOf
  699. * @memberOf MongooseArray
  700. */
  701. indexOf: function indexOf(obj) {
  702. if (obj instanceof ObjectId) {
  703. obj = obj.toString();
  704. }
  705. for (let i = 0, len = this.length; i < len; ++i) {
  706. if (obj == this[i]) {
  707. return i;
  708. }
  709. }
  710. return -1;
  711. },
  712. /**
  713. * Return whether or not the `obj` is included in the array.
  714. *
  715. * @param {Object} obj the item to check
  716. * @return {Boolean}
  717. * @api public
  718. * @method includes
  719. * @memberOf MongooseArray
  720. */
  721. includes: function includes(obj) {
  722. return this.indexOf(obj) !== -1;
  723. }
  724. };
  725. /**
  726. * Alias of [pull](#types_array_MongooseArray-pull)
  727. *
  728. * @see MongooseArray#pull #types_array_MongooseArray-pull
  729. * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull
  730. * @api public
  731. * @memberOf MongooseArray
  732. * @instance
  733. * @method remove
  734. */
  735. MongooseArray.mixin.remove = MongooseArray.mixin.pull;
  736. /*!
  737. * ignore
  738. */
  739. function _isAllSubdocs(docs, ref) {
  740. if (!ref) {
  741. return false;
  742. }
  743. for (let i = 0; i < docs.length; ++i) {
  744. const arg = docs[i];
  745. if (arg == null) {
  746. return false;
  747. }
  748. const model = arg.constructor;
  749. if (!(arg instanceof Document) ||
  750. (model.modelName !== ref && model.baseModelName !== ref)) {
  751. return false;
  752. }
  753. }
  754. return true;
  755. }
  756. /*!
  757. * ignore
  758. */
  759. function _checkManualPopulation(arr, docs) {
  760. const ref = arr == null ?
  761. null :
  762. get(arr[arraySchemaSymbol], 'caster.options.ref', null);
  763. if (arr.length === 0 &&
  764. docs.length > 0) {
  765. if (_isAllSubdocs(docs, ref)) {
  766. arr[arrayParentSymbol].populated(arr[arrayPathSymbol], [], { model: docs[0].constructor });
  767. }
  768. }
  769. }
  770. /*!
  771. * Module exports.
  772. */
  773. module.exports = exports = MongooseArray;