mongoose 5.4.0 → 5.4.4

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,44 @@
1
+ 5.4.4 / 2019-01-14
2
+ ==================
3
+ * fix(query): run casting on arrayFilters option #7079
4
+ * fix(document): support skipping timestamps on save() with `save({ timestamps: false })` #7357
5
+ * fix(model): apply custom where on `Document#remove()` so we attach the shardKey #7393
6
+ * docs(mongoose): document `mongoose.connections` #7338
7
+
8
+ 5.4.3 / 2019-01-09
9
+ ==================
10
+ * fix(populate): handle `count` option when using `Document#populate()` on a virtual #7380
11
+ * fix(connection): set connection state to DISCONNECTED if replica set has no primary #7330
12
+ * fix(mongoose): apply global plugins to schemas nested underneath embedded discriminators #7370
13
+ * fix(document): make modifiedPaths() return nested paths 1 level down on initial set #7313
14
+ * fix(plugins): ensure sharding plugin works even if ObjectId has a `valueOf()` #7353
15
+
16
+ 5.4.2 / 2019-01-03
17
+ ==================
18
+ * fix(document): ensure Document#updateOne() returns a query but still calls hooks #7366
19
+ * fix(query): allow explicitly projecting out populated paths that are automatically projected in #7383
20
+ * fix(document): support setting `flattenMaps` option for `toObject()` and `toJSON()` at schema level #7274
21
+ * fix(query): handle merging objectids with `.where()` #7360
22
+ * fix(schema): copy `.base` when cloning #7377
23
+ * docs: remove links to plugins.mongoosejs.com in favor of plugins.mongoosejs.io #7364
24
+
25
+ 5.4.1 / 2018-12-26
26
+ ==================
27
+ * fix(document): ensure doc array defaults get casted #7337
28
+ * fix(document): make `save()` not crash if nested doc has a property 'get' #7316
29
+ * fix(schema): allow using Schema.Types.Map as well as Map to declare a map type #7305
30
+ * fix(map): make set after init mark correct path as modified #7321
31
+ * fix(mongoose): don't recompile model if same collection and schema passed in to `mongoose.model()` #5767
32
+ * fix(schema): improve error message when type is invalid #7303
33
+ * fix(schema): add `populated` to reserved property names #7317
34
+ * fix(model): don't run built-in middleware on custom methods and ensure timestamp hooks don't run if children don't have timestamps set #7342
35
+ * docs(schematypes): clarify that you can add arbitrary options to a SchemaType #7340
36
+ * docs(mongoose): clarify that passing same name+schema to `mongoose.model()` returns the model #5767
37
+ * docs(index): add useNewUrlParser to example #7368 [JIBIN-P](https://github.com/JIBIN-P)
38
+ * docs(connection): add useNewUrlParser to examples #7362 [JIBIN-P](https://github.com/JIBIN-P)
39
+ * docs(discriminators): add back missing example from 'recursive embedded discriminators section' #7349
40
+ * docs(schema): improve docs for string and boolean cast() #7351
41
+
1
42
  5.4.0 / 2018-12-14
2
43
  ==================
3
44
  * feat(schematype): add `SchemaType.get()`, custom getters across all instances of a schematype #6912
package/lib/cast.js CHANGED
@@ -7,7 +7,7 @@
7
7
  const StrictModeError = require('./error/strict');
8
8
  const Types = require('./schema/index');
9
9
  const castTextSearch = require('./schema/operators/text');
10
- const get = require('lodash.get');
10
+ const get = require('./helpers/get');
11
11
  const util = require('util');
12
12
  const utils = require('./utils');
13
13
 
