UNPKG

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