mongoose 5.0.13 → 5.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/History.md CHANGED
@@ -1,3 +1,46 @@
1
+ 5.0.17 / 2018-04-30
2
+ ===================
3
+ * docs(migration): certain chars in passwords may cause connection failures #6401 [markstos](https://github.com/markstos)
4
+ * fix(document): don't throw when `push()` on a nested doc array #6398
5
+ * fix(model): apply hooks to custom methods if specified #6385
6
+ * fix(schema): support opting out of one timestamp field but not the other for `insertMany()` #6381
7
+ * fix(documentarray): handle `required: true` within documentarray definition #6364
8
+ * fix(document): ensure `isNew` is set before default functions run on init #3793
9
+
10
+ 5.0.16 / 2018-04-23
11
+ ===================
12
+ * docs(api): sort api methods based on their string property #6374 [lineus](https://github.com/lineus)
13
+ * docs(connection): fix typo in `createCollection()` #6370 [mattc41190](https://github.com/mattc41190)
14
+ * docs(document): remove vestigial reference to `numAffected` #6367 [ekulabuhov](https://github.com/ekulabuhov)
15
+ * docs(schema): fix typo #6366 [dhritzkiv](https://github.com/dhritzkiv)
16
+ * docs(schematypes): add missing `minlength` and `maxlength` docs #6365 [treble-snake](https://github.com/treble-snake)
17
+ * docs(queries): fix formatting #6360 [treble-snake](https://github.com/treble-snake)
18
+ * docs(api): add cursors to API docs #6353 #6344 [lineus](https://github.com/lineus)
19
+ * docs(aggregate): remove reference to non-existent `.select()` method #6346
20
+ * fix(update): handle `required` array with update validators and $pull #6341
21
+ * fix(update): avoid setting __v in findOneAndUpdate if it is `$set` #5973
22
+
23
+ 5.0.15 / 2018-04-16
24
+ ===================
25
+ * fix: add ability for casting from number to decimal128 #6336 #6331 [lineus](https://github.com/lineus)
26
+ * docs(middleware): enumerate the ways to error out in a hook #6315
27
+ * fix(document): respect schema-level depopulate option for toObject() #6313
28
+ * fix: bump mongodb driver -> 3.0.6 #6310
29
+ * fix(number): check for `valueOf()` function to support Decimal.js #6306 #6299 [lineus](https://github.com/lineus)
30
+ * fix(query): run array setters on query if input value is an array #6277
31
+ * fix(versioning): don't require matching version when using array.pull() #6190
32
+ * fix(document): add `toHexString()` function so you don't need to check whether a path is populated to get an id #6115
33
+
34
+ 5.0.14 / 2018-04-09
35
+ ===================
36
+ * fix(schema): clone aliases and alternative option syntax correctly
37
+ * fix(query): call utils.toObject in query.count like in query.find #6325 [lineus](https://github.com/lineus)
38
+ * docs(populate): add middleware examples #6320 [BorntraegerMarc](https://github.com/BorntraegerMarc)
39
+ * docs(compatibility): fix dead link #6319 [lacivert](https://github.com/lacivert)
40
+ * docs(api): fix markdown parsing for parameters #6318 #6314 [lineus](https://github.com/lineus)
41
+ * fix(populate): handle space-delimited paths in array populate #6296 #6284 [lineus](https://github.com/lineus)
42
+ * fix(populate): support basic virtual populate underneath embedded discriminators #6273
43
+
1
44
  5.0.13 / 2018-04-05
2
45
  ===================
3
46
  * docs(faq): add middleware to faq arrow function warning #6309 [lineus](https://github.com/lineus)