@@ -82,6 +82,7 @@ module.exports = function cast(schema, obj, options, context) {
82
82
  const pathLastHalf = split.slice(j).join('.');
83
83
  const _schematype = schema.path(pathFirstHalf);
84
84
  const discriminatorKey = get(_schematype, 'schema.options.discriminatorKey');
85
+
85
86
  // gh-6027: if we haven't found the schematype but this path is
86
87
  // underneath an embedded discriminator and the embedded discriminator
87
88
  // key is in the query, use the embedded discriminator schema
@@ -90,7 +91,7 @@ module.exports = function cast(schema, obj, options, context) {
90
91
  discriminatorKey != null &&
91
92
  pathLastHalf !== discriminatorKey) {
92
93
  const discriminatorVal = get(obj, pathFirstHalf + '.' + discriminatorKey);
93
- if (discriminatorVal) {
94
+ if (discriminatorVal != null) {
94
95
  schematype = _schematype.schema.discriminators[discriminatorVal].
95
96
  path(pathLastHalf);
96
97
  }
@@ -336,4 +337,4 @@ function _cast(val, numbertype, context) {
336
337
  }
337
338
  }
338
339
  }
339
- }
340
+ }
package/lib/connection.js CHANGED
@@ -549,6 +549,12 @@ Connection.prototype.openUri = function(uri, options, callback) {
549
549
  // Implicitly emits 'disconnected'
550
550
  _this.readyState = STATES.disconnected;
551
551
  });
552
+ client.on('left', function() {
553
+ if (_this.readyState === STATES.connected &&
554
+ get(db, 's.topology.s.coreTopology.s.replicaSetState.topologyType') === 'ReplicaSetNoPrimary') {
555
+ _this.readyState = STATES.disconnected;
556
+ }
557
+ });
552
558
  db.on('timeout', function() {
553
559
  _this.emit('timeout');
554
560
  });
package/lib/document.js CHANGED
@@ -33,6 +33,7 @@ const deepEqual = utils.deepEqual;
33
33
  const isMongooseObject = utils.isMongooseObject;
34
34
 
35
35
  const documentArrayParent = require('./helpers/symbols').documentArrayParent;
36
+ const getSymbol = require('./helpers/symbols').getSymbol;
36
37
 
37
38
  let DocumentArray;
38
39
  let MongooseArray;
@@ -239,10 +240,9 @@ function $__hasIncludedChildren(fields) {
239
240
  * ignore
240
241
  */
241
242
 
242
- function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip, options) {
243
+ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) {
243
244
  const paths = Object.keys(doc.schema.paths);
244
245
  const plen = paths.length;
245
- const init = options && 'isNew' in options ? options.isNew : false;
246
246
 
247
247
  for (let i = 0; i < plen; ++i) {
248
248
  let def;
@@ -307,21 +307,21 @@ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isB
307
307
  continue;
308
308
  }
309
309
 
310
- def = type.getDefault(doc, init);
310
+ def = type.getDefault(doc, false);
311
311
  if (typeof def !== 'undefined') {
312
312
  doc_[piece] = def;
313
313
  doc.$__.activePaths.default(p);
314
314
  }
315
315
  } else if (included) {
316
316
  // selected field
317
- def = type.getDefault(doc, init);
317
+ def = type.getDefault(doc, false);
318
318
  if (typeof def !== 'undefined') {
319
319
  doc_[piece] = def;
320
320
  doc.$__.activePaths.default(p);
321
321
  }
322
322
  }
