123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960 |
- 'use strict';
- const Document = require('../../../document');
- const ArraySubdocument = require('../../ArraySubdocument');
- const MongooseError = require('../../../error/mongooseError');
- const cleanModifiedSubpaths = require('../../../helpers/document/cleanModifiedSubpaths');
- const internalToObjectOptions = require('../../../options').internalToObjectOptions;
- const utils = require('../../../utils');
- const isBsonType = require('../../../helpers/isBsonType');
- const arrayAtomicsSymbol = require('../../../helpers/symbols').arrayAtomicsSymbol;
- const arrayParentSymbol = require('../../../helpers/symbols').arrayParentSymbol;
- const arrayPathSymbol = require('../../../helpers/symbols').arrayPathSymbol;
- const arraySchemaSymbol = require('../../../helpers/symbols').arraySchemaSymbol;
- const populateModelSymbol = require('../../../helpers/symbols').populateModelSymbol;
- const slicedSymbol = Symbol('mongoose#Array#sliced');
- const _basePush = Array.prototype.push;
- /*!
- * ignore
- */
- const methods = {
- /**
- * Depopulates stored atomic operation values as necessary for direct insertion to MongoDB.
- *
- * If no atomics exist, we return all array values after conversion.
- *
- * @return {Array}
- * @method $__getAtomics
- * @memberOf MongooseArray
- * @instance
- * @api private
- */
- $__getAtomics() {
- const ret = [];
- const keys = Object.keys(this[arrayAtomicsSymbol] || {});
- let i = keys.length;
- const opts = Object.assign({}, internalToObjectOptions, { _isNested: true });
- if (i === 0) {
- ret[0] = ['$set', this.toObject(opts)];
- return ret;
- }
- while (i--) {
- const op = keys[i];
- let val = this[arrayAtomicsSymbol][op];
- // the atomic values which are arrays are not MongooseArrays. we
- // need to convert their elements as if they were MongooseArrays
- // to handle populated arrays versus DocumentArrays properly.
- if (utils.isMongooseObject(val)) {
- val = val.toObject(opts);
- } else if (Array.isArray(val)) {
- val = this.toObject.call(val, opts);
- } else if (val != null && Array.isArray(val.$each)) {
- val.$each = this.toObject.call(val.$each, opts);
- } else if (val != null && typeof val.valueOf === 'function') {
- val = val.valueOf();
- }
- if (op === '$addToSet') {
- val = { $each: val };
- }
- ret.push([op, val]);
- }
- return ret;
- },
- /*!
- * ignore
- */
- $atomics() {
- return this[arrayAtomicsSymbol];
- },
- /*!
- * ignore
- */
- $parent() {
- return this[arrayParentSymbol];
- },
- /*!
- * ignore
- */
- $path() {
- return this[arrayPathSymbol];
- },
- /**
- * Atomically shifts the array at most one time per document `save()`.
- *
- * #### Note:
- *
- * _Calling this multiple times on an array before saving sends the same command as calling it once._
- * _This update is implemented using the MongoDB [$pop](https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop) method which enforces this restriction._
- *
- * doc.array = [1,2,3];
- *
- * const shifted = doc.array.$shift();
- * console.log(shifted); // 1
- * console.log(doc.array); // [2,3]
- *
- * // no affect
- * shifted = doc.array.$shift();
- * console.log(doc.array); // [2,3]
- *
- * doc.save(function (err) {
- * if (err) return handleError(err);
- *
- * // we saved, now $shift works again
- * shifted = doc.array.$shift();
- * console.log(shifted ); // 2
- * console.log(doc.array); // [3]
- * })
- *
- * @api public
- * @memberOf MongooseArray
- * @instance
- * @method $shift
- * @see mongodb https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop
- */
- $shift() {
- this._registerAtomic('$pop', -1);
- this._markModified();
- // only allow shifting once
- if (this._shifted) {
- return;
- }
- this._shifted = true;
- return [].shift.call(this);
- },
- /**
- * Pops the array atomically at most one time per document `save()`.
- *
- * #### NOTE:
- *
- * _Calling this multiple times on an array before saving sends the same command as calling it once._
- * _This update is implemented using the MongoDB [$pop](https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop) method which enforces this restriction._
- *
- * doc.array = [1,2,3];
- *
- * const popped = doc.array.$pop();
- * console.log(popped); // 3
- * console.log(doc.array); // [1,2]
- *
- * // no affect
- * popped = doc.array.$pop();
- * console.log(doc.array); // [1,2]
- *
- * doc.save(function (err) {
- * if (err) return handleError(err);
- *
- * // we saved, now $pop works again
- * popped = doc.array.$pop();
- * console.log(popped); // 2
- * console.log(doc.array); // [1]
- * })
- *
- * @api public
- * @method $pop
- * @memberOf MongooseArray
- * @instance
- * @see mongodb https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop
- * @method $pop
- * @memberOf MongooseArray
- */
- $pop() {
- this._registerAtomic('$pop', 1);
- this._markModified();
- // only allow popping once
- if (this._popped) {
- return;
- }
- this._popped = true;
- return [].pop.call(this);
- },
- /*!
- * ignore
- */
- $schema() {
- return this[arraySchemaSymbol];
- },
- /**
- * Casts a member based on this arrays schema.
- *
- * @param {any} value
- * @return value the casted value
- * @method _cast
- * @api private
- * @memberOf MongooseArray
- */
- _cast(value) {
- let populated = false;
- let Model;
- const parent = this[arrayParentSymbol];
- if (parent) {
- populated = parent.$populated(this[arrayPathSymbol], true);
- }
- if (populated && value !== null && value !== undefined) {
- // cast to the populated Models schema
- Model = populated.options[populateModelSymbol];
- // only objects are permitted so we can safely assume that
- // non-objects are to be interpreted as _id
- if (Buffer.isBuffer(value) ||
- isBsonType(value, 'ObjectID') || !utils.isObject(value)) {
- value = { _id: value };
- }
- // gh-2399
- // we should cast model only when it's not a discriminator
- const isDisc = value.schema && value.schema.discriminatorMapping &&
- value.schema.discriminatorMapping.key !== undefined;
- if (!isDisc) {
- value = new Model(value);
- }
- return this[arraySchemaSymbol].caster.applySetters(value, parent, true);
- }
- return this[arraySchemaSymbol].caster.applySetters(value, parent, false);
- },
- /**
- * Internal helper for .map()
- *
- * @api private
- * @return {Number}
- * @method _mapCast
- * @memberOf MongooseArray
- */
- _mapCast(val, index) {
- return this._cast(val, this.length + index);
- },
- /**
- * Marks this array as modified.
- *
- * If it bubbles up from an embedded document change, then it takes the following arguments (otherwise, takes 0 arguments)
- *
- * @param {ArraySubdocument} subdoc the embedded doc that invoked this method on the Array
- * @param {String} embeddedPath the path which changed in the subdoc
- * @method _markModified
- * @api private
- * @memberOf MongooseArray
- */
- _markModified(elem) {
- const parent = this[arrayParentSymbol];
- let dirtyPath;
- if (parent) {
- dirtyPath = this[arrayPathSymbol];
- if (arguments.length) {
- dirtyPath = dirtyPath + '.' + elem;
- }
- if (dirtyPath != null && dirtyPath.endsWith('.$')) {
- return this;
- }
- parent.markModified(dirtyPath, arguments.length !== 0 ? elem : parent);
- }
- return this;
- },
- /**
- * Register an atomic operation with the parent.
- *
- * @param {Array} op operation
- * @param {any} val
- * @method _registerAtomic
- * @api private
- * @memberOf MongooseArray
- */
- _registerAtomic(op, val) {
- if (this[slicedSymbol]) {
- return;
- }
- if (op === '$set') {
- // $set takes precedence over all other ops.
- // mark entire array modified.
- this[arrayAtomicsSymbol] = { $set: val };
- cleanModifiedSubpaths(this[arrayParentSymbol], this[arrayPathSymbol]);
- this._markModified();
- return this;
- }
- const atomics = this[arrayAtomicsSymbol];
- // reset pop/shift after save
- if (op === '$pop' && !('$pop' in atomics)) {
- const _this = this;
- this[arrayParentSymbol].once('save', function() {
- _this._popped = _this._shifted = null;
- });
- }
- // check for impossible $atomic combos (Mongo denies more than one
- // $atomic op on a single path
- if (atomics.$set || Object.keys(atomics).length && !(op in atomics)) {
- // a different op was previously registered.
- // save the entire thing.
- this[arrayAtomicsSymbol] = { $set: this };
- return this;
- }
- let selector;
- if (op === '$pullAll' || op === '$addToSet') {
- atomics[op] || (atomics[op] = []);
- atomics[op] = atomics[op].concat(val);
- } else if (op === '$pullDocs') {
- const pullOp = atomics['$pull'] || (atomics['$pull'] = {});
- if (val[0] instanceof ArraySubdocument) {
- selector = pullOp['$or'] || (pullOp['$or'] = []);
- Array.prototype.push.apply(selector, val.map(function(v) {
- return v.toObject({ transform: false, virtuals: false });
- }));
- } else {
- selector = pullOp['_id'] || (pullOp['_id'] = { $in: [] });
- selector['$in'] = selector['$in'].concat(val);
- }
- } else if (op === '$push') {
- atomics.$push = atomics.$push || { $each: [] };
- if (val != null && utils.hasUserDefinedProperty(val, '$each')) {
- atomics.$push = val;
- } else {
- atomics.$push.$each = atomics.$push.$each.concat(val);
- }
- } else {
- atomics[op] = val;
- }
- return this;
- },
- /**
- * Adds values to the array if not already present.
- *
- * #### Example:
- *
- * console.log(doc.array) // [2,3,4]
- * const added = doc.array.addToSet(4,5);
- * console.log(doc.array) // [2,3,4,5]
- * console.log(added) // [5]
- *
- * @param {any} [args...]
- * @return {Array} the values that were added
- * @memberOf MongooseArray
- * @api public
- * @method addToSet
- */
- addToSet() {
- _checkManualPopulation(this, arguments);
- let values = [].map.call(arguments, this._mapCast, this);
- values = this[arraySchemaSymbol].applySetters(values, this[arrayParentSymbol]);
- const added = [];
- let type = '';
- if (values[0] instanceof ArraySubdocument) {
- type = 'doc';
- } else if (values[0] instanceof Date) {
- type = 'date';
- }
- const rawValues = utils.isMongooseArray(values) ? values.__array : this;
- const rawArray = utils.isMongooseArray(this) ? this.__array : this;
- rawValues.forEach(function(v) {
- let found;
- const val = +v;
- switch (type) {
- case 'doc':
- found = this.some(function(doc) {
- return doc.equals(v);
- });
- break;
- case 'date':
- found = this.some(function(d) {
- return +d === val;
- });
- break;
- default:
- found = ~this.indexOf(v);
- }
- if (!found) {
- this._markModified();
- rawArray.push(v);
- this._registerAtomic('$addToSet', v);
- [].push.call(added, v);
- }
- }, this);
- return added;
- },
- /**
- * Returns the number of pending atomic operations to send to the db for this array.
- *
- * @api private
- * @return {Number}
- * @method hasAtomics
- * @memberOf MongooseArray
- */
- hasAtomics() {
- if (!utils.isPOJO(this[arrayAtomicsSymbol])) {
- return 0;
- }
- return Object.keys(this[arrayAtomicsSymbol]).length;
- },
- /**
- * Return whether or not the `obj` is included in the array.
- *
- * @param {Object} obj the item to check
- * @param {Number} fromIndex
- * @return {Boolean}
- * @api public
- * @method includes
- * @memberOf MongooseArray
- */
- includes(obj, fromIndex) {
- const ret = this.indexOf(obj, fromIndex);
- return ret !== -1;
- },
- /**
- * Return the index of `obj` or `-1` if not found.
- *
- * @param {Object} obj the item to look for
- * @param {Number} fromIndex
- * @return {Number}
- * @api public
- * @method indexOf
- * @memberOf MongooseArray
- */
- indexOf(obj, fromIndex) {
- if (isBsonType(obj, 'ObjectID')) {
- obj = obj.toString();
- }
- fromIndex = fromIndex == null ? 0 : fromIndex;
- const len = this.length;
- for (let i = fromIndex; i < len; ++i) {
- if (obj == this[i]) {
- return i;
- }
- }
- return -1;
- },
- /**
- * Helper for console.log
- *
- * @api public
- * @method inspect
- * @memberOf MongooseArray
- */
- inspect() {
- return JSON.stringify(this);
- },
- /**
- * Pushes items to the array non-atomically.
- *
- * #### Note:
- *
- * _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._
- *
- * @param {any} [args...]
- * @api public
- * @method nonAtomicPush
- * @memberOf MongooseArray
- */
- nonAtomicPush() {
- const values = [].map.call(arguments, this._mapCast, this);
- this._markModified();
- const ret = [].push.apply(this, values);
- this._registerAtomic('$set', this);
- return ret;
- },
- /**
- * Wraps [`Array#pop`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/pop) with proper change tracking.
- *
- * #### Note:
- *
- * _marks the entire array as modified which will pass the entire thing to $set potentially overwriting any changes that happen between when you retrieved the object and when you save it._
- *
- * @see MongooseArray#$pop #types_array_MongooseArray-%24pop
- * @api public
- * @method pop
- * @memberOf MongooseArray
- */
- pop() {
- this._markModified();
- const ret = [].pop.call(this);
- this._registerAtomic('$set', this);
- return ret;
- },
- /**
- * Pulls items from the array atomically. Equality is determined by casting
- * the provided value to an embedded document and comparing using
- * [the `Document.equals()` function.](/docs/api.html#document_Document-equals)
- *
- * #### Examples:
- *
- * doc.array.pull(ObjectId)
- * doc.array.pull({ _id: 'someId' })
- * doc.array.pull(36)
- * doc.array.pull('tag 1', 'tag 2')
- *
- * To remove a document from a subdocument array we may pass an object with a matching `_id`.
- *
- * doc.subdocs.push({ _id: 4815162342 })
- * doc.subdocs.pull({ _id: 4815162342 }) // removed
- *
- * Or we may passing the _id directly and let mongoose take care of it.
- *
- * doc.subdocs.push({ _id: 4815162342 })
- * doc.subdocs.pull(4815162342); // works
- *
- * 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.
- *
- * @param {any} [args...]
- * @see mongodb https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull
- * @api public
- * @method pull
- * @memberOf MongooseArray
- */
- pull() {
- const values = [].map.call(arguments, this._cast, this);
- const cur = this[arrayParentSymbol].get(this[arrayPathSymbol]);
- let i = cur.length;
- let mem;
- this._markModified();
- while (i--) {
- mem = cur[i];
- if (mem instanceof Document) {
- const some = values.some(function(v) {
- return mem.equals(v);
- });
- if (some) {
- [].splice.call(cur, i, 1);
- }
- } else if (~cur.indexOf.call(values, mem)) {
- [].splice.call(cur, i, 1);
- }
- }
- if (values[0] instanceof ArraySubdocument) {
- this._registerAtomic('$pullDocs', values.map(function(v) {
- return v.$__getValue('_id') || v;
- }));
- } else {
- this._registerAtomic('$pullAll', values);
- }
- // Might have modified child paths and then pulled, like
- // `doc.children[1].name = 'test';` followed by
- // `doc.children.remove(doc.children[0]);`. In this case we fall back
- // to a `$set` on the whole array. See #3511
- if (cleanModifiedSubpaths(this[arrayParentSymbol], this[arrayPathSymbol]) > 0) {
- this._registerAtomic('$set', this);
- }
- return this;
- },
- /**
- * Wraps [`Array#push`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push) with proper change tracking.
- *
- * #### Example:
- *
- * const schema = Schema({ nums: [Number] });
- * const Model = mongoose.model('Test', schema);
- *
- * const doc = await Model.create({ nums: [3, 4] });
- * doc.nums.push(5); // Add 5 to the end of the array
- * await doc.save();
- *
- * // You can also pass an object with `$each` as the
- * // first parameter to use MongoDB's `$position`
- * doc.nums.push({
- * $each: [1, 2],
- * $position: 0
- * });
- * doc.nums; // [1, 2, 3, 4, 5]
- *
- * @param {Object} [args...]
- * @api public
- * @method push
- * @memberOf MongooseArray
- */
- push() {
- let values = arguments;
- let atomic = values;
- const isOverwrite = values[0] != null &&
- utils.hasUserDefinedProperty(values[0], '$each');
- const arr = utils.isMongooseArray(this) ? this.__array : this;
- if (isOverwrite) {
- atomic = values[0];
- values = values[0].$each;
- }
- if (this[arraySchemaSymbol] == null) {
- return _basePush.apply(this, values);
- }
- _checkManualPopulation(this, values);
- const parent = this[arrayParentSymbol];
- values = [].map.call(values, this._mapCast, this);
- values = this[arraySchemaSymbol].applySetters(values, parent, undefined,
- undefined, { skipDocumentArrayCast: true });
- let ret;
- const atomics = this[arrayAtomicsSymbol];
- this._markModified();
- if (isOverwrite) {
- atomic.$each = values;
- if ((atomics.$push && atomics.$push.$each && atomics.$push.$each.length || 0) !== 0 &&
- atomics.$push.$position != atomic.$position) {
- throw new MongooseError('Cannot call `Array#push()` multiple times ' +
- 'with different `$position`');
- }
- if (atomic.$position != null) {
- [].splice.apply(arr, [atomic.$position, 0].concat(values));
- ret = this.length;
- } else {
- ret = [].push.apply(arr, values);
- }
- } else {
- if ((atomics.$push && atomics.$push.$each && atomics.$push.$each.length || 0) !== 0 &&
- atomics.$push.$position != null) {
- throw new MongooseError('Cannot call `Array#push()` multiple times ' +
- 'with different `$position`');
- }
- atomic = values;
- ret = [].push.apply(arr, values);
- }
- this._registerAtomic('$push', atomic);
- return ret;
- },
- /**
- * Alias of [pull](#mongoosearray_MongooseArray-pull)
- *
- * @see MongooseArray#pull #types_array_MongooseArray-pull
- * @see mongodb https://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull
- * @api public
- * @memberOf MongooseArray
- * @instance
- * @method remove
- */
- remove() {
- return this.pull.apply(this, arguments);
- },
- /**
- * Sets the casted `val` at index `i` and marks the array modified.
- *
- * #### Example:
- *
- * // given documents based on the following
- * const Doc = mongoose.model('Doc', new Schema({ array: [Number] }));
- *
- * const doc = new Doc({ array: [2,3,4] })
- *
- * console.log(doc.array) // [2,3,4]
- *
- * doc.array.set(1,"5");
- * console.log(doc.array); // [2,5,4] // properly cast to number
- * doc.save() // the change is saved
- *
- * // VS not using array#set
- * doc.array[1] = "5";
- * console.log(doc.array); // [2,"5",4] // no casting
- * doc.save() // change is not saved
- *
- * @return {Array} this
- * @api public
- * @method set
- * @memberOf MongooseArray
- */
- set(i, val, skipModified) {
- const arr = this.__array;
- if (skipModified) {
- arr[i] = val;
- return this;
- }
- const value = methods._cast.call(this, val, i);
- methods._markModified.call(this, i);
- arr[i] = value;
- return this;
- },
- /**
- * Wraps [`Array#shift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking.
- *
- * #### Example:
- *
- * doc.array = [2,3];
- * const res = doc.array.shift();
- * console.log(res) // 2
- * console.log(doc.array) // [3]
- *
- * #### Note:
- *
- * _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._
- *
- * @api public
- * @method shift
- * @memberOf MongooseArray
- */
- shift() {
- const arr = utils.isMongooseArray(this) ? this.__array : this;
- this._markModified();
- const ret = [].shift.call(arr);
- this._registerAtomic('$set', this);
- return ret;
- },
- /**
- * Wraps [`Array#sort`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort) with proper change tracking.
- *
- * #### Note:
- *
- * _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._
- *
- * @api public
- * @method sort
- * @memberOf MongooseArray
- * @see https://masteringjs.io/tutorials/fundamentals/array-sort
- */
- sort() {
- const arr = utils.isMongooseArray(this) ? this.__array : this;
- const ret = [].sort.apply(arr, arguments);
- this._registerAtomic('$set', this);
- return ret;
- },
- /**
- * Wraps [`Array#splice`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice) with proper change tracking and casting.
- *
- * #### Note:
- *
- * _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._
- *
- * @api public
- * @method splice
- * @memberOf MongooseArray
- * @see https://masteringjs.io/tutorials/fundamentals/array-splice
- */
- splice() {
- let ret;
- const arr = utils.isMongooseArray(this) ? this.__array : this;
- _checkManualPopulation(this, Array.prototype.slice.call(arguments, 2));
- if (arguments.length) {
- let vals;
- if (this[arraySchemaSymbol] == null) {
- vals = arguments;
- } else {
- vals = [];
- for (let i = 0; i < arguments.length; ++i) {
- vals[i] = i < 2 ?
- arguments[i] :
- this._cast(arguments[i], arguments[0] + (i - 2));
- }
- }
- ret = [].splice.apply(arr, vals);
- this._registerAtomic('$set', this);
- }
- return ret;
- },
- /*!
- * ignore
- */
- toBSON() {
- return this.toObject(internalToObjectOptions);
- },
- /**
- * Returns a native js Array.
- *
- * @param {Object} options
- * @return {Array}
- * @api public
- * @method toObject
- * @memberOf MongooseArray
- */
- toObject(options) {
- const arr = utils.isMongooseArray(this) ? this.__array : this;
- if (options && options.depopulate) {
- options = utils.clone(options);
- options._isNested = true;
- // Ensure return value is a vanilla array, because in Node.js 6+ `map()`
- // is smart enough to use the inherited array's constructor.
- return [].concat(arr).map(function(doc) {
- return doc instanceof Document
- ? doc.toObject(options)
- : doc;
- });
- }
- return [].concat(arr);
- },
- $toObject() {
- return this.constructor.prototype.toObject.apply(this, arguments);
- },
- /**
- * Wraps [`Array#unshift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking.
- *
- * #### Note:
- *
- * _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._
- *
- * @api public
- * @method unshift
- * @memberOf MongooseArray
- */
- unshift() {
- _checkManualPopulation(this, arguments);
- let values;
- if (this[arraySchemaSymbol] == null) {
- values = arguments;
- } else {
- values = [].map.call(arguments, this._cast, this);
- values = this[arraySchemaSymbol].applySetters(values, this[arrayParentSymbol]);
- }
- const arr = utils.isMongooseArray(this) ? this.__array : this;
- this._markModified();
- [].unshift.apply(arr, values);
- this._registerAtomic('$set', this);
- return this.length;
- }
- };
- /*!
- * ignore
- */
- function _isAllSubdocs(docs, ref) {
- if (!ref) {
- return false;
- }
- for (const arg of docs) {
- if (arg == null) {
- return false;
- }
- const model = arg.constructor;
- if (!(arg instanceof Document) ||
- (model.modelName !== ref && model.baseModelName !== ref)) {
- return false;
- }
- }
- return true;
- }
- /*!
- * ignore
- */
- function _checkManualPopulation(arr, docs) {
- const ref = arr == null ?
- null :
- arr[arraySchemaSymbol] && arr[arraySchemaSymbol].caster && arr[arraySchemaSymbol].caster.options && arr[arraySchemaSymbol].caster.options.ref || null;
- if (arr.length === 0 &&
- docs.length !== 0) {
- if (_isAllSubdocs(docs, ref)) {
- arr[arrayParentSymbol].$populated(arr[arrayPathSymbol], [], {
- [populateModelSymbol]: docs[0].constructor
- });
- }
- }
- }
- const returnVanillaArrayMethods = [
- 'filter',
- 'flat',
- 'flatMap',
- 'map',
- 'slice'
- ];
- for (const method of returnVanillaArrayMethods) {
- if (Array.prototype[method] == null) {
- continue;
- }
- methods[method] = function() {
- const _arr = utils.isMongooseArray(this) ? this.__array : this;
- const arr = [].concat(_arr);
- return arr[method].apply(arr, arguments);
- };
- }
- module.exports = methods;
|