mongoose 5.0.7 → 5.0.11

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,45 @@
1
+ 5.0.11 / 2018-03-19
2
+ ===================
3
+ * fix(update): handle $pull with $in in update validators #6240
4
+ * fix(query): don't convert undefined to null when casting so driver `ignoreUndefined` option works #6236
5
+ * docs(middleware): add example of using async/await with middleware #6235
6
+ * fix(populate): apply justOne option before `completeMany()` so it works with lean() #6234
7
+ * fix(query): ensure errors in user callbacks aren't caught in init #6195 #6178
8
+ * docs(connections): document dbName option for Atlas connections #6179
9
+ * fix(discriminator): make child schema nested paths overwrite parent schema paths #6076
10
+
11
+ 4.13.12 / 2018-03-13
12
+ ====================
13
+ * fix(document): make virtual get() return undefined instead of null if no getters #6223
14
+ * docs: fix url in useMongoClient error message #6219 #6217 [lineus](https://github.com/lineus)
15
+ * fix(discriminator): don't copy `discriminators` property from base schema #6122 #6064
16
+
17
+ 5.0.10 / 2018-03-12
18
+ ===================
19
+ * docs(schematype): add notes re: running setters on queries #6209
20
+ * docs: fix typo #6208 [kamagatos](https://github.com/kamagatos)
21
+ * fix(query): only call setters once on query filter props for findOneAndUpdate and findOneAndRemove #6203
22
+ * docs: elaborate on connection string changes in migration guide #6193
23
+ * fix(document): skip applyDefaults if subdoc is null #6187
24
+ * docs: fix schematypes docs and link to them #6176
25
+ * docs(faq): add FAQs re: array defaults and casting aggregation pipelines #6184 #6176 #6170 [lineus](https://github.com/lineus)
26
+ * fix(document): ensure primitive defaults are set and built-in default functions run before setters #6155
27
+ * fix(query): handle single embedded embedded discriminators in castForQuery #6027
28
+
29
+ 5.0.9 / 2018-03-05
30
+ ==================
31
+ * perf: bump mongodb -> 3.0.4 to fix SSL perf issue #6065
32
+
33
+ 5.0.8 / 2018-03-03
34
+ ==================
35
+ * docs: remove obsolete references to `emitIndexErrors` #6186 [isaackwan](https://github.com/isaackwan)
36
+ * fix(query): don't cast findOne() until exec() so setters don't run twice #6157
37
+ * fix: remove document_provider.web.js file #6186
38
+ * fix(discriminator): support custom discriminator model names #6100 [wentout](https://github.com/wentout)
39
+ * fix: support caching calls to `useDb()` #6036 [rocketspacer](https://github.com/rocketspacer)
40
+ * fix(query): add omitUndefined option so setDefaultsOnInsert can kick in on undefined #6034
41
+ * fix: upgrade mongodb -> 3.0.3 for reconnectTries: 0 blocking process exit fix #6028
42
+
1
43
  5.0.7 / 2018-02-23
2
44
  ==================
3
45
  * fix: support eachAsync options with aggregation cursor #6169 #6168 [vichle](https://github.com/vichle)