323
323
  } else {
324
- def = type.getDefault(doc, init);
324
+ def = type.getDefault(doc, false);
325
325
  if (typeof def !== 'undefined') {
326
326
  doc_[piece] = def;
327
327
  doc.$__.activePaths.default(p);
@@ -595,16 +595,19 @@ Document.prototype.update = function update() {
595
595
  */
596
596
 
597
597
  Document.prototype.updateOne = function updateOne(doc, options, callback) {
598
- return utils.promiseOrCallback(callback,
599
- cb => this.$__updateOne(doc, options, cb), this.constructor.events);
600
- };
598
+ const query = this.constructor.updateOne({_id: this._id}, doc, options);
599
+ query._pre(cb => {
600
+ this.constructor._middleware.execPre('updateOne', this, [], cb);
601
+ });
602
+ query._post(cb => {
603
+ this.constructor._middleware.execPost('updateOne', this, [], {}, cb);
604
+ });
601
605
 
602
- /*!
603
- * ignore
604
- */
606
+ if (callback != null) {
607
+ return query.exec(callback);
608
+ }
605
609
 
606
- Document.prototype.$__updateOne = function $__updateOne(doc, options, callback) {
607
- return this.constructor.updateOne({ _id: this._id }, doc, options, callback);
610
+ return query;
608
611
  };
609
612
 
610
613
  /**
@@ -908,9 +911,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
908
911
  } else {
909
912
  for (i = 0; i < parts.length; ++i) {
910
913
  const subpath = parts.slice(0, i + 1).join('.');
911
- if (this.isDirectModified(subpath) // earlier prefixes that are already
912
- // marked as dirty have precedence
913
- || this.get(subpath) === null) {
914
+ if (this.get(subpath) === null) {
914
915
  pathToMark = subpath;
915
916
  break;
916
917
  }
@@ -1282,7 +1283,7 @@ Document.prototype.get = function(path, type, options) {
1282
1283
  obj = adhoc.cast(obj);
1283
1284
  }
1284
1285
 
1285
- if (schema) {
1286
+ if (schema != null) {
1286
1287
  obj = schema.applyGetters(obj, this);
1287
1288
  } else if (this.schema.nested[path] && options.virtuals) {
1288
1289
  // Might need to apply virtuals if this is a nested path
@@ -1292,6 +1293,12 @@ Document.prototype.get = function(path, type, options) {
1292
1293
  return obj;
1293
1294
  };
1294
1295
 
1296
+ /*!
1297
+ * ignore
1298
+ */
1299
+
1300
+ Document.prototype[getSymbol] = Document.prototype.get;
1301
+
1295
1302
  /**
1296
1303
  * Returns the schematype for the given `path`.
1297
1304
  *
@@ -1304,7 +1311,7 @@ Document.prototype.get = function(path, type, options) {
1304
1311
 
1305
1312
  Document.prototype.$__path = function(path) {
1306
1313
  const adhocs = this.$__.adhocPaths;
1307
- const adhocType = adhocs && adhocs[path];
1314
+ const adhocType = adhocs && adhocs.hasOwnProperty(path) ? adhocs[path] : null;
1308
1315
 
1309
1316
  if (adhocType) {
1310
1317
  return adhocType;
@@ -2462,6 +2469,10 @@ Document.prototype.$toObject = function(options, json) {
2462
2469
  clone(options) :
2463
2470
  {};
2464
2471
 
2472
+ if (!('flattenMaps' in options)) {
2473
+ options.flattenMaps = defaultOptions.flattenMaps;
2474
+ }
2475
+
2465
2476
  let _minimize;
2466
2477
  if (options.minimize != null) {
2467
2478
  _minimize = options.minimize;
@@ -1,9 +1,11 @@
1
1
  'use strict';
2
2
 
3
- let Document;
4
- const get = require('lodash.get');
3
+ const get = require('../../helpers/get');
4
+ const getSymbol = require('../../helpers/symbols').getSymbol;
5
5
  const utils = require('../../utils');
6
6
 
7
+ let Document;
8
+
7
9
  /*!
8
10
  * exports
9
11
  */
@@ -118,7 +120,7 @@ function defineKey(prop, subprops, prototype, prefix, keys, options) {
118
120
  enumerable: true,
119
121
  configurable: true,
120
122
  get: function() {
121
- return this.get.call(this.$__.scope || this, path);
123
+ return this[getSymbol].call(this.$__.scope || this, path);
122
124
  },
123
125
  set: function(v) {
124
126
  return this.$set.call(this.$__.scope || this, path, v);
@@ -7,13 +7,22 @@
7
7
 
8
8
  module.exports = function get(obj, path, def) {
9
9
  const parts = path.split('.');
10
+ let rest = path;
10
11
  let cur = obj;
11
12
  for (const part of parts) {
12
13
  if (cur == null) {
13
14
  return def;
14
15
  }
15
16
 
17
+ // `lib/cast.js` depends on being able to get dotted paths in updates,
18
+ // like `{ $set: { 'a.b': 42 } }`
19
+ if (cur[rest] != null) {
20
+ return cur[rest];
21
+ }
22
+
16
23
  cur = getProperty(cur, part);
24
+
25
+ rest = rest.substr(part.length + 1);
17
26
  }
18
27
 
19
28
  return cur == null ? def : cur;
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const symbols = require('../../schema/symbols');
3
4
  const utils = require('../../utils');
4
5
 
5
6
  /*!
@@ -58,15 +59,26 @@ function applyHooks(model, schema, options) {
58
59
  // promises and make it so that `doc.save.toString()` provides meaningful
59
60
  // information.
60
61
 
61
- const middleware = schema.s.hooks.filter(hook => {
62
- if (hook.name === 'updateOne') {
63
- return !!hook['document'];
64
- }
65
- if (hook.name === 'remove') {
66
- return hook['document'] == null || !!hook['document'];
67
- }
68
- return true;
69
- });
62
+ const middleware = schema.s.hooks.
63
+ filter(hook => {
64
+ if (hook.name === 'updateOne') {
65
+ return !!hook['document'];
66
+ }
67
+ if (hook.name === 'remove') {
68
+ return hook['document'] == null || !!hook['document'];
69
+ }
70
+ return true;
71
+ }).
72
+ filter(hook => {
73
+ // If user has overwritten the method, don't apply built-in middleware
74
+ if (schema.methods[hook.name]) {
75
+ return !hook.fn[symbols.builtInMiddleware];
76
+ }
77
+
78
+ return true;
79
+ });
80
+
81
+ model._middleware = middleware;
70
82
 
71
83
  objToDecorate.$__save = middleware.
72
84
  createWrapper('save', objToDecorate.$__save, null, kareemOptions);
@@ -74,8 +86,6 @@ function applyHooks(model, schema, options) {
74
86
  createWrapper('validate', objToDecorate.$__validate, null, kareemOptions);
75
87
  objToDecorate.$__remove = middleware.
76
88
  createWrapper('remove', objToDecorate.$__remove, null, kareemOptions);
77
- objToDecorate.$__updateOne = middleware.
78
- createWrapper('updateOne', objToDecorate.$__updateOne, null, kareemOptions);
79
89
  objToDecorate.$__init = middleware.
80
90
  createWrapperSync('init', objToDecorate.$__init, null, kareemOptions);
81
91
 
@@ -104,7 +114,7 @@ function applyHooks(model, schema, options) {
104
114
  return utils.promiseOrCallback(cb, callback => {
105
115
  this[`$__${method}`].apply(this,
106
116
  argsWithoutCallback.concat([callback]));
107
- });
117
+ }, model.events);
108
118
  };
109
119
  objToDecorate[`$__${method}`] = middleware.
110
120
  createWrapper(method, originalMethod, null, customMethodOptions);
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const defineKey = require('../document/compile').defineKey;
4
+ const get = require('../../helpers/get');
4
5
  const utils = require('../../utils');
5
6
 
6
7
  const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
@@ -19,9 +20,11 @@ module.exports = function discriminator(model, name, schema, tiedValue) {
19
20
  throw new Error('You must pass a valid discriminator Schema');
20
21
  }
21
22
 
22
- if (model.base && model.base.options.applyPluginsToDiscriminators) {
23
- model.base._applyPlugins(schema);
24
- }
23
+ const applyPluginsToDiscriminators = get(model.base,
24
+ 'options.applyPluginsToDiscriminators', false);
25
+ // Even if `applyPluginsToDiscriminators` isn't set, we should still apply
26
+ // global plugins to schemas embedded in the discriminator schema (gh-7370)
27
+ model.base._applyPlugins(schema, { skipTopLevel: !applyPluginsToDiscriminators });
25
28
 
26
29
  if (model.schema.discriminatorMapping &&
27
30
  !model.schema.discriminatorMapping.isRoot) {
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ module.exports = function once(fn) {
4
+ let called = false;
5
+ return function() {
6
+ if (called) {
7
+ return;
8
+ }
9
+ called = true;
10
+ return fn.apply(null, arguments);
11
+ };
12
+ };
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ module.exports = function castFilterPath(query, schematype, val) {
4
+ const any$conditionals = Object.keys(val).some(function(k) {
5
+ return k.charAt(0) === '$' && k !== '$id' && k !== '$ref';
6
+ });
7
+
8
+ if (!any$conditionals) {
9
+ return schematype.castForQueryWrapper({
10
+ val: val,
11
+ context: query
12
+ });
13
+ }
14
+
15
+ const ks = Object.keys(val);
16
+
17
+ let k = ks.length;
18
+
19
+ while (k--) {
20
+ const $cond = ks[k];
21
+ const nested = val[$cond];
22
+
23
+ if ($cond === '$not') {
24
+ if (nested && schematype && !schematype.caster) {
25
+ const _keys = Object.keys(nested);
26
+ if (_keys.length && _keys[0].charAt(0) === '$') {
27
+ for (const key in nested) {
28
+ nested[key] = schematype.castForQueryWrapper({
29
+ $conditional: key,
30
+ val: nested[key],
31
+ context: context
32
+ });
33
+ }
34
+ } else {
35
+ val[$cond] = schematype.castForQueryWrapper({
36
+ $conditional: $cond,
37
+ val: nested,
38
+ context: context
39
+ });
40
+ }
41
+ continue;
42
+ }
43
+ // cast(schematype.caster ? schematype.caster.schema : schema, nested, options, context);
44
+ } else {
45
+ val[$cond] = schematype.castForQueryWrapper({
46
+ $conditional: $cond,
47
+ val: nested,
48
+ context: context
49
+ });
50
+ }
51
+ }
52
+
53
+ return val;
54
+ };
@@ -9,16 +9,17 @@ module.exports = function selectPopulatedFields(query) {
9
9
 
10
10
  if (opts.populate != null) {
11
11
  const paths = Object.keys(opts.populate);
12
- let i;
13
12
  const userProvidedFields = query._userProvidedFields || {};
14
13
  if (query.selectedInclusively()) {
15
- for (i = 0; i < paths.length; ++i) {
14
+ for (let i = 0; i < paths.length; ++i) {
16
15
  if (!isPathInFields(userProvidedFields, paths[i])) {
17
16
  query.select(paths[i]);
17
+ } else if (userProvidedFields[paths[i]] === 0) {
18
+ delete query._fields[paths[i]];
18
19
  }
19
20
  }
20
21
  } else if (query.selectedExclusively()) {
21
- for (i = 0; i < paths.length; ++i) {
22
+ for (let i = 0; i < paths.length; ++i) {
22
23
  if (userProvidedFields[paths[i]] == null) {
23
24
  delete query._fields[paths[i]];
24
25
  }
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ /*!
4
+ * A query thunk is the function responsible for sending the query to MongoDB,
5
+ * like `Query#_findOne()` or `Query#_execUpdate()`. The `Query#exec()` function
6
+ * calls a thunk. The term "thunk" here is the traditional Node.js definition:
7
+ * a function that takes exactly 1 parameter, a callback.
8
+ *
9
+ * This function defines common behavior for all query thunks.
10
+ */
11
+
12
+ module.exports = function wrapThunk(fn) {
13
+ return function _wrappedThunk(cb) {
14
+ ++this._executionCount;
15
+
16
+ fn.call(this, cb);
17
+ };
18
+ };
@@ -4,4 +4,8 @@ exports.validatorErrorSymbol = Symbol.for('mongoose:validatorError');
4
4
 
5
5
  exports.documentArrayParent = Symbol.for('mongoose:documentArrayParent');
6
6
 
7
- exports.modelSymbol = Symbol.for('mongoose#Model');
7
+ exports.modelSymbol = Symbol.for('mongoose#Model');
8
+
9
+ exports.getSymbol = Symbol.for('mongoose#Document#get');
10
+
11
+ exports.objectIdSymbol = Symbol.for('mongoose#ObjectId');
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ const castFilterPath = require('../query/castFilterPath');
4
+ const modifiedPaths = require('./modifiedPaths');
5
+
6
+ module.exports = function castArrayFilters(query) {
7
+ const arrayFilters = query.options.arrayFilters;
8
+ if (!Array.isArray(arrayFilters)) {
9
+ return;
10
+ }
11
+
12
+ const update = query.getUpdate();
13
+ const schema = query.schema;
14
+
15
+ const updatedPaths = modifiedPaths(update);
16
+
17
+ const updatedPathsByFilter = Object.keys(updatedPaths).reduce((cur, path) => {
18
+ const matches = path.match(/\$\[[^\]]+\]/g);
19
+ if (matches == null) {
20
+ return cur;
21
+ }
22
+ for (const match of matches) {
23
+ const firstMatch = path.indexOf(match);
24
+ if (firstMatch !== path.lastIndexOf(match)) {
25
+ throw new Error(`Path '${path}' contains the same array filter multiple times`);
26
+ }
27
+ cur[match.substring(2, match.length - 1)] = path.substr(0, firstMatch - 1);
28
+ }
29
+ return cur;
30
+ }, {});
31
+
32
+ for (const filter of arrayFilters) {
33
+ if (filter == null) {
34
+ throw new Error(`Got null array filter in ${arrayFilters}`);
35
+ }
36
+ const firstKey = Object.keys(filter)[0];
37
+
38
+ if (filter[firstKey] == null) {
39
+ continue;
40
+ }
41
+
42
+ const dot = firstKey.indexOf('.');
43
+ let filterPath = dot === -1 ?
44
+ updatedPathsByFilter[firstKey] + '.0' :
45
+ updatedPathsByFilter[firstKey.substr(0, dot)] + '.0' + firstKey.substr(dot);
46
+
47
+ if (filterPath == null) {
48
+ throw new Error(`Filter path not found for ${firstKey}`);
49
+ }
50
+
51
+ // If there are multiple array filters in the path being updated, make sure
52
+ // to replace them so we can get the schema path.
53
+ filterPath = filterPath.replace(/\$\[[^\]]+\]/g, '0');
54
+
55
+ const schematype = schema.path(filterPath);
56
+ if (typeof filter[firstKey] === 'object') {
57
+ filter[firstKey] = castFilterPath(query, schematype, filter[firstKey]);
58
+ } else {
59
+ filter[firstKey] = schematype.castForQuery(filter[firstKey]);
60
+ }
61
+ }
62
+ };
package/lib/index.js CHANGED
@@ -335,20 +335,32 @@ Mongoose.prototype.pluralize = function(fn) {
335
335
  /**
336
336
  * Defines a model or retrieves it.
337
337
  *
338
- * Models defined on the `mongoose` instance are available to all connection created by the same `mongoose` instance.
338
+ * Models defined on the `mongoose` instance are available to all connection
339
+ * created by the same `mongoose` instance.
340
+ *
341
+ * If you call `mongoose.model()` with twice the same name but a different schema,
342
+ * you will get an `OverwriteModelError`. If you call `mongoose.model()` with
343
+ * the same name and same schema, you'll get the same schema back.
339
344
  *
340
345
  * ####Example:
341
346
  *
342
347
  * var mongoose = require('mongoose');
343
348
  *
344
349
  * // define an Actor model with this mongoose instance
345
- * mongoose.model('Actor', new Schema({ name: String }));
350
+ * const Schema = new Schema({ name: String });
351
+ * mongoose.model('Actor', schema);
346
352
  *
347
353
  * // create a new connection
348
354
  * var conn = mongoose.createConnection(..);
349
355
  *
350
- * // retrieve the Actor model
351
- * var Actor = conn.model('Actor');
356
+ * // create Actor model
357
+ * var Actor = conn.model('Actor', schema);
358
+ * conn.model('Actor') === Actor; // true
359
+ * conn.model('Actor', schema) === Actor; // true, same schema
360
+ * conn.model('Actor', schema, 'actors') === Actor; // true, same schema and collection name
361
+ *
362
+ * // This throws an `OverwriteModelError` because the schema is different.
363
+ * conn.model('Actor', new Schema({ name: String }));
352
364
  *
353
365
  * _When no `collection` argument is passed, Mongoose uses the model name. If you don't like this behavior, either pass a collection name, use `mongoose.pluralize()`, or set your schemas collection name option._
354
366
  *
@@ -366,10 +378,10 @@ Mongoose.prototype.pluralize = function(fn) {
366
378
  * var M = mongoose.model('Actor', schema, collectionName)
367
379
  *
368
380
  * @param {String|Function} name model name or class extending Model
369
- * @param {Schema} [schema]
381
+ * @param {Schema} [schema] the schema to use.
370
382
  * @param {String} [collection] name (optional, inferred from model name)
371
383
  * @param {Boolean} [skipInit] whether to skip initialization (defaults to false)
372
- * @return {Model}
384
+ * @return {Model} The model associated with `name`. Mongoose will create the model if it doesn't already exist.
373
385
  * @api public
374
386
  */
375
387
 
@@ -441,7 +453,7 @@ Mongoose.prototype.model = function(name, schema, collection, skipInit) {
441
453
  throw new _mongoose.Error.OverwriteModelError(name);
442
454
  }
443
455
 
444
- if (collection) {
456
+ if (collection && collection !== _mongoose.models[name].collection.name) {
445
457
  // subclass current model with alternate collection
446
458
  model = _mongoose.models[name];
447
459
  schema = model.prototype.schema;
@@ -539,16 +551,18 @@ Mongoose.prototype.modelNames = function() {
539
551
  * @api private
540
552
  */
541
553
 
542
- Mongoose.prototype._applyPlugins = function(schema) {
554
+ Mongoose.prototype._applyPlugins = function(schema, options) {
543
555
  if (schema.$globalPluginsApplied) {
544
556
  return;
545
557
  }
546
558
  let i;
547
559
  let len;
548
- for (i = 0, len = this.plugins.length; i < len; ++i) {
549
- schema.plugin(this.plugins[i][0], this.plugins[i][1]);
560
+ if (!options || !options.skipTopLevel) {
561
+ for (i = 0, len = this.plugins.length; i < len; ++i) {
562
+ schema.plugin(this.plugins[i][0], this.plugins[i][1]);
563
+ }
564
+ schema.$globalPluginsApplied = true;
550
565
  }
551
- schema.$globalPluginsApplied = true;
552
566
  for (i = 0, len = schema.childSchemas.length; i < len; ++i) {
553
567
  this._applyPlugins(schema.childSchemas[i].schema);
554
568
  }
@@ -572,7 +586,7 @@ Mongoose.prototype.plugin = function(fn, opts) {
572
586
  };
573
587
 
574
588
  /**
575
- * The default connection of the mongoose module.
589
+ * The Mongoose module's default connection. Equivalent to `mongoose.connections][0]`, see [`connections`](#mongoose_Mongoose-connections).
576
590
  *
577
591
  * ####Example:
578
592
  *
@@ -582,10 +596,11 @@ Mongoose.prototype.plugin = function(fn, opts) {
582
596
  *
583
597
  * This is the connection used by default for every model created using [mongoose.model](#index_Mongoose-model).
584
598
  *
599
+ * To create a new connection, use [`createConnection()`](#mongoose_Mongoose-createConnection).
600
+ *
585
601
  * @memberOf Mongoose
586
602
  * @instance
587
- * @property connection
588
- * @return {Connection}
603
+ * @property {Connection} connection
589
604
  * @api public
590
605
  */
591
606
 
@@ -600,6 +615,29 @@ Mongoose.prototype.__defineSetter__('connection', function(v) {
600
615
  }
601
616
  });
602
617
 
618
+ /**
619
+ * An array containing all [connections](connections.html) associated with this
620
+ * Mongoose instance. By default, there is 1 connection. Calling
621
+ * [`createConnection()`](#mongoose_Mongoose-createConnection) adds a connection
622
+ * to this array.
623
+ *
624
+ * ####Example:
625
+ *
626
+ * const mongoose = require('mongoose');
627
+ * mongoose.connections.length; // 1, just the default connection
628
+ * mongoose.connections[0] === mongoose.connection; // true
629
+ *
630
+ * mongoose.createConnection('mongodb://localhost:27017/test');
631
+ * mongoose.connections.length; // 2
632
+ *
633
+ * @memberOf Mongoose
634
+ * @instance
635
+ * @property {Array} connections
636
+ * @api public
637
+ */
638
+
639
+ Mongoose.prototype.connections;
640
+
603
641
  /*!
604
642
  * Driver dependent APIs
605
643
  */