package/lib/connection.js CHANGED
@@ -243,7 +243,7 @@ Connection.prototype.config;
243
243
  * Options are passed down without modification to the [MongoDB driver's `createCollection()` function](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
244
244
  *
245
245
  * @method createCollection
246
- * @param {string} collection The collection to delete
246
+ * @param {string} collection The collection to create
247
247
  * @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
248
248
  * @param {Function} [callback]
249
249
  * @return {Promise}
package/lib/document.js CHANGED
@@ -4,34 +4,37 @@
4
4
  * Module dependencies.
5
5
  */
6
6
 
7
- var EventEmitter = require('events').EventEmitter;
8
- var MongooseError = require('./error');
9
- var MixedSchema = require('./schema/mixed');
10
- var Schema = require('./schema');
11
- var ObjectExpectedError = require('./error/objectExpected');
12
- var ObjectParameterError = require('./error/objectParameter');
13
- var StrictModeError = require('./error/strict');
14
- var ValidatorError = require('./schematype').ValidatorError;
15
- var VirtualType = require('./virtualtype');
16
- var utils = require('./utils');
17
- var clone = utils.clone;
18
- var isDefiningProjection = require('./services/projection/isDefiningProjection');
19
- var isExclusive = require('./services/projection/isExclusive');
20
- var isMongooseObject = utils.isMongooseObject;
21
- var inspect = require('util').inspect;
22
- var internalToObjectOptions = require('./options').internalToObjectOptions;
23
- var ValidationError = MongooseError.ValidationError;
24
- var InternalCache = require('./internal');
25
- var cleanModifiedSubpaths = require('./services/document/cleanModifiedSubpaths');
26
- var compile = require('./services/document/compile').compile;
27
- var deepEqual = utils.deepEqual;
28
- var defineKey = require('./services/document/compile').defineKey;
29
- var DocumentArray;
30
- var MongooseArray;
31
- var Embedded;
32
- var flatten = require('./services/common').flatten;
33
- var mpath = require('mpath');
34
- var idGetter = require('./plugins/idGetter');
7
+ const EventEmitter = require('events').EventEmitter;
8
+ const InternalCache = require('./internal');
9
+ const MongooseError = require('./error');
10
+ const MixedSchema = require('./schema/mixed');
11
+ const Schema = require('./schema');
12
+ const ObjectExpectedError = require('./error/objectExpected');
13
+ const ObjectParameterError = require('./error/objectParameter');
14
+ const StrictModeError = require('./error/strict');
15
+ const ValidatorError = require('./schematype').ValidatorError;
16
+ const VirtualType = require('./virtualtype');
17
+ const cleanModifiedSubpaths = require('./services/document/cleanModifiedSubpaths');
18
+ const compile = require('./services/document/compile').compile;
19
+ const defineKey = require('./services/document/compile').defineKey;
20
+ const flatten = require('./services/common').flatten;
21
+ const get = require('lodash.get');
22
+ const idGetter = require('./plugins/idGetter');
23
+ const isDefiningProjection = require('./services/projection/isDefiningProjection');
24
+ const isExclusive = require('./services/projection/isExclusive');
25
+ const inspect = require('util').inspect;
26
+ const internalToObjectOptions = require('./options').internalToObjectOptions;
27
+ const mpath = require('mpath');
28
+ const utils = require('./utils');
29
+
30
+ const ValidationError = MongooseError.ValidationError;
31
+ const clone = utils.clone;
32
+ const deepEqual = utils.deepEqual;
33
+ const isMongooseObject = utils.isMongooseObject;
34
+
35
+ let DocumentArray;
36
+ let MongooseArray;
37
+ let Embedded;
35
38
 
36
39
  /**
37
40
  * Document constructor.
@@ -46,9 +49,15 @@ var idGetter = require('./plugins/idGetter');
46
49
  */
47
50
 
48
51
  function Document(obj, fields, skipId, options) {
52
+ if (typeof skipId === 'object' && skipId != null) {
53
+ options = skipId;
54
+ skipId = options.skipId;
55
+ }
56
+ options = options || {};
57
+
49
58
  this.$__ = new InternalCache;
50
59
  this.$__.emitter = new EventEmitter();
51
- this.isNew = true;
60
+ this.isNew = 'isNew' in options ? options.isNew : true;
52
61
  this.errors = undefined;
53
62
  this.$__.$options = options || {};
54
63
 
@@ -1513,7 +1522,12 @@ function _getPathsToValidate(doc) {
1513
1522
  var path = paths[i];
1514
1523
 
1515
1524
  var _pathType = doc.schema.path(path);
1516
- if (!_pathType || !_pathType.$isMongooseArray || _pathType.$isMongooseDocumentArray) {
1525
+ if (!_pathType ||
1526
+ !_pathType.$isMongooseArray ||
1527
+ // To avoid potential performance issues, skip doc arrays whose children
1528
+ // are not required. `getPositionalPathType()` may be slow, so avoid
1529
+ // it unless we have a case of #6364
1530
+ (_pathType.$isMongooseDocumentArray && !get(_pathType, 'schemaOptions.required'))) {
1517
1531
  continue;
1518
1532
  }
1519
1533
 
@@ -1802,7 +1816,7 @@ Document.prototype.$markValid = function(path) {
1802
1816
  * ####Example:
1803
1817
  *
1804
1818
  * product.sold = Date.now();
1805
- * product.save(function (err, product, numAffected) {
1819
+ * product.save(function (err, product) {
1806
1820
  * if (err) ..
1807
1821
  * })
1808
1822
  *
@@ -1810,7 +1824,6 @@ Document.prototype.$markValid = function(path) {
1810
1824
  *
1811
1825
  * 1. `err` if an error occurred
1812
1826
  * 2. `product` which is the saved `product`
1813
- * 3. `numAffected` will be 1 when the document was successfully persisted to MongoDB, otherwise 0. Unless you tweak mongoose's internals, you don't need to worry about checking this parameter for errors - checking `err` is sufficient to make sure your document was properly saved.
1814
1827
  *
1815
1828
  * As an extra measure of flow control, save will return a Promise.
1816
1829
  * ####Example:
@@ -2138,15 +2151,21 @@ Document.prototype.$toObject = function(options, json) {
2138
2151
  _minimize = this.schema.options.minimize;
2139
2152
  }
2140
2153
 
2154
+ // The original options that will be passed to `clone()`. Important because
2155
+ // `clone()` will recursively call `$toObject()` on embedded docs, so we
2156
+ // need the original options the user passed in, plus `_isNested` and
2157
+ // `_parentOptions` for checking whether we need to depopulate.
2141
2158
  const cloneOptions = Object.assign(utils.clone(options), {
2142
2159
  _isNested: true,
2143
2160
  json: json,
2144
2161
  minimize: _minimize
2145
2162
  });
2146
2163
 
2164
+ const depopulate = options.depopulate ||
2165
+ get(options, '_parentOptions.depopulate', false);
2147
2166
  // _isNested will only be true if this is not the top level document, we
2148
2167
  // should never depopulate
2149
- if (options.depopulate && options._isNested && this.$__.wasPopulated) {
2168
+ if (depopulate && options._isNested && this.$__.wasPopulated) {
2150
2169
  // populated paths that we set to a document
2151
2170
  return clone(this._id, cloneOptions);
2152
2171
  }
@@ -2157,6 +2176,8 @@ Document.prototype.$toObject = function(options, json) {
2157
2176
  options.json = json;
2158
2177
  options.minimize = _minimize;
2159
2178
 
2179
+ cloneOptions._parentOptions = options;
2180
+
2160
2181
  // remember the root transform function
2161
2182
  // to save it from being overwritten by sub-transform functions
2162
2183
  var originalTransform = options.transform;
package/lib/model.js CHANGED
@@ -27,6 +27,8 @@ var internalToObjectOptions = require('./options').internalToObjectOptions;
27
27
  var isPathSelectedInclusive = require('./services/projection/isPathSelectedInclusive');
28
28
  var get = require('lodash.get');
29
29
  var getSchemaTypes = require('./services/populate/getSchemaTypes');
30
+ var getVirtual = require('./services/populate/getVirtual');
31
+ var modifiedPaths = require('./services/update/modifiedPaths');
30
32
  var mpath = require('mpath');
31
33
  var parallel = require('async/parallel');
32
34
  var parallelLimit = require('async/parallelLimit');
@@ -57,7 +59,7 @@ function Model(doc, fields, skipId) {
57
59
  '**not** a schema. Make sure you\'re calling `mongoose.model()`, not ' +
58
60
  '`mongoose.Model()`.');
59
61
  }
60
- Document.call(this, doc, fields, skipId, true);
62
+ Document.call(this, doc, fields, skipId);
61
63
  }
62
64
 
63
65
  /*!
@@ -408,8 +410,7 @@ function operand(self, where, delta, data, val, op) {
408
410
  // editing the correct array element.
409
411
  // only increment the version if an array position changes.
410
412
  // modifying elements of an array is ok if position does not change.
411
-
412
- if (op === '$push' || op === '$addToSet') {
413
+ if (op === '$push' || op === '$addToSet' || op === '$pullAll' || op === '$pull') {
413
414
  self.$__.version = VERSION_INC;
414
415
  } else if (/^\$p/.test(op)) {
415
416
  // potentially changing array positions
@@ -732,7 +733,7 @@ Model.prototype.$__where = function _where(where) {
732
733
  * })
733
734
  *
734
735
  *
735
- * As an extra measure of flow control, remove will return a Promise (bound to `fn` if passed) so it could be chained, or hooked to recive errors
736
+ * As an extra measure of flow control, remove will return a Promise (bound to `fn` if passed) so it could be chained, or hooked to recieve errors
736
737
  *
737
738
  * ####Example:
738
739
  * product.remove().then(function (product) {
@@ -1724,13 +1725,16 @@ Model.findOneAndUpdate = function(conditions, update, options, callback) {
1724
1725
 
1725
1726
  update = utils.clone(update, {depopulate: 1, _isNested: true});
1726
1727
  if (this.schema.options.versionKey && options && options.upsert) {
1727
- if (options.overwrite) {
1728
- update[this.schema.options.versionKey] = 0;
1729
- } else {
1730
- if (!update.$setOnInsert) {
1731
- update.$setOnInsert = {};
1728
+ var updatedPaths = modifiedPaths(update);
1729
+ if (!updatedPaths[this.schema.options.versionKey]) {
1730
+ if (options.overwrite) {
1731
+ update[this.schema.options.versionKey] = 0;
1732
+ } else {
1733
+ if (!update.$setOnInsert) {
1734
+ update.$setOnInsert = {};
1735
+ }
1736
+ update.$setOnInsert[this.schema.options.versionKey] = 0;
1732
1737
  }
1733
- update.$setOnInsert[this.schema.options.versionKey] = 0;
1734
1738
  }
1735
1739
  }
1736
1740
 
@@ -2617,14 +2621,15 @@ function _update(model, op, conditions, doc, options, callback) {
2617
2621
  }
2618
2622
  options = typeof options === 'function' ? options : utils.clone(options);
2619
2623
 
2620
- if (model.schema.options.versionKey && options && options.upsert) {
2624
+ var versionKey = get(model, 'schema.options.versionKey', null);
2625
+ if (versionKey && options && options.upsert) {
2621
2626
  if (options.overwrite) {
2622
- doc[model.schema.options.versionKey] = 0;
2627
+ doc[versionKey] = 0;
2623
2628
  } else {
2624
2629
  if (!doc.$setOnInsert) {
2625
2630
  doc.$setOnInsert = {};
2626
2631
  }
2627
- doc.$setOnInsert[model.schema.options.versionKey] = 0;
2632
+ doc.$setOnInsert[versionKey] = 0;
2628
2633
  }
2629
2634
  }
2630
2635
 
@@ -2767,10 +2772,10 @@ Model.mapReduce = function mapReduce(o, callback) {
2767
2772
  * });
2768
2773
  *
2769
2774
  * // Or use the aggregation pipeline builder.
2770
- * Users.aggregate()
2771
- * .group({ _id: null, maxBalance: { $max: '$balance' } })
2772
- * .select('-id maxBalance')
2773
- * .exec(function (err, res) {
2775
+ * Users.aggregate().
2776
+ * group({ _id: null, maxBalance: { $max: '$balance' } }).
2777
+ * project('-id maxBalance').
2778
+ * exec(function (err, res) {
2774
2779
  * if (err) return handleError(err);
2775
2780
  * console.log(res); // [ { maxBalance: 98 } ]
2776
2781
  * });
@@ -3026,8 +3031,8 @@ function _populate(model, docs, paths, cache, callback) {
3026
3031
  /*!
3027
3032
  * Populates `docs`
3028
3033
  */
3029
- var excludeIdReg = /\s?-_id\s?/,
3030
- excludeIdRegGlobal = /\s?-_id\s?/g;
3034
+ const excludeIdReg = /\s?-_id\s?/;
3035
+ const excludeIdRegGlobal = /\s?-_id\s?/g;
3031
3036
 
3032
3037
  function populate(model, docs, options, callback) {
3033
3038
  var modelsMap;
@@ -3157,12 +3162,7 @@ function populate(model, docs, options, callback) {
3157
3162
  var val;
3158
3163
 
3159
3164
  // Clone because `assignRawDocsToIdStructure` will mutate the array
3160
- var allIds = [].concat(mod.allIds.map(function(v) {
3161
- if (Array.isArray(v)) {
3162
- return [].concat(v);
3163
- }
3164
- return v;
3165
- }));
3165
+ var allIds = utils.clone(mod.allIds);
3166
3166
 
3167
3167
  // optimization:
3168
3168
  // record the document positions as returned by
@@ -3224,7 +3224,8 @@ function populate(model, docs, options, callback) {
3224
3224
 
3225
3225
  assignVals({
3226
3226
  originalModel: model,
3227
- rawIds: mod.allIds,
3227
+ // If virtual, make sure to not mutate original field
3228
+ rawIds: mod.isVirtual ? allIds : mod.allIds,
3228
3229
  allIds: allIds,
3229
3230
  localField: mod.localField,
3230
3231
  foreignField: mod.foreignField,
@@ -3253,7 +3254,6 @@ function assignVals(o) {
3253
3254
 
3254
3255
  // now update the original documents being populated using the
3255
3256
  // result structure that contains real documents.
3256
-
3257
3257
  var docs = o.docs;
3258
3258
  var rawIds = o.rawIds;
3259
3259
  var options = o.options;
@@ -3264,7 +3264,7 @@ function assignVals(o) {
3264
3264
 
3265
3265
  for (var i = 0; i < docs.length; ++i) {
3266
3266
  if (utils.getValue(o.path, docs[i]) == null &&
3267
- !o.originalModel.schema._getVirtual(o.path)) {
3267
+ !getVirtual(o.originalModel.schema, o.path)) {
3268
3268
  continue;
3269
3269
  }
3270
3270
 
@@ -3294,7 +3294,7 @@ function assignVals(o) {
3294
3294
  if (docs[i].$__) {
3295
3295
  docs[i].populated(o.path, o.allIds[i], o.allOptions);
3296
3296
  }
3297
- utils.setValue(o.path, rawIds[i], docs[i], setValue);
3297
+ utils.setValue(o.path, rawIds[i], docs[i], setValue, false);
3298
3298
  }
3299
3299
  }
3300
3300
  }
@@ -3383,11 +3383,20 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, lo
3383
3383
  }
3384
3384
 
3385
3385
  function getModelsMapForPopulate(model, docs, options) {
3386
- var i, doc, len = docs.length,
3387
- available = {},
3388
- map = [],
3389
- modelNameFromQuery = options.model && options.model.modelName || options.model,
3390
- schema, refPath, Model, currentOptions, modelNames, modelName, discriminatorKey, modelForFindSchema;
3386
+ let i;
3387
+ let doc;
3388
+ let len = docs.length;
3389
+ let available = {};
3390
+ let map = [];
3391
+ let modelNameFromQuery = options.model && options.model.modelName || options.model;
3392
+ let schema;
3393
+ let refPath;
3394
+ let Model;
3395
+ let currentOptions;
3396
+ let modelNames;
3397
+ let modelName;
3398
+ let discriminatorKey;
3399
+ let modelForFindSchema;
3391
3400
 
3392
3401
  var originalModel = options.model;
3393
3402
  var isVirtual = false;
@@ -3398,23 +3407,21 @@ function getModelsMapForPopulate(model, docs, options) {
3398
3407
  doc = docs[i];
3399
3408
 
3400
3409
  schema = getSchemaTypes(modelSchema, doc, options.path);
3401
- var isUnderneathDocArray = schema && schema.$isUnderneathDocArray;
3402
- if (isUnderneathDocArray &&
3403
- options &&
3404
- options.options &&
3405
- options.options.sort) {
3410
+ const isUnderneathDocArray = schema && schema.$isUnderneathDocArray;
3411
+ if (isUnderneathDocArray && get(options, 'options.sort') != null) {
3406
3412
  return new Error('Cannot populate with `sort` on path ' + options.path +
3407
3413
  ' because it is a subproperty of a document array');
3408
3414
  }
3409
3415
 
3416
+ modelNames = null;
3410
3417
  if (Array.isArray(schema)) {
3411
- for (var j = 0; j < schema.length; ++j) {
3418
+ for (let j = 0; j < schema.length; ++j) {
3412
3419
  var _modelNames = _getModelNames(doc, schema[j]);
3413
3420
  if (!_modelNames) {
3414
3421
  continue;
3415
3422
  }
3416
- modelNames = (modelNames || []);
3417
- for (var x = 0; x < _modelNames.length; ++x) {
3423
+ modelNames = modelNames || [];
3424
+ for (let x = 0; x < _modelNames.length; ++x) {
3418
3425
  if (modelNames.indexOf(_modelNames[x]) === -1) {
3419
3426
  modelNames.push(_modelNames[x]);
3420
3427
  }
@@ -3427,10 +3434,10 @@ function getModelsMapForPopulate(model, docs, options) {
3427
3434
  }
3428
3435
  }
3429
3436
 
3430
- var virtual = model.schema._getVirtual(options.path);
3431
- var localField;
3437
+ let virtual = getVirtual(model.schema, options.path);
3438
+ let localField;
3432
3439
  if (virtual && virtual.options) {
3433
- var virtualPrefix = virtual.$nestedSchemaPath ?
3440
+ let virtualPrefix = virtual.$nestedSchemaPath ?
3434
3441
  virtual.$nestedSchemaPath + '.' : '';
3435
3442
  if (typeof virtual.options.localField === 'function') {
3436
3443
  localField = virtualPrefix + virtual.options.localField.call(doc, doc);
@@ -3440,17 +3447,24 @@ function getModelsMapForPopulate(model, docs, options) {
3440
3447
  } else {
3441
3448
  localField = options.path;
3442
3449
  }
3443
- var foreignField = virtual && virtual.options ?
3450
+ let foreignField = virtual && virtual.options ?
3444
3451
  virtual.options.foreignField :
3445
3452
  '_id';
3446
- var justOne = true;
3453
+ let justOne = true;
3447
3454
  if (virtual && virtual.options && virtual.options.ref) {
3448
3455
  justOne = virtual.options.justOne;
3449
3456
  isVirtual = true;
3457
+ if (!modelNames) {
3458
+ modelNames = [virtual.options.ref];
3459
+ }
3450
3460
  } else if (schema) {
3451
3461
  justOne = !schema.$isMongooseArray;
3452
3462
  }
3453
3463
 
3464
+ if (!modelNames) {
3465
+ continue;
3466
+ }
3467
+
3454
3468
  if (virtual && (!localField || !foreignField)) {
3455
3469
  throw new Error('If you are populating a virtual, you must set the ' +
3456
3470
  'localField and foreignField options');
@@ -3463,11 +3477,11 @@ function getModelsMapForPopulate(model, docs, options) {
3463
3477
  if (typeof foreignField === 'function') {
3464
3478
  foreignField = foreignField.call(doc);
3465
3479
  }
3466
- var ret = convertTo_id(utils.getValue(localField, doc));
3467
- var id = String(utils.getValue(foreignField, doc));
3480
+ const ret = convertTo_id(utils.getValue(localField, doc));
3481
+ const id = String(utils.getValue(foreignField, doc));
3468
3482
  options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
3469
3483
 
3470
- var k = modelNames.length;
3484
+ let k = modelNames.length;
3471
3485
  while (k--) {
3472
3486
  modelName = modelNames[k];
3473
3487
  if (modelName == null) {
@@ -3559,7 +3573,7 @@ function getModelsMapForPopulate(model, docs, options) {
3559
3573
  } else {
3560
3574
  schemaForCurrentDoc = schema;
3561
3575
  }
3562
- var virtual = modelForCurrentDoc.schema._getVirtual(options.path);
3576
+ var virtual = getVirtual(modelForCurrentDoc.schema, options.path);
3563
3577
 
3564
3578
  var ref;
3565
3579
  if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) {
@@ -7,10 +7,24 @@
7
7
  module.exports = function(schema) {
8
8
  // ensure the documents receive an id getter unless disabled
9
9
  var autoIdGetter = !schema.paths['id'] &&
10
- (!schema.options.noVirtualId && schema.options.id);
11
- if (autoIdGetter) {
12
- schema.virtual('id').get(idGetter);
10
+ (!schema.options.noVirtualId && schema.options.id);
11
+ if (!autoIdGetter) {
12
+ return;
13
13
  }
14
+
15
+ schema.virtual('id').get(idGetter);
16
+
17
+ if ('toHexString' in schema.methods) {
18
+ return;
19
+ }
20
+
21
+ // Re: gh-6115, make it easy to get something usable as an ObjectID regardless
22
+ // of whether a property is populated or not. With this, you can do
23
+ // `blogPost.author.toHexString()` and get a hex string regardless of whether
24
+ // `author` is populated
25
+ schema.methods.toHexString = function() {
26
+ return this.id;
27
+ };
14
28
  };
15
29
 
16
30
  /*!
package/lib/query.js CHANGED
@@ -1,23 +1,26 @@
1
+ 'use strict';
2
+
1
3
  /*!
2
4
  * Module dependencies.
3
5
  */
4
6
 
5
- var CastError = require('./error/cast');
6
- var ObjectParameterError = require('./error/objectParameter');
7
- var QueryCursor = require('./cursor/QueryCursor');
8
- var ReadPreference = require('./drivers').ReadPreference;
9
- var cast = require('./cast');
10
- var castUpdate = require('./services/query/castUpdate');
11
- var hasDollarKeys = require('./services/query/hasDollarKeys');
12
- var helpers = require('./queryhelpers');
13
- var isInclusive = require('./services/projection/isInclusive');
14
- var mquery = require('mquery');
15
- var selectPopulatedFields = require('./services/query/selectPopulatedFields');
16
- var setDefaultsOnInsert = require('./services/setDefaultsOnInsert');
17
- var slice = require('sliced');
18
- var updateValidators = require('./services/updateValidators');
19
- var util = require('util');
20
- var utils = require('./utils');
7
+ const CastError = require('./error/cast');
8
+ const ObjectParameterError = require('./error/objectParameter');
9
+ const QueryCursor = require('./cursor/QueryCursor');
10
+ const ReadPreference = require('./drivers').ReadPreference;
11
+ const cast = require('./cast');
12
+ const castUpdate = require('./services/query/castUpdate');
13
+ const get = require('lodash.get');
14
+ const hasDollarKeys = require('./services/query/hasDollarKeys');
15
+ const helpers = require('./queryhelpers');
16
+ const isInclusive = require('./services/projection/isInclusive');
17
+ const mquery = require('mquery');
18
+ const selectPopulatedFields = require('./services/query/selectPopulatedFields');
19
+ const setDefaultsOnInsert = require('./services/setDefaultsOnInsert');
20
+ const slice = require('sliced');
21
+ const updateValidators = require('./services/updateValidators');
22
+ const util = require('util');
23
+ const utils = require('./utils');
21
24
 
22
25
  /**
23
26
  * Query constructor used for building queries. You do not need
@@ -80,8 +83,9 @@ function Query(conditions, options, model, collection) {
80
83
  }
81
84
 
82
85
  this.options = this.options || {};
83
- if (this.schema != null && this.schema.options.collation != null) {
84
- this.options.collation = this.schema.options.collation;
86
+ const collation = get(this, 'schema.options.collation', null);
87
+ if (collation != null) {
88
+ this.options.collation = collation;
85
89
  }
86
90
  }
87
91
 
@@ -1163,12 +1167,14 @@ Query.prototype._optionsForExec = function(model) {
1163
1167
  return options;
1164
1168
  }
1165
1169
 
1166
- if (!('safe' in options) && model.schema.options.safe) {
1167
- options.safe = model.schema.options.safe;
1170
+ const safe = get(model, 'schema.options.safe', null);
1171
+ if (!('safe' in options) && safe != null) {
1172
+ options.safe = safe;
1168
1173
  }
1169
1174
 
1170
- if (!('readPreference' in options) && model.schema.options.read) {
1171
- options.readPreference = model.schema.options.read;
1175
+ const readPreference = get(model, 'schema.options.read');
1176
+ if (!('readPreference' in options) && readPreference) {
1177
+ options.readPreference = readPreference;
1172
1178
  }
1173
1179
 
1174
1180
  if (options.upsert !== void 0) {
@@ -1675,6 +1681,8 @@ Query.prototype.count = function(conditions, callback) {
1675
1681
  conditions = undefined;
1676
1682
  }
1677
1683
 
1684
+ conditions = utils.toObject(conditions);
1685
+
1678
1686
  if (mquery.canMerge(conditions)) {
1679
1687
  this.merge(conditions);
1680
1688
  }
@@ -3209,9 +3217,9 @@ Query.prototype.cast = function(model, obj) {
3209
3217
  upsert: this.options && this.options.upsert,
3210
3218
  strict: (this.options && 'strict' in this.options) ?
3211
3219
  this.options.strict :
3212
- (model.schema.options && model.schema.options.strict),
3220
+ get(model, 'schema.options.strict', null),
3213
3221
  strictQuery: (this.options && this.options.strictQuery) ||
3214
- (model.schema.options && model.schema.options.strictQuery)
3222
+ get(model, 'schema.options.strictQuery', null)
3215
3223
  }, this);
3216
3224
  } catch (err) {
3217
3225
  // CastError, assign model
@@ -103,7 +103,7 @@ exports.createModel = function createModel(model, doc, fields, userProvidedField
103
103
  }
104
104
  }
105
105
 
106
- return new model(undefined, fields, true);
106
+ return new model(undefined, fields, { skipId: true, isNew: false });
107
107
  };
108
108
 
109
109
  /*!
@@ -70,6 +70,7 @@ function SchemaArray(key, cast, options, schemaOptions) {
70
70
  : cast;
71
71
 
72
72
  this.casterConstructor = caster;
73
+
73
74
  if (typeof caster === 'function' && !caster.$isArraySubdocument) {
74
75
  this.caster = new caster(null, castOptions);
75
76
  } else {
@@ -140,20 +141,6 @@ SchemaArray.prototype.enum = function() {
140
141
  return this;
141
142
  };
142
143
 
143
- /**
144
- * Check if the given value satisfies a `required` validator. The given value
145
- * must be an array (that is, not `null` or `undefined`).
146
- * Empty arrays will **not** make `required` validator fail.
147
- *
148
- * @param {Any} value
149
- * @return {Boolean}
150
- * @api public
151
- */
152
-
153
- SchemaArray.prototype.checkRequired = function(value) {
154
- return Array.isArray(value);
155
- };
156
-
157
144
  /**
158
145
  * Overrides the getters application for the population special-case
159
146
  *
@@ -273,6 +260,9 @@ SchemaArray.prototype.castForQuery = function($conditional, value) {
273
260
  var caster = this.caster;
274
261
 
275
262
  if (Array.isArray(val)) {
263
+ this.setters.reverse().forEach(setter => {
264
+ val = setter.call(this, val, this);
265
+ });
276
266
  val = val.map(function(v) {
277
267
  if (utils.isObject(v) && v.$elemMatch) {
278
268
  return v;
@@ -124,6 +124,10 @@ Decimal128.prototype.cast = function(value, doc, init) {
124
124
  return new Decimal128Type(value);
125
125
  }
126
126
 
127
+ if (typeof value === 'number') {
128
+ return Decimal128Type.fromString(String(value));
129
+ }
130
+
127
131
  throw new CastError('Decimal128', value, this.path);
128
132
  };
129
133
 
@@ -1,4 +1,4 @@
1
- /* eslint no-empty: 1 */
1
+ 'use strict';
2
2
 
3
3
  /*!
4
4
  * Module dependencies.
@@ -26,15 +26,16 @@ var Subdocument;
26
26
  * @api public
27
27
  */
28
28
 
29
- function DocumentArray(key, schema, options) {
30
- var EmbeddedDocument = _createConstructor(schema, options);
29
+ function DocumentArray(key, schema, options, schemaOptions) {
30
+ const EmbeddedDocument = _createConstructor(schema, options);
31
31
  EmbeddedDocument.prototype.$basePath = key;
32
32
 
33
33
  ArrayType.call(this, key, EmbeddedDocument, options);
34
34
 
35
35
  this.schema = schema;
36
+ this.schemaOptions = schemaOptions || {};
36
37
  this.$isMongooseDocumentArray = true;
37
- var fn = this.defaultValue;
38
+ const fn = this.defaultValue;
38
39
 
39
40
  if (!('defaultValue' in this) || fn !== void 0) {
40
41
  this.default(function() {
@@ -169,7 +170,7 @@ DocumentArray.prototype.doValidate = function(array, fn, scope, options) {
169
170
  for (var i = 0, len = count; i < len; ++i) {
170
171
  // sidestep sparse entries
171
172
  var doc = array[i];
172
- if (!doc) {
173
+ if (doc == null) {
173
174
  --count || fn(error);
174
175
  continue;
175
176
  }
@@ -223,6 +223,9 @@ SchemaNumber.prototype.cast = function(value, doc, init) {
223
223
  if (typeof val === 'number') {
224
224
  return val;
225
225
  }
226
+ if (!Array.isArray(val) && typeof val.valueOf === 'function') {
227
+ return Number(val.valueOf());
228
+ }
226
229
  if (val.toString && !Array.isArray(val) && val.toString() == Number(val)) {
227
230
  return new Number(val);
228
231
  }
package/lib/schema.js CHANGED
@@ -9,6 +9,7 @@ var utils = require('./utils');
9
9
  var MongooseTypes;
10
10
  var Kareem = require('kareem');
11
11
  var SchemaType = require('./schematype');
12
+ var get = require('lodash.get');
12
13
  var mpath = require('mpath');
13
14
 
14
15
  /**
@@ -235,6 +236,7 @@ Schema.prototype.tree;
235
236
  Schema.prototype.clone = function() {
236
237
  var s = new Schema({}, this._userProvidedOptions);
237
238
  s.obj = this.obj;
239
+ s.options = utils.clone(this.options);
238
240
  s.callQueue = this.callQueue.map(function(f) { return f; });
239
241
  s.methods = utils.clone(this.methods);
240
242
  s.statics = utils.clone(this.statics);
@@ -248,12 +250,24 @@ Schema.prototype.clone = function() {
248
250
  s.nested = utils.clone(this.nested);
249
251
  s.subpaths = utils.clone(this.subpaths);
250
252
  s.childSchemas = this.childSchemas.slice();
253
+ s.singleNestedPaths = utils.clone(this.singleNestedPaths);
251
254
 
252
255
  s.virtuals = utils.clone(this.virtuals);
253
256
  s.$globalPluginsApplied = this.$globalPluginsApplied;
254
257
  s.$isRootDiscriminator = this.$isRootDiscriminator;
255
- s.discriminatorMapping = Object.assign({}, this.discriminatorMapping);
256
- s.discriminators = Object.assign({}, this.discriminators);
258
+
259
+ if (this.discriminatorMapping != null) {
260
+ s.discriminatorMapping = Object.assign({}, this.discriminatorMapping);
261
+ }
262
+ if (s.discriminators != null) {
263
+ s.discriminators = Object.assign({}, this.discriminators);
264
+ }
265
+
266
+ s.aliases = Object.assign({}, this.aliases);
267
+
268
+ // Bubble up `init` for backwards compat
269
+ s.on('init', v => this.emit('init', v));
270
+
257
271
  return s;
258
272
  };
259
273
 
@@ -540,7 +554,7 @@ Schema.interpretAsType = function(path, obj, options) {
540
554
  if (cast &&
541
555
  cast[options.typeKey] &&
542
556
  cast[options.typeKey].instanceOfSchema) {
543
- return new MongooseTypes.DocumentArray(path, cast[options.typeKey], cast);
557
+ return new MongooseTypes.DocumentArray(path, cast[options.typeKey], obj, cast);
544
558
  }
545
559
 
546
560
  if (Array.isArray(cast)) {
@@ -762,11 +776,11 @@ Schema.prototype.setupTimestamp = function(timestamps) {
762
776
  var defaultTimestamp = new Date();
763
777
  var auto_id = this._id && this._id.auto;
764
778
 
765
- if (createdAt != null && !this.get(createdAt) && this.isSelected(createdAt)) {
779
+ if (createdAt && !this.get(createdAt) && this.isSelected(createdAt)) {
766
780
  this.set(createdAt, auto_id ? this._id.getTimestamp() : defaultTimestamp);
767
781
  }
768
782
 
769
- if (updatedAt != null && (this.isNew || this.isModified())) {
783
+ if (updatedAt && (this.isNew || this.isModified())) {
770
784
  var ts = defaultTimestamp;
771
785
  if (this.isNew) {
772
786
  if (createdAt != null) {
@@ -791,10 +805,10 @@ Schema.prototype.setupTimestamp = function(timestamps) {
791
805
  updates.$set = {};
792
806
  _updates = updates.$set;
793
807
  }
794
- if (updatedAt != null && !currentUpdate[updatedAt]) {
808
+ if (updatedAt && !currentUpdate[updatedAt]) {
795
809
  _updates[updatedAt] = now;
796
810
  }
797
- if (createdAt != null && !currentUpdate[createdAt]) {
811
+ if (createdAt && !currentUpdate[createdAt]) {
798
812
  _updates[createdAt] = now;
799
813
  }
800
814
  return updates;
@@ -802,12 +816,12 @@ Schema.prototype.setupTimestamp = function(timestamps) {
802
816
  updates = { $set: {} };
803
817
  currentUpdate = currentUpdate || {};
804
818
 
805
- if (updatedAt != null &&
819
+ if (updatedAt &&
806
820
  (!currentUpdate.$currentDate || !currentUpdate.$currentDate[updatedAt])) {
807
821
  updates.$set[updatedAt] = now;
808
822
  }
809
823
 
810
- if (createdAt != null) {
824
+ if (createdAt) {
811
825
  if (currentUpdate[createdAt]) {
812
826
  delete currentUpdate[createdAt];
813
827
  }
@@ -823,10 +837,10 @@ Schema.prototype.setupTimestamp = function(timestamps) {
823
837
  };
824
838
 
825
839
  this.methods.initializeTimestamps = function() {
826
- if (!this.get(createdAt)) {
840
+ if (createdAt && !this.get(createdAt)) {
827
841
  this.set(createdAt, new Date());
828
842
  }
829
- if (!this.get(updatedAt)) {
843
+ if (updatedAt && !this.get(updatedAt)) {
830
844
  this.set(updatedAt, new Date());
831
845
  }
832
846
  return this;
@@ -980,7 +994,9 @@ function getPositionalPathType(self, path) {
980
994
  if (i === last && val && !/\D/.test(subpath)) {
981
995
  if (val.$isMongooseDocumentArray) {
982
996
  var oldVal = val;
983
- val = new SchemaType(subpath);
997
+ val = new SchemaType(subpath, {
998
+ required: get(val, 'schemaOptions.required', false)
999
+ });
984
1000
  val.cast = function(value, doc, init) {
985
1001
  return oldVal.cast(value, doc, init)[0];
986
1002
  };
@@ -1341,7 +1357,8 @@ Schema.prototype.indexes = function() {
1341
1357
  path = schema.paths[key];
1342
1358
 
1343
1359
  if ((path instanceof MongooseTypes.DocumentArray) || path.$isSingleNested) {
1344
- if (path.options.excludeIndexes !== true) {
1360
+ if (get(path, 'options.excludeIndexes') !== true &&
1361
+ get(path, 'schemaOptions.excludeIndexes') !== true) {
1345
1362
  collectIndexes(path.schema, prefix + key + '.');
1346
1363
  }
1347
1364
 
@@ -1526,43 +1543,6 @@ Schema.prototype.virtual = function(name, options) {
1526
1543
  return virtuals[name];
1527
1544
  };
1528
1545
 
1529
- /*!
1530
- * ignore
1531
- */
1532
-
1533
- Schema.prototype._getVirtual = function(name) {
1534
- return _getVirtual(this, name);
1535
- };
1536
-
1537
- /*!
1538
- * ignore
1539
- */
1540
-
1541
- function _getVirtual(schema, name) {
1542
- if (schema.virtuals[name]) {
1543
- return schema.virtuals[name];
1544
- }
1545
- var parts = name.split('.');
1546
- var cur = '';
1547
- var nestedSchemaPath = '';
1548
- for (var i = 0; i < parts.length; ++i) {
1549
- cur += (cur.length > 0 ? '.' : '') + parts[i];
1550
- if (schema.virtuals[cur]) {
1551
- if (i === parts.length - 1) {
1552
- schema.virtuals[cur].$nestedSchemaPath = nestedSchemaPath;
1553
- return schema.virtuals[cur];
1554
- }
1555
- continue;
1556
- } else if (schema.paths[cur] && schema.paths[cur].schema) {
1557
- schema = schema.paths[cur].schema;
1558
- nestedSchemaPath += (nestedSchemaPath.length > 0 ? '.' : '') + cur;
1559
- cur = '';
1560
- } else {
1561
- return null;
1562
- }
1563
- }
1564
- }
1565
-
1566
1546
  /**
1567
1547
  * Returns the virtual type with the given `name`.
1568
1548
  *
@@ -1638,7 +1618,7 @@ Schema.prototype.remove = function(path) {
1638
1618
  * ```
1639
1619
  *
1640
1620
  * @param {Function} model
1641
- * @param {Boolean} [virtualsOny] if truthy, only pulls virtuals from the class, not methods or statics
1621
+ * @param {Boolean} [virtualsOnly] if truthy, only pulls virtuals from the class, not methods or statics
1642
1622
  */
1643
1623
  Schema.prototype.loadClass = function(model, virtualsOnly) {
1644
1624
  if (model === Object.prototype ||
@@ -40,6 +40,9 @@ function applyHooks(model, schema, options) {
40
40
  }
41
41
  }
42
42
 
43
+ // Built-in hooks rely on hooking internal functions in order to support
44
+ // promises and make it so that `doc.save.toString()` provides meaningful
45
+ // information.
43
46
  objToDecorate.$__save = schema.s.hooks.
44
47
  createWrapper('save', objToDecorate.$__save, null, kareemOptions);
45
48
  objToDecorate.$__validate = schema.s.hooks.
@@ -48,4 +51,17 @@ function applyHooks(model, schema, options) {
48
51
  createWrapper('remove', objToDecorate.$__remove, null, kareemOptions);
49
52
  objToDecorate.$__init = schema.s.hooks.
50
53
  createWrapperSync('init', objToDecorate.$__init, null, kareemOptions);
54
+
55
+ // Support hooks for custom methods
56
+ const customMethods = Object.keys(schema.methods);
57
+ for (const method of customMethods) {
58
+ if (!schema.s.hooks.hasHooks(method)) {
59
+ // Don't wrap if there are no hooks for the custom method to avoid
60
+ // surprises. Also, `createWrapper()` enforces consistent async,
61
+ // so wrapping a sync method would break it.
62
+ continue;
63
+ }
64
+ objToDecorate[method] = schema.s.hooks.
65
+ createWrapper(method, objToDecorate[method], null, kareemOptions);
66
+ }
51
67
  }
@@ -136,7 +136,7 @@ module.exports = function discriminator(model, name, schema, tiedValue) {
136
136
 
137
137
  schema.plugins = Array.prototype.slice(baseSchema.plugins);
138
138
  schema.callQueue = baseSchema.callQueue.concat(schema.callQueue);
139
- schema._requiredpaths = undefined; // reset just in case Schema#requiredPaths() was called on either schema
139
+ delete schema._requiredpaths; // reset just in case Schema#requiredPaths() was called on either schema
140
140
  }
141
141
 
142
142
  // merges base schema into new discriminator schema and sets new type field.
@@ -148,6 +148,8 @@ module.exports = function discriminator(model, name, schema, tiedValue) {
148
148
 
149
149
  if (!model.schema.discriminatorMapping) {
150
150
  model.schema.discriminatorMapping = {key: key, value: null, isRoot: true};
151
+ }
152
+ if (!model.schema.discriminators) {
151
153
  model.schema.discriminators = {};
152
154
  }
153
155
 
@@ -14,16 +14,16 @@ var mpath = require('mpath');
14
14
  */
15
15
 
16
16
  module.exports = function getSchemaTypes(schema, doc, path) {
17
- var pathschema = schema.path(path);
17
+ const pathschema = schema.path(path);
18
18
 
19
19
  if (pathschema) {
20
20
  return pathschema;
21
21
  }
22
22
 
23
23
  function search(parts, schema) {
24
- var p = parts.length + 1;
25
- var foundschema;
26
- var trypath;
24
+ let p = parts.length + 1;
25
+ let foundschema;
26
+ let trypath;
27
27
 
28
28
  while (p--) {
29
29
  trypath = parts.slice(0, p).join('.');
@@ -35,10 +35,10 @@ module.exports = function getSchemaTypes(schema, doc, path) {
35
35
  return foundschema.caster;
36
36
  }
37
37
 
38
- var schemas = null;
38
+ let schemas = null;
39
39
  if (doc != null && foundschema.schema != null && foundschema.schema.discriminators != null) {
40
- var discriminators = foundschema.schema.discriminators;
41
- var keys = mpath.get(trypath + '.' + foundschema.schema.options.discriminatorKey,
40
+ const discriminators = foundschema.schema.discriminators;
41
+ const keys = mpath.get(trypath + '.' + foundschema.schema.options.discriminatorKey,
42
42
  doc);
43
43
  schemas = Object.keys(discriminators).
44
44
  reduce(function(cur, discriminator) {
@@ -56,7 +56,7 @@ module.exports = function getSchemaTypes(schema, doc, path) {
56
56
  // If there is no foundschema.schema we are dealing with
57
57
  // a path like array.$
58
58
  if (p !== parts.length && foundschema.schema) {
59
- var ret;
59
+ let ret;
60
60
  if (parts[p] === '$') {
61
61
  if (p + 1 === parts.length) {
62
62
  // comments.$
@@ -74,7 +74,7 @@ module.exports = function getSchemaTypes(schema, doc, path) {
74
74
  if (schemas != null && schemas.length > 0) {
75
75
  ret = [];
76
76
  for (var i = 0; i < schemas.length; ++i) {
77
- var _ret = search(parts.slice(p), schemas[i]);
77
+ let _ret = search(parts.slice(p), schemas[i]);
78
78
  if (_ret != null) {
79
79
  _ret.$isUnderneathDocArray = _ret.$isUnderneathDocArray ||
80
80
  !foundschema.schema.$isSingleNested;
@@ -104,8 +104,8 @@ module.exports = function getSchemaTypes(schema, doc, path) {
104
104
  }
105
105
 
106
106
  // look for arrays
107
- var parts = path.split('.');
108
- for (var i = 0; i < parts.length; ++i) {
107
+ const parts = path.split('.');
108
+ for (let i = 0; i < parts.length; ++i) {
109
109
  if (parts[i] === '$') {
110
110
  // Re: gh-5628, because `schema.path()` doesn't take $ into account.
111
111
  parts[i] = '0';
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ module.exports = getVirtual;
4
+
5
+ /*!
6
+ * ignore
7
+ */
8
+
9
+ function getVirtual(schema, name) {
10
+ if (schema.virtuals[name]) {
11
+ return schema.virtuals[name];
12
+ }
13
+ const parts = name.split('.');
14
+ let cur = '';
15
+ let nestedSchemaPath = '';
16
+ for (let i = 0; i < parts.length; ++i) {
17
+ cur += (cur.length > 0 ? '.' : '') + parts[i];
18
+ if (schema.virtuals[cur]) {
19
+ if (i === parts.length - 1) {
20
+ schema.virtuals[cur].$nestedSchemaPath = nestedSchemaPath;
21
+ return schema.virtuals[cur];
22
+ }
23
+ continue;
24
+ }
25
+
26
+ if (schema.paths[cur] && schema.paths[cur].schema) {
27
+ schema = schema.paths[cur].schema;
28
+
29
+ if (i === parts.length - 2 && schema.discriminators) {
30
+ // Check for embedded discriminators, don't currently support populating
31
+ // nested virtuals underneath embedded discriminators because that will
32
+ // require substantial refactoring.
33
+ for (let key of Object.keys(schema.discriminators)) {
34
+ const discriminatorSchema = schema.discriminators[key];
35
+ let _cur = parts[i + 1];
36
+ if (discriminatorSchema.virtuals[_cur]) {
37
+ discriminatorSchema.virtuals[_cur].$nestedSchemaPath =
38
+ (nestedSchemaPath.length > 0 ? '.' : '') + cur;
39
+ return discriminatorSchema.virtuals[_cur];
40
+ }
41
+ }
42
+ }
43
+
44
+ nestedSchemaPath += (nestedSchemaPath.length > 0 ? '.' : '') + cur;
45
+ cur = '';
46
+ continue;
47
+ }
48
+
49
+ return null;
50
+ }
51
+ }
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ const _modifiedPaths = require('../common').modifiedPaths;
4
+
5
+ /**
6
+ * Given an update document with potential update operators (`$set`, etc.)
7
+ * returns an object whose keys are the directly modified paths
8
+ *
9
+ * @param {Object} update
10
+ * @return {Object} modified
11
+ */
12
+
13
+ module.exports = function modifiedPaths(update) {
14
+ const keys = Object.keys(update);
15
+ const hasDollarKey = keys.filter(key => key.startsWith('$')).length > 0;
16
+
17
+ const res = {};
18
+ if (hasDollarKey) {
19
+ for (const key of keys) {
20
+ _modifiedPaths(update[key], '', res);
21
+ }
22
+ } else {
23
+ _modifiedPaths(update, '', res);
24
+ }
25
+
26
+ return res;
27
+ };
@@ -6,6 +6,7 @@ const EmbeddedDocument = require('./embedded');
6
6
  const Document = require('../document');
7
7
  const ObjectId = require('./objectid');
8
8
  const cleanModifiedSubpaths = require('../services/document/cleanModifiedSubpaths');
9
+ const get = require('lodash.get');
9
10
  const internalToObjectOptions = require('../options').internalToObjectOptions;
10
11
  const utils = require('../utils');
11
12
 
@@ -514,10 +515,10 @@ MongooseArray.mixin = {
514
515
  */
515
516
 
516
517
  pull: function() {
517
- var values = [].map.call(arguments, this._cast, this),
518
- cur = this._parent.get(this._path),
519
- i = cur.length,
520
- mem;
518
+ var values = [].map.call(arguments, this._cast, this);
519
+ var cur = this._parent.get(this._path);
520
+ var i = cur.length;
521
+ var mem;
521
522
 
522
523
  while (i--) {
523
524
  mem = cur[i];
@@ -822,7 +823,7 @@ function _isAllSubdocs(docs, ref) {
822
823
  */
823
824
 
824
825
  function _checkManualPopulation(arr, docs) {
825
- var ref = arr._schema.caster.options && arr._schema.caster.options.ref;
826
+ var ref = get(arr, '_schema.caster.options.ref', null);
826
827
  if (arr.length === 0 &&
827
828
  docs.length > 0) {
828
829
  if (_isAllSubdocs(docs, ref)) {
@@ -64,6 +64,13 @@ function MongooseDocumentArray(values, path, doc) {
64
64
  if (doc && doc instanceof Document) {
65
65
  arr._parent = doc;
66
66
  arr._schema = doc.schema.path(path);
67
+
68
+ // Tricky but this may be a document array embedded in a normal array,
69
+ // in which case `path` would point to the embedded array. See #6405, #6398
70
+ if (arr._schema && !arr._schema.$isMongooseDocumentArray) {
71
+ arr._schema = arr._schema.casterConstructor;
72
+ }
73
+
67
74
  arr._handlers = {
68
75
  isNew: arr.notify('isNew'),
69
76
  save: arr.notify('save')
package/lib/utils.js CHANGED
@@ -538,7 +538,22 @@ exports.populate = function populate(path, select, model, match, options, subPop
538
538
  throw new TypeError('utils.populate: invalid path. Expected string. Got typeof `' + typeof path + '`');
539
539
  }
540
540
 
541
- if (typeof subPopulate === 'object') {
541
+ if (Array.isArray(subPopulate)) {
542
+ let ret = [];
543
+ subPopulate.forEach(function(obj) {
544
+ if (/[\s]/.test(obj.path)) {
545
+ let copy = Object.assign({}, obj);
546
+ let paths = copy.path.split(' ');
547
+ paths.forEach(function(p) {
548
+ copy.path = p;
549
+ ret.push(exports.populate(copy)[0]);
550
+ });
551
+ } else {
552
+ ret.push(exports.populate(obj)[0]);
553
+ }
554
+ });
555
+ subPopulate = exports.populate(ret);
556
+ } else if (typeof subPopulate === 'object') {
542
557
  subPopulate = exports.populate(subPopulate);
543
558
  }
544
559
 
@@ -571,8 +586,8 @@ exports.getValue = function(path, obj, map) {
571
586
  * @param {Object} obj
572
587
  */
573
588
 
574
- exports.setValue = function(path, val, obj, map) {
575
- mpath.set(path, val, obj, '_doc', map);
589
+ exports.setValue = function(path, val, obj, map, _copying) {
590
+ mpath.set(path, val, obj, '_doc', map, _copying);
576
591
  };
577
592
 
578
593
  /*!
package/migrating_to_5.md CHANGED
@@ -102,7 +102,43 @@ const cursorWithOptions = MyModel.
102
102
 
103
103
  `Model.geoNear()` has been removed because the [MongoDB driver no longer supports it](https://github.com/mongodb/node-mongodb-native/blob/3.0.0/CHANGES_3.0.0.md#geonear-command-helper)
104
104
 
105
- ### Domain sockets
105
+ ### Required URI encoding of connection strings
106
+
107
+ Due to changes in the MongoDB driver, connection strings must be URI encoded.
108
+
109
+ If they are not, connections may fail with an illegeal character message.
110
+
111
+ #### Passwords which contain certain characters
112
+
113
+ See a [full list of affected characters](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding).
114
+
115
+ If your app is used by a lot of different connection strings, it's possible
116
+ that your test cases will pass, but production passwords will fail. Encode all your connection
117
+ strings to be safe.
118
+
119
+ If you want to continue to use unencoded connection strings, the easiest fix is to use
120
+ the `mongodb-uri` module to parse the connection strings, and then produce the properly encoded
121
+ versions. You can use a function like this:
122
+
123
+ ```javascript
124
+ const uriFormat = require('mongodb-uri')
125
+ function encodeMongoURI (urlString) {
126
+ if (urlString) {
127
+ let parsed = uriFormat.parse(urlString)
128
+ urlString = uriFormat.format(parsed);
129
+ }
130
+ return urlString;
131
+ }
132
+ }
133
+
134
+ // Your un-encoded string.
135
+ const mongodbConnectString = "mongodb://...";
136
+ mongoose.connect(encodeMongoURI(mongodbConnectString)
137
+ ```
138
+
139
+ The function above is safe to use whether the existing string is already encoded or not.
140
+
141
+ #### Domain sockets
106
142
 
107
143
  Domain sockets must be URI encoded. For example:
108
144
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "5.0.13",
4
+ "version": "5.0.17",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",
@@ -21,11 +21,11 @@
21
21
  "dependencies": {
22
22
  "async": "2.1.4",
23
23
  "bson": "~1.0.4",
24
- "kareem": "2.0.6",
24
+ "kareem": "2.0.7",
25
25
  "lodash.get": "4.4.2",
26
- "mongodb": "3.0.4",
26
+ "mongodb": "3.0.7",
27
27
  "mongoose-legacy-pluralize": "1.0.2",
28
- "mpath": "0.3.0",
28
+ "mpath": "0.4.1",
29
29
  "mquery": "3.0.0",
30
30
  "ms": "2.0.0",
31
31
  "regexp-clone": "0.0.1",