package/README.md CHANGED
@@ -10,6 +10,8 @@ Mongoose is a [MongoDB](https://www.mongodb.org/) object modeling tool designed
10
10
 
11
11
  [mongoosejs.com](http://mongoosejs.com/)
12
12
 
13
+ [Mongoose 5.0.0](https://github.com/Automattic/mongoose/blob/master/History.md#500--2018-01-17) was released on January 17, 2018. You can find more details on backwards breaking changes in 5.0.0 on [GitHub](https://github.com/Automattic/mongoose/blob/master/migrating_to_5.md).
14
+
13
15
  ## Support
14
16
 
15
17
  - [Stack Overflow](http://stackoverflow.com/questions/tagged/mongoose)
@@ -57,7 +59,7 @@ First, we need to define a connection. If your app uses only one database, you s
57
59
  Both `connect` and `createConnection` take a `mongodb://` URI, or the parameters `host, database, port, options`.
58
60
 
59
61
  ```js
60
- var mongoose = require('mongoose');
62
+ const mongoose = require('mongoose');
61
63
 
62
64
  mongoose.connect('mongodb://localhost/my_database');
63
65
  ```
@@ -73,14 +75,14 @@ Once connected, the `open` event is fired on the `Connection` instance. If you'r
73
75
  Models are defined through the `Schema` interface.
74
76
 
75
77
  ```js
76
- var Schema = mongoose.Schema,
78
+ const Schema = mongoose.Schema,
77
79
  ObjectId = Schema.ObjectId;
78
80
 
79
- var BlogPost = new Schema({
80
- author : ObjectId,
81
- title : String,
82
- body : String,
83
- date : Date
81
+ const BlogPost = new Schema({
82
+ author: ObjectId,
83
+ title: String,
84
+ body: String,
85
+  date: Date
84
86
  });
85
87
  ```
86
88
 
@@ -100,7 +102,7 @@ Aside from defining the structure of your documents and the types of data you're
100
102
  The following example shows some of these features:
101
103
 
102
104
  ```js
103
- var Comment = new Schema({
105
+ const Comment = new Schema({
104
106
  name: { type: String, default: 'hahaha' },
105
107
  age: { type: Number, min: 18, index: true },
106
108
  bio: { type: String, match: /[a-z]/ },
@@ -127,19 +129,19 @@ Take a look at the example in `examples/schema.js` for an end-to-end example of
127
129
  Once we define a model through `mongoose.model('ModelName', mySchema)`, we can access it through the same function
128
130
 
129
131
  ```js
130
- var myModel = mongoose.model('ModelName');
132
+ const myModel = mongoose.model('ModelName');
131
133
  ```
132
134
 
133
135
  Or just do it all at once
134
136
 
135
137
  ```js
136
- var MyModel = mongoose.model('ModelName', mySchema);
138
+ const MyModel = mongoose.model('ModelName', mySchema);
137
139
  ```
138
140
 
139
141
  The first argument is the _singular_ name of the collection your model is for. **Mongoose automatically looks for the _plural_ version of your model name.** For example, if you use
140
142
 
141
143
  ```js
142
- var MyModel = mongoose.model('Ticket', mySchema);
144
+ const MyModel = mongoose.model('Ticket', mySchema);
143
145
  ```
144
146
 
145
147
  Then Mongoose will create the model for your __tickets__ collection, not your __ticket__ collection.
@@ -147,7 +149,7 @@ Then Mongoose will create the model for your __tickets__ collection, not your __
147
149
  Once we have our model, we can then instantiate it, and save it:
148
150
 
149
151
  ```js
150
- var instance = new MyModel();
152
+ const instance = new MyModel();
151
153
  instance.my.key = 'hello';
152
154
  instance.save(function (err) {
153
155
  //
@@ -167,18 +169,18 @@ You can also `findOne`, `findById`, `update`, etc. For more details check out [t
167
169
  **Important!** If you opened a separate connection using `mongoose.createConnection()` but attempt to access the model through `mongoose.model('ModelName')` it will not work as expected since it is not hooked up to an active db connection. In this case access your model through the connection you created:
168
170
 
169
171
  ```js
170
- var conn = mongoose.createConnection('your connection string'),
171
- MyModel = conn.model('ModelName', schema),
172
- m = new MyModel;
172
+ const conn = mongoose.createConnection('your connection string');
173
+ const MyModel = conn.model('ModelName', schema);
174
+ const m = new MyModel;
173
175
  m.save(); // works
174
176
  ```
175
177
 
176
178
  vs
177
179
 
178
180
  ```js
179
- var conn = mongoose.createConnection('your connection string'),
180
- MyModel = mongoose.model('ModelName', schema),
181
- m = new MyModel;
181
+ const conn = mongoose.createConnection('your connection string');
182
+ const MyModel = mongoose.model('ModelName', schema);
183
+ const m = new MyModel;
182
184
  m.save(); // does not work b/c the default connection object was never connected
183
185
  ```
184
186
 
package/browser.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Export lib/mongoose
3
+ *
4
+ */
5
+
6
+ module.exports = require('./lib/browser');
package/lib/cast.js CHANGED
@@ -1,13 +1,16 @@
1
+ 'use strict';
2
+
1
3
  /*!
2
4
  * Module dependencies.
3
5
  */
4
6
 
5
- var StrictModeError = require('./error/strict');
6
- var Types = require('./schema/index');
7
- var util = require('util');
8
- var utils = require('./utils');
7
+ const StrictModeError = require('./error/strict');
8
+ const Types = require('./schema/index');
9
+ const get = require('lodash.get');
10
+ const util = require('util');
11
+ const utils = require('./utils');
9
12
 
10
- var ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];
13
+ const ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];
11
14
 
12
15
  /**
13
16
  * Handles internal casting for query filters.
@@ -67,6 +70,31 @@ module.exports = function cast(schema, obj, options, context) {
67
70
 
68
71
  schematype = schema.path(path);
69
72
 
73
+ // Check for embedded discriminator paths
74
+ if (!schematype) {
75
+ let split = path.split('.');
76
+ let j = split.length;
77
+ while (j--) {
78
+ let pathFirstHalf = split.slice(0, j).join('.');
79
+ let pathLastHalf = split.slice(j).join('.');
80
+ let _schematype = schema.path(pathFirstHalf);
81
+ let discriminatorKey = get(_schematype, 'schema.options.discriminatorKey');
82
+ // gh-6027: if we haven't found the schematype but this path is
83
+ // underneath an embedded discriminator and the embedded discriminator
84
+ // key is in the query, use the embedded discriminator schema
85
+ if (_schematype != null &&
86
+ get(_schematype, 'schema.discriminators') != null &&
87
+ discriminatorKey != null &&
88
+ pathLastHalf !== discriminatorKey) {
89
+ const discriminatorVal = get(obj, pathFirstHalf + '.' + discriminatorKey);
90
+ if (discriminatorVal) {
91
+ schematype = _schematype.schema.discriminators[discriminatorVal].
92
+ path(pathLastHalf);
93
+ }
94
+ }
95
+ }
96
+ }
97
+
70
98
  if (!schematype) {
71
99
  // Handle potential embedded array queries
72
100
  var split = path.split('.'),
@@ -206,8 +234,7 @@ module.exports = function cast(schema, obj, options, context) {
206
234
  } else if (options.strictQuery) {
207
235
  delete obj[path];
208
236
  }
209
- } else if (val === null || val === undefined) {
210
- obj[path] = null;
237
+ } else if (val == null) {
211
238
  continue;
212
239
  } else if (val.constructor.name === 'Object') {
213
240
  any$conditionals = Object.keys(val).some(function(k) {
package/lib/connection.js CHANGED
@@ -59,7 +59,8 @@ function Connection(base) {
59
59
  this.pass = null;
60
60
  this.name = null;
61
61
  this.options = null;
62
- this.otherDbs = [];
62
+ this.otherDbs = []; // FIXME: To be replaced with relatedDbs
63
+ this.relatedDbs = {}; // Hashmap of other dbs that share underlying connection
63
64
  this.states = STATES;
64
65
  this._readyState = STATES.disconnected;
65
66
  this._closeCalled = false;
@@ -103,11 +104,16 @@ Object.defineProperty(Connection.prototype, 'readyState', {
103
104
 
104
105
  if (this._readyState !== val) {
105
106
  this._readyState = val;
106
- // loop over the otherDbs on this connection and change their state
107
+ // [legacy] loop over the otherDbs on this connection and change their state
107
108
  for (var i = 0; i < this.otherDbs.length; i++) {
108
109
  this.otherDbs[i].readyState = val;
109
110
  }
110
111
 
112
+ // loop over relatedDbs on this connection and change their state
113
+ for (var k in this.relatedDbs) {
114
+ this.relatedDbs[k].readyState = val;
115
+ }
116
+
111
117
  if (STATES.connected === val) {
112
118
  this._hasOpened = true;
113
119
  }
package/lib/document.js CHANGED
@@ -87,6 +87,10 @@ function Document(obj, fields, skipId, options) {
87
87
 
88
88
  this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren);
89
89
 
90
+ // By default, defaults get applied **before** setting initial values
91
+ // Re: gh-6155
92
+ $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true);
93
+
90
94
  if (obj) {
91
95
  if (obj instanceof Document) {
92
96
  this.isNew = obj.isNew;
@@ -99,7 +103,10 @@ function Document(obj, fields, skipId, options) {
99
103
  }
100
104
  }
101
105
 
102
- $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren);
106
+ // Function defaults get applied **after** setting initial values so they
107
+ // see the full doc rather than an empty one, unless they opt out.
108
+ // Re: gh-3781, gh-6155
109
+ $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false);
103
110
 
104
111
  this.$__._id = this._id;
105
112
 
@@ -202,7 +209,7 @@ function $__hasIncludedChildren(fields) {
202
209
  * ignore
203
210
  */
204
211
 
205
- function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren) {
212
+ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isBeforeSetters) {
206
213
  const paths = Object.keys(doc.schema.paths);
207
214
  const plen = paths.length;
208
215
 
@@ -222,6 +229,10 @@ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren) {
222
229
  let doc_ = doc._doc;
223
230
 
224
231
  for (let j = 0; j < len; ++j) {
232
+ if (doc_ == null) {
233
+ break;
234
+ }
235
+
225
236
  let piece = path[j];
226
237
  curPath += (!curPath.length ? '' : '.') + piece;
227
238
 
@@ -242,6 +253,18 @@ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren) {
242
253
  break;
243
254
  }
244
255
 
256
+ if (typeof type.defaultValue === 'function') {
257
+ if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
258
+ break;
259
+ }
260
+ if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
261
+ break;
262
+ }
263
+ } else if (!isBeforeSetters) {
264
+ // Non-function defaults should always run **before** setters
265
+ continue;
266
+ }
267
+
245
268
  if (fields && exclude !== null) {
246
269
  if (exclude === true) {
247
270
  // apply defaults to all non-excluded fields
@@ -1009,6 +1032,9 @@ Document.prototype.get = function(path, type) {
1009
1032
  var obj = this._doc;
1010
1033
 
1011
1034
  if (schema instanceof VirtualType) {
1035
+ if (schema.getters.length === 0) {
1036
+ return void 0;
1037
+ }
1012
1038
  return schema.applyGetters(null, this);
1013
1039
  }
1014
1040
 
@@ -40,7 +40,10 @@ NativeConnection.prototype.__proto__ = MongooseConnection.prototype;
40
40
  * @api public
41
41
  */
42
42
 
43
- NativeConnection.prototype.useDb = function(name) {
43
+ NativeConnection.prototype.useDb = function(name, options) {
44
+ // Return immediately if cached
45
+ if (options && options.useCache && this.relatedDbs[name]) return this.relatedDbs[name];
46
+
44
47
  // we have to manually copy all of the attributes...
45
48
  var newConn = new this.constructor();
46
49
  newConn.name = name;
@@ -88,6 +91,12 @@ NativeConnection.prototype.useDb = function(name) {
88
91
  this.otherDbs.push(newConn);
89
92
  newConn.otherDbs.push(this);
90
93
 
94
+ // push onto the relatedDbs cache, this is used when state changes
95
+ if (options && options.useCache) {
96
+ this.relatedDbs[newConn.name] = newConn;
97
+ newConn.relatedDbs = this.relatedDbs;
98
+ }
99
+
91
100
  return newConn;
92
101
  };
93
102
 
package/lib/index.js CHANGED
@@ -477,7 +477,7 @@ Mongoose.prototype.__defineSetter__('connection', function(v) {
477
477
  });
478
478
 
479
479
  /*!
480
- * Driver depentend APIs
480
+ * Driver dependent APIs
481
481
  */
482
482
 
483
483
  var driver = global.MONGOOSE_DRIVER_PATH || './drivers/node-mongodb-native';
package/lib/model.js CHANGED
@@ -22,6 +22,7 @@ var applyStatics = require('./services/model/applyStatics');
22
22
  var cast = require('./cast');
23
23
  var castUpdate = require('./services/query/castUpdate');
24
24
  var discriminator = require('./services/model/discriminator');
25
+ var getDiscriminatorByValue = require('./queryhelpers').getDiscriminatorByValue;
25
26
  var internalToObjectOptions = require('./options').internalToObjectOptions;
26
27
  var isPathSelectedInclusive = require('./services/projection/isPathSelectedInclusive');
27
28
  var get = require('lodash.get');
@@ -829,10 +830,11 @@ Model.prototype.model = function model(name) {
829
830
  *
830
831
  * @param {String} name discriminator model name
831
832
  * @param {Schema} schema discriminator model schema
833
+ * @param {String} value discriminator model typeKey value
832
834
  * @api public
833
835
  */
834
836
 
835
- Model.discriminator = function(name, schema) {
837
+ Model.discriminator = function(name, schema, value) {
836
838
  var model;
837
839
  if (typeof name === 'function') {
838
840
  model = name;
@@ -842,7 +844,7 @@ Model.discriminator = function(name, schema) {
842
844
  }
843
845
  }
844
846
 
845
- schema = discriminator(this, name, schema);
847
+ schema = discriminator(this, name, schema, value);
846
848
  if (this.db.models[name]) {
847
849
  throw new OverwriteModelError(name);
848
850
  }
@@ -2034,7 +2036,7 @@ Model.create = function create(doc, callback) {
2034
2036
  args.forEach(doc => {
2035
2037
  toExecute.push(callback => {
2036
2038
  var Model = this.discriminators && doc[discriminatorKey] ?
2037
- this.discriminators[doc[discriminatorKey]] :
2039
+ this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this, doc[discriminatorKey]) :
2038
2040
  this;
2039
2041
  var toSave = doc;
2040
2042
  var callbackWrapper = (error, doc) => {
@@ -3267,6 +3269,8 @@ function assignVals(o) {
3267
3269
  } else {
3268
3270
  rawIds[i] = [rawIds[i]];
3269
3271
  }
3272
+ } else if (o.isVirtual && o.justOne && Array.isArray(rawIds[i])) {
3273
+ rawIds[i] = rawIds[i][0];
3270
3274
  }
3271
3275
 
3272
3276
  if (o.isVirtual && docs[i].constructor.name === 'model') {
package/lib/query.js CHANGED
@@ -1025,6 +1025,10 @@ Query.prototype.setOptions = function(options, overwrite) {
1025
1025
  this._mongooseOptions.useFindAndModify = options.useFindAndModify;
1026
1026
  delete options.useFindAndModify;
1027
1027
  }
1028
+ if ('omitUndefined' in options) {
1029
+ this._mongooseOptions.omitUndefined = options.omitUndefined;
1030
+ delete options.omitUndefined;
1031
+ }
1028
1032
 
1029
1033
  return Query.base.setOptions.call(this, options);
1030
1034
  };
@@ -1446,10 +1450,10 @@ function completeMany(model, docs, fields, userProvidedFields, pop, callback) {
1446
1450
  error = error || _error;
1447
1451
  }
1448
1452
  if (error != null) {
1449
- --count || callback(error);
1453
+ --count || process.nextTick(() => callback(error));
1450
1454
  return;
1451
1455
  }
1452
- --count || callback(error, arr);
1456
+ --count || process.nextTick(() => callback(error, arr));
1453
1457
  }
1454
1458
  for (var i = 0; i < len; ++i) {
1455
1459
  arr[i] = helpers.createModel(model, docs[i], fields, userProvidedFields);
@@ -1595,13 +1599,6 @@ Query.prototype.findOne = function(conditions, projection, options, callback) {
1595
1599
  this.merge(conditions);
1596
1600
 
1597
1601
  prepareDiscriminatorCriteria(this);
1598
-
1599
- try {
1600
- this.cast(this.model);
1601
- this.error(null);
1602
- } catch (err) {
1603
- this.error(err);
1604
- }
1605
1602
  } else if (conditions != null) {
1606
1603
  this.error(new ObjectParameterError(conditions, 'filter', 'findOne'));
1607
1604
  }
@@ -2000,14 +1997,14 @@ function completeOne(model, doc, res, options, fields, userProvidedFields, pop,
2000
1997
 
2001
1998
  function _init(err) {
2002
1999
  if (err) {
2003
- return callback(err);
2000
+ return process.nextTick(() => callback(err));
2004
2001
  }
2005
2002
 
2006
2003
  if (options.rawResult) {
2007
2004
  res.value = casted;
2008
- return callback(null, res);
2005
+ return process.nextTick(() => callback(null, res));
2009
2006
  }
2010
- callback(null, casted);
2007
+ process.nextTick(() => callback(null, casted));
2011
2008
  }
2012
2009
  }
2013
2010
 
@@ -2148,8 +2145,6 @@ Query.prototype.findOneAndUpdate = function(criteria, doc, options, callback) {
2148
2145
  */
2149
2146
 
2150
2147
  Query.prototype._findOneAndUpdate = function(callback) {
2151
- this._castConditions();
2152
-
2153
2148
  if (this.error() != null) {
2154
2149
  return callback(this.error());
2155
2150
  }
@@ -2243,8 +2238,6 @@ Query.prototype.findOneAndRemove = function(conditions, options, callback) {
2243
2238
  * @api private
2244
2239
  */
2245
2240
  Query.prototype._findOneAndRemove = function(callback) {
2246
- this._castConditions();
2247
-
2248
2241
  if (this.error() != null) {
2249
2242
  return callback(this.error());
2250
2243
  }
@@ -3071,9 +3064,16 @@ Query.prototype._castUpdate = function _castUpdate(obj, overwrite) {
3071
3064
  } else {
3072
3065
  strict = true;
3073
3066
  }
3067
+
3068
+ var omitUndefined = false;
3069
+ if ('omitUndefined' in this._mongooseOptions) {
3070
+ omitUndefined = this._mongooseOptions.omitUndefined;
3071
+ }
3072
+
3074
3073
  return castUpdate(this.schema, obj, {
3075
3074
  overwrite: overwrite,
3076
- strict: strict
3075
+ strict: strict,
3076
+ omitUndefined
3077
3077
  }, this);
3078
3078
  };
3079
3079
 
@@ -46,6 +46,34 @@ exports.preparePopulationOptionsMQ = function preparePopulationOptionsMQ(query,
46
46
  return pop;
47
47
  };
48
48
 
49
+
50
+ /*!
51
+ * returns discriminator by discriminatorMapping.value
52
+ *
53
+ * @param {Model} model
54
+ * @param {string} value
55
+ */
56
+ function getDiscriminatorByValue(model, value) {
57
+ var discriminator = null;
58
+ if (!model.discriminators) {
59
+ return discriminator;
60
+ }
61
+ for (var name in model.discriminators) {
62
+ var it = model.discriminators[name];
63
+ if (
64
+ it.schema &&
65
+ it.schema.discriminatorMapping &&
66
+ it.schema.discriminatorMapping.value == value
67
+ ) {
68
+ discriminator = it;
69
+ break;
70
+ }
71
+ }
72
+ return discriminator;
73
+ }
74
+
75
+ exports.getDiscriminatorByValue = getDiscriminatorByValue;
76
+
49
77
  /*!
50
78
  * If the document is a mapped discriminator type, it returns a model instance for that type, otherwise,
51
79
  * it returns an instance of the given model.
@@ -65,11 +93,14 @@ exports.createModel = function createModel(model, doc, fields, userProvidedField
65
93
  ? discriminatorMapping.key
66
94
  : null;
67
95
 
68
- if (key && doc[key] && model.discriminators && model.discriminators[doc[key]]) {
69
- var discriminator = model.discriminators[doc[key]];
70
- var _fields = utils.clone(userProvidedFields);
71
- exports.applyPaths(_fields, discriminator.schema);
72
- return new model.discriminators[doc[key]](undefined, _fields, true);
96
+ var value = doc[key];
97
+ if (key && value && model.discriminators) {
98
+ var discriminator = model.discriminators[value] || getDiscriminatorByValue(model, value);
99
+ if (discriminator) {
100
+ var _fields = utils.clone(userProvidedFields);
101
+ exports.applyPaths(_fields, discriminator.schema);
102
+ return new discriminator(undefined, _fields, true);
103
+ }
73
104
  }
74
105
 
75
106
  return new model(undefined, fields, true);
@@ -22,6 +22,7 @@ var util = require('util');
22
22
  var utils = require('../utils');
23
23
  var castToNumber = require('./operators/helpers').castToNumber;
24
24
  var geospatial = require('./operators/geospatial');
25
+ var getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByValue;
25
26
 
26
27
  var MongooseArray;
27
28
  var EmbeddedDoc;
@@ -93,7 +94,7 @@ function SchemaArray(key, cast, options, schemaOptions) {
93
94
  }
94
95
 
95
96
  if (!('defaultValue' in this) || this.defaultValue !== void 0) {
96
- this.default(function() {
97
+ var defaultFn = function() {
97
98
  var arr = [];
98
99
  if (fn) {
99
100
  arr = defaultArr();
@@ -102,7 +103,9 @@ function SchemaArray(key, cast, options, schemaOptions) {
102
103
  }
103
104
  // Leave it up to `cast()` to convert the array
104
105
  return arr;
105
- });
106
+ };
107
+ defaultFn.$runBeforeSetters = true;
108
+ this.default(defaultFn);
106
109
  }
107
110
  }
108
111
 
@@ -247,10 +250,18 @@ SchemaArray.prototype.castForQuery = function($conditional, value) {
247
250
 
248
251
  if (val &&
249
252
  Constructor.discriminators &&
250
- Constructor.schema.options.discriminatorKey &&
251
- typeof val[Constructor.schema.options.discriminatorKey] === 'string' &&
252
- Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]]) {
253
- Constructor = Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]];
253
+ Constructor.schema &&
254
+ Constructor.schema.options &&
255
+ Constructor.schema.options.discriminatorKey) {
256
+ if (typeof val[Constructor.schema.options.discriminatorKey] === 'string' &&
257
+ Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]]) {
258
+ Constructor = Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]];
259
+ } else {
260
+ var constructorByValue = getDiscriminatorByValue(Constructor, val[Constructor.schema.options.discriminatorKey]);
261
+ if (constructorByValue) {
262
+ Constructor = constructorByValue;
263
+ }
264
+ }
254
265
  }
255
266
 
256
267
  var proto = this.casterConstructor.prototype;
@@ -12,6 +12,7 @@ var SchemaType = require('../schematype');
12
12
  var discriminator = require('../services/model/discriminator');
13
13
  var util = require('util');
14
14
  var utils = require('../utils');
15
+ var getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByValue;
15
16
 
16
17
  var MongooseDocumentArray;
17
18
  var Subdocument;
@@ -291,9 +292,17 @@ DocumentArray.prototype.cast = function(value, doc, init, prev, options) {
291
292
 
292
293
  var Constructor = this.casterConstructor;
293
294
  if (Constructor.discriminators &&
294
- typeof value[i][Constructor.schema.options.discriminatorKey] === 'string' &&
295
- Constructor.discriminators[value[i][Constructor.schema.options.discriminatorKey]]) {
296
- Constructor = Constructor.discriminators[value[i][Constructor.schema.options.discriminatorKey]];
295
+ Constructor.schema &&
296
+ Constructor.schema.options &&
297
+ typeof value[i][Constructor.schema.options.discriminatorKey] === 'string') {
298
+ if (Constructor.discriminators[value[i][Constructor.schema.options.discriminatorKey]]) {
299
+ Constructor = Constructor.discriminators[value[i][Constructor.schema.options.discriminatorKey]];
300
+ } else {
301
+ var constructorByValue = getDiscriminatorByValue(Constructor, value[i][Constructor.schema.options.discriminatorKey]);
302
+ if (constructorByValue) {
303
+ Constructor = constructorByValue;
304
+ }
305
+ }
297
306
  }
298
307
 
299
308
  // Check if the document has a different schema (re gh-3701)
@@ -11,6 +11,7 @@ const castToNumber = require('./operators/helpers').castToNumber;
11
11
  const discriminator = require('../services/model/discriminator');
12
12
  const geospatial = require('./operators/geospatial');
13
13
  const internalToObjectOptions = require('../options').internalToObjectOptions;
14
+ var getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByValue;
14
15
 
15
16
  let Subdocument;
16
17
 
@@ -140,9 +141,15 @@ Embedded.prototype.cast = function(val, doc, init, priorVal) {
140
141
  var discriminatorKey = Constructor.schema.options.discriminatorKey;
141
142
  if (val != null &&
142
143
  Constructor.discriminators &&
143
- typeof val[discriminatorKey] === 'string' &&
144
- Constructor.discriminators[val[discriminatorKey]]) {
145
- Constructor = Constructor.discriminators[val[discriminatorKey]];
144
+ typeof val[discriminatorKey] === 'string') {
145
+ if (Constructor.discriminators[val[discriminatorKey]]) {
146
+ Constructor = Constructor.discriminators[val[discriminatorKey]];
147
+ } else {
148
+ var constructorByValue = getDiscriminatorByValue(Constructor, val[discriminatorKey]);
149
+ if (constructorByValue) {
150
+ Constructor = constructorByValue;
151
+ }
152
+ }
146
153
  }
147
154
 
148
155
  var subdoc;
@@ -188,7 +195,22 @@ Embedded.prototype.castForQuery = function($conditional, val) {
188
195
  val = this._applySetters(val);
189
196
  }
190
197
 
191
- return new this.caster(val);
198
+ var Constructor = this.caster;
199
+ var discriminatorKey = Constructor.schema.options.discriminatorKey;
200
+ if (val != null &&
201
+ Constructor.discriminators &&
202
+ typeof val[discriminatorKey] === 'string') {
203
+ if (Constructor.discriminators[val[discriminatorKey]]) {
204
+ Constructor = Constructor.discriminators[val[discriminatorKey]];
205
+ } else {
206
+ var constructorByValue = getDiscriminatorByValue(Constructor, val[discriminatorKey]);
207
+ if (constructorByValue) {
208
+ Constructor = constructorByValue;
209
+ }
210
+ }
211
+ }
212
+
213
+ return new Constructor(val);
192
214
  };
193
215
 
194
216
  /**
@@ -202,11 +224,18 @@ Embedded.prototype.doValidate = function(value, fn, scope) {
202
224
  var discriminatorKey = Constructor.schema.options.discriminatorKey;
203
225
  if (value != null &&
204
226
  Constructor.discriminators &&
205
- typeof value[discriminatorKey] === 'string' &&
206
- Constructor.discriminators[value[discriminatorKey]]) {
207
- Constructor = Constructor.discriminators[value[discriminatorKey]];
227
+ typeof value[discriminatorKey] === 'string') {
228
+ if (Constructor.discriminators[value[discriminatorKey]]) {
229
+ Constructor = Constructor.discriminators[value[discriminatorKey]];
230
+ } else {
231
+ var constructorByValue = getDiscriminatorByValue(Constructor, value[discriminatorKey]);
232
+ if (constructorByValue) {
233
+ Constructor = constructorByValue;
234
+ }
235
+ }
208
236
  }
209
237
 
238
+
210
239
  SchemaType.prototype.doValidate.call(this, value, function(error) {
211
240
  if (error) {
212
241
  return fn(error);
@@ -206,6 +206,8 @@ function defaultId() {
206
206
  return new oid();
207
207
  }
208
208
 
209
+ defaultId.$runBeforeSetters = true;
210
+
209
211
  function resetId(v) {
210
212
  Document || (Document = require('./../document'));
211
213
 
package/lib/schema.js CHANGED
@@ -29,7 +29,6 @@ var mpath = require('mpath');
29
29
  * - [bufferCommands](/docs/guide.html#bufferCommands): bool - defaults to true
30
30
  * - [capped](/docs/guide.html#capped): bool - defaults to false
31
31
  * - [collection](/docs/guide.html#collection): string - no default
32
- * - [emitIndexErrors](/docs/guide.html#emitIndexErrors): bool - defaults to false.
33
32
  * - [id](/docs/guide.html#id): bool - defaults to true
34
33
  * - [_id](/docs/guide.html#_id): bool - defaults to true
35
34
  * - `minimize`: bool - controls [document#toObject](#document_Document-toObject) behavior when called manually - defaults to true
package/lib/schematype.js CHANGED
@@ -245,15 +245,19 @@ SchemaType.prototype.sparse = function(bool) {
245
245
  * }
246
246
  *
247
247
  * // defining within the schema
248
- * var s = new Schema({ name: { type: String, set: capitalize }})
248
+ * var s = new Schema({ name: { type: String, set: capitalize }});
249
249
  *
250
- * // or by retreiving its SchemaType
250
+ * // or with the SchemaType
251
251
  * var s = new Schema({ name: String })
252
- * s.path('name').set(capitalize)
252
+ * s.path('name').set(capitalize);
253
253
  *
254
- * Setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key.
254
+ * Setters allow you to transform the data before it gets to the raw mongodb
255
+ * document or query.
255
256
  *
256
- * Suppose you are implementing user registration for a website. Users provide an email and password, which gets saved to mongodb. The email is a string that you will want to normalize to lower case, in order to avoid one email having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM.
257
+ * Suppose you are implementing user registration for a website. Users provide
258
+ * an email and password, which gets saved to mongodb. The email is a string
259
+ * that you will want to normalize to lower case, in order to avoid one email
260
+ * having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM.
257
261
  *
258
262
  * You can set up email lower case normalization easily via a Mongoose setter.
259
263
  *
@@ -276,7 +280,8 @@ SchemaType.prototype.sparse = function(bool) {
276
280
  * console.log(user.email); // 'avenue@q.com'
277
281
  * User.updateOne({ _id: _id }, { $set: { email: 'AVENUE@Q.COM' } }); // update to 'avenue@q.com'
278
282
  *
279
- * As you can see above, setters allow you to transform the data before it stored in MongoDB.
283
+ * As you can see above, setters allow you to transform the data before it
284
+ * stored in MongoDB, or before executing a query.
280
285
  *
281
286
  * _NOTE: we could have also just used the built-in `lowercase: true` SchemaType option instead of defining our own function._
282
287
  *
@@ -303,6 +308,24 @@ SchemaType.prototype.sparse = function(bool) {
303
308
  * console.log(v.name); // name is required
304
309
  * console.log(v.taxonomy); // Parvovirinae
305
310
  *
311
+ * You can also use setters to modify other properties on the document. If
312
+ * you're setting a property `name` on a document, the setter will run with
313
+ * `this` as the document. Be careful, in mongoose 5 setters will also run
314
+ * when querying by `name` with `this` as the query.
315
+ *
316
+ * ```javascript
317
+ * const nameSchema = new Schema({ name: String, keywords: [String] });
318
+ * nameSchema.path('name').set(function(v) {
319
+ * // Need to check if `this` is a document, because in mongoose 5
320
+ * // setters will also run on queries, in which case `this` will be a
321
+ * // mongoose query object.
322
+ * if (this instanceof Document && v != null) {
323
+ * this.keywords = v.split(' ');
324
+ * }
325
+ * return v;
326
+ * });
327
+ * ```
328
+ *
306
329
  * @param {Function} fn
307
330
  * @return {SchemaType} this
308
331
  * @api public
@@ -14,7 +14,7 @@ var CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
14
14
  * ignore
15
15
  */
16
16
 
17
- module.exports = function discriminator(model, name, schema) {
17
+ module.exports = function discriminator(model, name, schema, tiedValue) {
18
18
  if (!(schema && schema.instanceOfSchema)) {
19
19
  throw new Error('You must pass a valid discriminator Schema');
20
20
  }
@@ -46,6 +46,11 @@ module.exports = function discriminator(model, name, schema) {
46
46
  '" cannot have field with name "' + key + '"');
47
47
  }
48
48
 
49
+ var value = name;
50
+ if (typeof tiedValue == 'string' && tiedValue.length) {
51
+ value = tiedValue;
52
+ }
53
+
49
54
  function merge(schema, baseSchema) {
50
55
  if (baseSchema.paths._id &&
51
56
  baseSchema.paths._id.options &&
@@ -55,15 +60,38 @@ module.exports = function discriminator(model, name, schema) {
55
60
  delete schema.paths._id;
56
61
  delete schema.tree._id;
57
62
  }
58
- utils.merge(schema, baseSchema, { omit: { discriminators: true } });
63
+
64
+ // Find conflicting paths: if something is a path in the base schema
65
+ // and a nested path in the child schema, overwrite the base schema path.
66
+ // See gh-6076
67
+ var baseSchemaPaths = Object.keys(baseSchema.paths);
68
+ var conflictingPaths = [];
69
+ for (i = 0; i < baseSchemaPaths.length; ++i) {
70
+ if (schema.nested[baseSchemaPaths[i]]) {
71
+ conflictingPaths.push(baseSchemaPaths[i]);
72
+ }
73
+ }
74
+
75
+ utils.merge(schema, baseSchema, {
76
+ omit: { discriminators: true },
77
+ omitNested: conflictingPaths.reduce((cur, path) => {
78
+ cur['tree.' + path] = true;
79
+ return cur;
80
+ }, {})
81
+ });
82
+
83
+ // Clean up conflicting paths _after_ merging re: gh-6076
84
+ for (i = 0; i < conflictingPaths.length; ++i) {
85
+ delete schema.paths[conflictingPaths[i]];
86
+ }
59
87
 
60
88
  var obj = {};
61
89
  obj[key] = {
62
- default: name,
90
+ default: value,
63
91
  select: true,
64
92
  set: function(newName) {
65
- if (newName === name) {
66
- return name;
93
+ if (newName === value) {
94
+ return value;
67
95
  }
68
96
  throw new Error('Can\'t set discriminator key "' + key + '"');
69
97
  },
@@ -71,7 +99,7 @@ module.exports = function discriminator(model, name, schema) {
71
99
  };
72
100
  obj[key][schema.options.typeKey] = String;
73
101
  schema.add(obj);
74
- schema.discriminatorMapping = {key: key, value: name, isRoot: false};
102
+ schema.discriminatorMapping = {key: key, value: value, isRoot: false};
75
103
 
76
104
  if (baseSchema.options.collection) {
77
105
  schema.options.collection = baseSchema.options.collection;
@@ -71,7 +71,7 @@ module.exports = function castUpdate(schema, obj, options, context) {
71
71
  typeof val === 'object' &&
72
72
  !Buffer.isBuffer(val) &&
73
73
  (!overwrite || hasDollarKey)) {
74
- hasKeys |= walkUpdatePath(schema, val, op, options.strict, context);
74
+ hasKeys |= walkUpdatePath(schema, val, op, options, context);
75
75
  } else if (overwrite && ret && typeof ret === 'object') {
76
76
  // if we are just using overwrite, cast the query and then we will
77
77
  // *always* return the value, even if it is an empty object. We need to
@@ -79,7 +79,7 @@ module.exports = function castUpdate(schema, obj, options, context) {
79
79
  // user passes {} and wants to clobber the whole document
80
80
  // Also, _walkUpdatePath expects an operation, so give it $set since that
81
81
  // is basically what we're doing
82
- walkUpdatePath(schema, ret, '$set', options.strict, context);
82
+ walkUpdatePath(schema, ret, '$set', options, context);
83
83
  } else {
84
84
  var msg = 'Invalid atomic update value for ' + op + '. '
85
85
  + 'Expected an object, received ' + typeof val;
@@ -97,14 +97,17 @@ module.exports = function castUpdate(schema, obj, options, context) {
97
97
  * @param {Schema} schema
98
98
  * @param {Object} obj - part of a query
99
99
  * @param {String} op - the atomic operator ($pull, $set, etc)
100
- * @param {Boolean|String} strict
100
+ * @param {Object} options
101
+ * @param {Boolean|String} [options.strict]
102
+ * @param {Boolean} [options.omitUndefined]
101
103
  * @param {Query} context
102
104
  * @param {String} pref - path prefix (internal only)
103
105
  * @return {Bool} true if this path has keys to update
104
106
  * @api private
105
107
  */
106
108
 
107
- function walkUpdatePath(schema, obj, op, strict, context, pref) {
109
+ function walkUpdatePath(schema, obj, op, options, context, pref) {
110
+ var strict = options.strict;
108
111
  var prefix = pref ? pref + '.' : '';
109
112
  var keys = Object.keys(obj);
110
113
  var i = keys.length;
@@ -126,9 +129,8 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
126
129
  schematype = schema._getSchema(prefix + key);
127
130
  if (schematype && schematype.caster && op in castOps) {
128
131
  // embedded doc schema
129
- hasKeys = true;
130
-
131
132
  if ('$each' in val) {
133
+ hasKeys = true;
132
134
  try {
133
135
  obj[key] = {
134
136
  $each: castUpdateVal(schematype, val.$each, op, context)
@@ -154,6 +156,13 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
154
156
  } catch (error) {
155
157
  aggregatedError = _handleCastError(error, context, key, aggregatedError);
156
158
  }
159
+
160
+ if (options.omitUndefined && obj[key] === void 0) {
161
+ delete obj[key];
162
+ continue;
163
+ }
164
+
165
+ hasKeys = true;
157
166
  }
158
167
  } else if ((op === '$currentDate') || (op in castOps && schematype)) {
159
168
  // $currentDate can take an object
@@ -163,6 +172,11 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
163
172
  aggregatedError = _handleCastError(error, context, key, aggregatedError);
164
173
  }
165
174
 
175
+ if (options.omitUndefined && obj[key] === void 0) {
176
+ delete obj[key];
177
+ continue;
178
+ }
179
+
166
180
  hasKeys = true;
167
181
  } else {
168
182
  var pathToCheck = (prefix + key);
@@ -187,7 +201,7 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
187
201
  // gh-2314
188
202
  // we should be able to set a schema-less field
189
203
  // to an empty object literal
190
- hasKeys |= walkUpdatePath(schema, val, op, strict, context, prefix + key) ||
204
+ hasKeys |= walkUpdatePath(schema, val, op, options, context, prefix + key) ||
191
205
  (utils.isObject(val) && Object.keys(val).length === 0);
192
206
  }
193
207
  } else {
@@ -222,7 +236,6 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
222
236
  continue;
223
237
  }
224
238
 
225
- hasKeys = true;
226
239
  try {
227
240
  obj[key] = castUpdateVal(schematype, val, op, key, context);
228
241
  } catch (error) {
@@ -232,6 +245,13 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
232
245
  if (Array.isArray(obj[key]) && (op === '$addToSet' || op === '$push') && key !== '$each') {
233
246
  obj[key] = { $each: obj[key] };
234
247
  }
248
+
249
+ if (options.omitUndefined && obj[key] === void 0) {
250
+ delete obj[key];
251
+ continue;
252
+ }
253
+
254
+ hasKeys = true;
235
255
  }
236
256
  }
237
257
  }
@@ -1,12 +1,14 @@
1
+ 'use strict';
2
+
1
3
  /*!
2
4
  * Module dependencies.
3
5
  */
4
6
 
5
- var Mixed = require('../schema/mixed');
6
- var ValidationError = require('../error/validation');
7
- var parallel = require('async/parallel');
8
- var flatten = require('./common').flatten;
9
- var modifiedPaths = require('./common').modifiedPaths;
7
+ const Mixed = require('../schema/mixed');
8
+ const ValidationError = require('../error/validation');
9
+ const flatten = require('./common').flatten;
10
+ const modifiedPaths = require('./common').modifiedPaths;
11
+ const parallel = require('async/parallel');
10
12
 
11
13
  /**
12
14
  * Applies validators and defaults to update and findOneAndUpdate operations,
@@ -57,6 +59,11 @@ module.exports = function(query, schema, castedDoc, options) {
57
59
  var updatedPath = paths[j].replace('.$.', '.0.');
58
60
  updatedPath = updatedPath.replace(/\.\$$/, '.0');
59
61
  key = keys[i];
62
+ // With `$pull` we might flatten `$in`. Skip stuff nested under `$in`
63
+ // for the rest of the logic, it will get handled later.
64
+ if (updatedPath.indexOf('$') !== -1) {
65
+ continue;
66
+ }
60
67
  if (key === '$set' || key === '$setOnInsert' ||
61
68
  key === '$pull' || key === '$pullAll') {
62
69
  updatedValues[updatedPath] = flat[paths[j]];
@@ -86,19 +93,38 @@ module.exports = function(query, schema, castedDoc, options) {
86
93
  if (schemaPath instanceof Mixed && schemaPath.$fullPath !== updates[i]) {
87
94
  return;
88
95
  }
89
- validatorsToExecute.push(function(callback) {
90
- schemaPath.doValidate(
91
- v,
92
- function(err) {
93
- if (err) {
94
- err.path = updates[i];
95
- validationErrors.push(err);
96
- }
97
- callback(null);
98
- },
99
- options && options.context === 'query' ? query : null,
100
- {updateValidator: true});
101
- });
96
+
97
+ if (v && Array.isArray(v.$in)) {
98
+ v.$in.forEach((v, i) => {
99
+ validatorsToExecute.push(function(callback) {
100
+ schemaPath.doValidate(
101
+ v,
102
+ function(err) {
103
+ if (err) {
104
+ err.path = updates[i] + '.$in.' + i;
105
+ validationErrors.push(err);
106
+ }
107
+ callback(null);
108
+ },
109
+ options && options.context === 'query' ? query : null,
110
+ {updateValidator: true});
111
+ });
112
+ });
113
+ } else {
114
+ validatorsToExecute.push(function(callback) {
115
+ schemaPath.doValidate(
116
+ v,
117
+ function(err) {
118
+ if (err) {
119
+ err.path = updates[i];
120
+ validationErrors.push(err);
121
+ }
122
+ callback(null);
123
+ },
124
+ options && options.context === 'query' ? query : null,
125
+ {updateValidator: true});
126
+ });
127
+ }
102
128
  }
103
129
  }
104
130
  for (i = 0; i < numUpdates; ++i) {
@@ -10,6 +10,7 @@ const ObjectIdSchema = require('../schema/objectid');
10
10
  const internalToObjectOptions = require('../options').internalToObjectOptions;
11
11
  const utils = require('../utils');
12
12
  const Document = require('../document');
13
+ const getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByValue;
13
14
 
14
15
  /**
15
16
  * DocumentArray constructor
@@ -123,10 +124,18 @@ MongooseDocumentArray.mixin = {
123
124
 
124
125
  if (value &&
125
126
  Constructor.discriminators &&
126
- Constructor.schema.options.discriminatorKey &&
127
- typeof value[Constructor.schema.options.discriminatorKey] === 'string' &&
128
- Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]]) {
129
- Constructor = Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]];
127
+ Constructor.schema &&
128
+ Constructor.schema.options &&
129
+ Constructor.schema.options.discriminatorKey) {
130
+ if (typeof value[Constructor.schema.options.discriminatorKey] === 'string' &&
131
+ Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]]) {
132
+ Constructor = Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]];
133
+ } else {
134
+ var constructorByValue = getDiscriminatorByValue(Constructor, value[Constructor.schema.options.discriminatorKey]);
135
+ if (constructorByValue) {
136
+ Constructor = constructorByValue;
137
+ }
138
+ }
130
139
  }
131
140
 
132
141
  return new Constructor(value, this, undefined, undefined, index);
@@ -233,10 +242,18 @@ MongooseDocumentArray.mixin = {
233
242
  var Constructor = this._schema.casterConstructor;
234
243
  if (obj &&
235
244
  Constructor.discriminators &&
236
- Constructor.schema.options.discriminatorKey &&
237
- typeof obj[Constructor.schema.options.discriminatorKey] === 'string' &&
238
- Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]]) {
239
- Constructor = Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]];
245
+ Constructor.schema &&
246
+ Constructor.schema.options &&
247
+ Constructor.schema.options.discriminatorKey) {
248
+ if (typeof obj[Constructor.schema.options.discriminatorKey] === 'string' &&
249
+ Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]]) {
250
+ Constructor = Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]];
251
+ } else {
252
+ var constructorByValue = getDiscriminatorByValue(Constructor, obj[Constructor.schema.options.discriminatorKey]);
253
+ if (constructorByValue) {
254
+ Constructor = constructorByValue;
255
+ }
256
+ }
240
257
  }
241
258
 
242
259
  return new Constructor(obj);
package/lib/utils.js CHANGED
@@ -306,7 +306,7 @@ exports.random = function() {
306
306
  * @api private
307
307
  */
308
308
 
309
- exports.merge = function merge(to, from, options) {
309
+ exports.merge = function merge(to, from, options, path) {
310
310
  options = options || {};
311
311
 
312
312
  const keys = Object.keys(from);
@@ -314,18 +314,24 @@ exports.merge = function merge(to, from, options) {
314
314
  const len = keys.length;
315
315
  let key;
316
316
 
317
+ path = path || '';
318
+ const omitNested = options.omitNested || {};
319
+
317
320
  while (i < len) {
318
321
  key = keys[i++];
319
322
  if (options.omit && options.omit[key]) {
320
323
  continue;
321
324
  }
325
+ if (omitNested[path]) {
326
+ continue;
327
+ }
322
328
  if (to[key] == null) {
323
329
  to[key] = from[key];
324
330
  } else if (exports.isObject(from[key])) {
325
331
  if (!exports.isObject(to[key])) {
326
332
  to[key] = {};
327
333
  }
328
- merge(to[key], from[key], options);
334
+ merge(to[key], from[key], options, path ? path + '.' + key : key);
329
335
  } else if (options.overwrite) {
330
336
  to[key] = from[key];
331
337
  }
package/migrating_to_5.md CHANGED
@@ -33,6 +33,21 @@ mongoose.connect('mongodb://localhost:27017/test');
33
33
  mongoose.model('Test', new Schema({}));
34
34
  ```
35
35
 
36
+ ### Connection Logic and `useMongoClient`
37
+
38
+ The [`useMongoClient` option](http://mongoosejs.com/docs/4.x/docs/connections.html#use-mongo-client) was
39
+ removed in Mongoose 5, it is now always `true`. As a consequence, Mongoose 5
40
+ no longer supports several function signatures for `mongoose.connect()` that
41
+ worked in Mongoose 4.x if the `useMongoClient` option was off. Below are some
42
+ examples of `mongoose.connect()` calls that do **not** work in Mongoose 5.x.
43
+
44
+ * `mongoose.connect('localhost', 27017);`
45
+ * `mongoose.connect('localhost', 'mydb', 27017);`
46
+ * `mongoose.connect('mongodb://host1:27017,mongodb://host2:27017');`
47
+
48
+ In Mongoose 5.x, the first parameter to `mongoose.connect()` and `mongoose.createConnection()`, if specified, **must** be a [MongoDB connection string](https://docs.mongodb.com/manual/reference/connection-string/). The
49
+ connection string and options are then passed down to [the MongoDB Node.js driver's `MongoClient.connect()` function](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#.connect). Mongoose does not modify the connection string, although `mongoose.connect()` and `mongoose.createConnection()` support a [few additional options in addition to the ones the MongoDB driver supports](http://mongoosejs.com/docs/connections.html#options).
50
+
36
51
  ### Setter Order
37
52
 
38
53
  Setters run in reverse order in 4.x:
@@ -134,6 +149,36 @@ The above code does **not** work in 5.x, you **must** wrap the `$match` and `$sk
134
149
  MyModel.aggregate([{ $match: { isDeleted: false } }, { $skip: 10 }]).exec(cb);
135
150
  ```
136
151
 
152
+ ### Boolean Casting
153
+
154
+ By default, mongoose 4 would coerce any value to a boolean without error.
155
+
156
+ ```javascript
157
+ // Fine in mongoose 4, would save a doc with `boolField = true`
158
+ const MyModel = mongoose.model('Test', new Schema({
159
+ boolField: Boolean
160
+ }));
161
+
162
+ MyModel.create({ boolField: 'not a boolean' });
163
+ ```
164
+
165
+ Mongoose 5 only casts the following values to `true`:
166
+
167
+ * `true`
168
+ * `'true'`
169
+ * `1`
170
+ * `'1'`
171
+ * `'yes'`
172
+
173
+ And the following values to `false`:
174
+
175
+ * `false`
176
+ * `'false'`
177
+ * `0`
178
+ * `'0'`
179
+ * `'no'`
180
+
181
+ All other values will cause a `CastError`
137
182
  ### Query Casting
138
183
 
139
184
  Casting for `update()`, `updateOne()`, `updateMany()`, `replaceOne()`,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "5.0.7",
4
+ "version": "5.0.11",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",
@@ -23,7 +23,7 @@
23
23
  "bson": "~1.0.4",
24
24
  "kareem": "2.0.5",
25
25
  "lodash.get": "4.4.2",
26
- "mongodb": "3.0.2",
26
+ "mongodb": "3.0.4",
27
27
  "mongoose-legacy-pluralize": "1.0.2",
28
28
  "mpath": "0.3.0",
29
29
  "mquery": "3.0.0",
@@ -64,8 +64,8 @@
64
64
  "fix-lint": "eslint . --fix",
65
65
  "lint": "eslint . --quiet",
66
66
  "nsp": "nsp check",
67
- "release": "git push origin master --tags && npm publish",
68
- "release-legacy": "git push origin 4.x --tags && npm publish --tag legacy",
67
+ "release": "git pull && git push origin master --tags && npm publish",
68
+ "release-legacy": "git pull origin 4.x && git push origin 4.x --tags && npm publish --tag legacy",
69
69
  "test": "mocha --exit test/*.test.js test/**/*.test.js",
70
70
  "test-cov": "nyc --reporter=html --reporter=text npm test"
71
71
  },
@@ -81,7 +81,7 @@
81
81
  "url": "git://github.com/Automattic/mongoose.git"
82
82
  },
83
83
  "homepage": "http://mongoosejs.com",
84
- "browser": "lib/browser.js",
84
+ "browser": "./browser.js",
85
85
  "eslintConfig": {
86
86
  "extends": [
87
87
  "eslint:recommended"
@@ -1,17 +0,0 @@
1
- 'use strict';
2
-
3
- /* eslint-env browser */
4
-
5
- /*!
6
- * Module dependencies.
7
- */
8
- var BrowserDocument = require('./browserDocument.js');
9
-
10
- /**
11
- * Returns the Document constructor for the current context
12
- *
13
- * @api private
14
- */
15
- module.exports = function() {
16
- return BrowserDocument;
17
- };