diff --git a/docs/change-streams.md b/docs/change-streams.md
index dc7eaeb9515..bc1424ccf12 100644
--- a/docs/change-streams.md
+++ b/docs/change-streams.md
@@ -65,9 +65,7 @@ exports.handler = async (event, context) => {
context.callbackWaitsForEmptyEventLoop = false;
await connectToDatabase();
-
- // Use MongoDB Node driver's `watch()` function, because Mongoose change streams
- // don't support `next()` yet. See https://github.com/Automattic/mongoose/issues/11527
+
const changeStream = await Country.watch([], { resumeAfter });
// Change stream `next()` will wait forever if there are no changes. So make sure to
diff --git a/docs/models.md b/docs/models.md
index f72f716154a..565cc2d8a7b 100644
--- a/docs/models.md
+++ b/docs/models.md
@@ -1,4 +1,4 @@
-## Models
+# Models
[Models](./api.html#model-js) are fancy constructors compiled from
`Schema` definitions. An instance of a model is called a
@@ -11,8 +11,9 @@ reading documents from the underlying MongoDB database.
* [Deleting](#deleting)
* [Updating](#updating)
* [Change Streams](#change-streams)
+* [Views](#views)
-
+Compiling your first model
When you call `mongoose.model()` on a schema, Mongoose _compiles_ a model
for you.
@@ -31,7 +32,7 @@ in the database.
you've added everything you want to `schema`, including hooks,
before calling `.model()`!
-### Constructing Documents
+## Constructing Documents
An instance of a model is called a [document](./documents.html). Creating
them and saving to the database is easy.
@@ -73,7 +74,7 @@ const connection = mongoose.createConnection('mongodb://localhost:27017/test');
const Tank = connection.model('Tank', yourSchema);
```
-### Querying
+## Querying
Finding documents is easy with Mongoose, which supports the [rich](http://www.mongodb.org/display/DOCS/Advanced+Queries) query syntax of MongoDB. Documents can be retrieved using a `model`'s [find](./api.html#model_Model-find), [findById](./api.html#model_Model-findById), [findOne](./api.html#model_Model-findOne), or [where](./api.html#model_Model-where) static methods.
@@ -83,7 +84,7 @@ Tank.find({ size: 'small' }).where('createdDate').gt(oneYearAgo).exec(callback);
See the chapter on [queries](./queries.html) for more details on how to use the [Query](./api.html#query-js) api.
-### Deleting
+## Deleting
Models have static `deleteOne()` and `deleteMany()` functions
for removing all documents matching the given `filter`.
@@ -95,7 +96,7 @@ Tank.deleteOne({ size: 'large' }, function (err) {
});
```
-### Updating
+## Updating
Each `model` has its own `update` method for modifying documents in the
database without returning them to your application. See the
@@ -112,9 +113,7 @@ _If you want to update a single document in the db and return it to your
application, use [findOneAndUpdate](./api.html#model_Model-findOneAndUpdate)
instead._
-### Change Streams
-
-_New in MongoDB 3.6.0 and Mongoose 5.0.0_
+## Change Streams
[Change streams](https://docs.mongodb.com/manual/changeStreams/) provide
a way for you to listen to all inserts and updates going through your
@@ -154,10 +153,58 @@ The output from the above [async function](http://thecodebarbarian.com/80-20-gui
You can read more about [change streams in mongoose in this blog post](http://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-change-streams.html#change-streams-in-mongoose).
-### Yet more
+## Views
+
+[MongoDB Views](https://www.mongodb.com/docs/manual/core/views) are essentially read-only collections that contain data computed from other collections using [aggregations](./api/aggregate.html).
+In Mongoose, you should define a separate Model for each of your Views.
+You can also create a View using [`createCollection()`](./api/model.html#model_Model-createCollection).
+
+The following example shows how you can create a new `RedactedUser` View on a `User` Model that hides potentially sensitive information, like name and email.
+
+```javascript
+// Make sure to disable `autoCreate` and `autoIndex` for Views,
+// because you want to create the collection manually.
+const userSchema = new Schema({
+ name: String,
+ email: String,
+ roles: [String]
+}, { autoCreate: false, autoIndex: false });
+const User = mongoose.model('User', userSchema);
+
+const RedactedUser = mongoose.model('RedactedUser', userSchema);
+
+// First, create the User model's underlying collection...
+await User.createCollection();
+// Then create the `RedactedUser` model's underlying collection
+// as a View.
+await RedactedUser.createCollection({
+ viewOn: 'users', // Set `viewOn` to the collection name, **not** model name.
+ pipeline: [
+ {
+ $set: {
+ name: { $concat: [{ $substr: ['$name', 0, 3] }, '...'] },
+ email: { $concat: [{ $substr: ['$email', 0, 3] }, '...'] }
+ }
+ }
+ ]
+});
+
+await User.create([
+ { name: 'John Smith', email: 'john.smith@gmail.com', roles: ['user'] },
+ { name: 'Bill James', email: 'bill@acme.co', roles: ['user', 'admin'] }
+]);
+
+// [{ _id: ..., name: 'Bil...', email: 'bil...', roles: ['user', 'admin'] }]
+console.log(await RedactedUser.find({ roles: 'admin' }));
+```
+
+Note that Mongoose does **not** currently enforce that Views are read-only.
+If you attempt to `save()` a document from a View, you will get an error from the MongoDB server.
+
+## Yet more
The [API docs](./api.html#model_Model) cover many additional methods available like [count](./api.html#model_Model-count), [mapReduce](./api.html#model_Model-mapReduce), [aggregate](./api.html#model_Model-aggregate), and [more](./api.html#model_Model-findOneAndRemove).
-### Next Up
+## Next Up
Now that we've covered `Models`, let's take a look at [Documents](/docs/documents.html).
diff --git a/docs/subdocs.md b/docs/subdocs.md
index 64b03bd19d3..7068e9c14da 100644
--- a/docs/subdocs.md
+++ b/docs/subdocs.md
@@ -11,15 +11,31 @@ const childSchema = new Schema({ name: 'string' });
const parentSchema = new Schema({
// Array of subdocuments
children: [childSchema],
- // Single nested subdocuments. Caveat: single nested subdocs only work
- // in mongoose >= 4.2.0
+ // Single nested subdocuments
child: childSchema
});
```
-Aside from code reuse, one important reason to use subdocuments is to create
-a path where there would otherwise not be one to allow for validation over
-a group of fields (e.g. dateRange.fromDate <= dateRange.toDate).
+Note that populated documents are **not** subdocuments in Mongoose.
+Subdocument data is embedded in the top-level document.
+Referenced documents are separate top-level documents.
+
+```javascript
+const childSchema = new Schema({ name: 'string' });
+const Child = mongoose.model('Child', childSchema);
+
+const parentSchema = new Schema({
+ child: {
+ type: mongoose.ObjectId,
+ ref: 'Child'
+ }
+});
+const Parent = mongoose.model('Parent', parentSchema);
+
+const doc = await Parent.findOne().populate('child');
+// NOT a subdocument. `doc.child` is a separate top-level document.
+doc.child;
+```
- What is a Subdocument?
diff --git a/lib/schema/SubdocumentPath.js b/lib/schema/SubdocumentPath.js
index e748d6a5026..1eb65207d85 100644
--- a/lib/schema/SubdocumentPath.js
+++ b/lib/schema/SubdocumentPath.js
@@ -172,6 +172,7 @@ SubdocumentPath.prototype.cast = function(val, doc, init, priorVal, options) {
options = Object.assign({}, options, { priorDoc: priorVal });
if (init) {
subdoc = new Constructor(void 0, selected, doc, false, { defaults: false });
+ delete subdoc.$__.defaults;
subdoc.$init(val);
applyDefaults(subdoc, selected);
} else {
diff --git a/test/document.test.js b/test/document.test.js
index ca2aca278d9..2d38adc8629 100644
--- a/test/document.test.js
+++ b/test/document.test.js
@@ -11865,6 +11865,35 @@ describe('document', function() {
assert.strictEqual(called, 1);
});
+ it('applies defaults to pushed subdocs after initing document (gh-12515)', async function() {
+ const animalSchema = new Schema({ title: String });
+ const animalsSchema = new Schema({
+ species: [animalSchema],
+ totalAnimals: Number
+ });
+ const Userschema = new Schema({
+ animals: animalsSchema
+ });
+ const UserModel = db.model('User', Userschema);
+
+ const doc = new UserModel();
+ doc.animals = { totalAnimals: 1 };
+ doc.animals.species = [{ title: 'Lion' }];
+ await doc.save();
+ // once created we fetch it again
+ let user = await UserModel.findById(doc._id);
+
+ // add new animal
+ user.animals.species.push({ title: 'Elephant' });
+ await user.save();
+ assert.ok(user.animals.species[0]._id);
+ assert.ok(user.animals.species[1]._id);
+ user = await UserModel.collection.findOne({ _id: user._id });
+
+ assert.ok(user.animals.species[0]._id);
+ assert.ok(user.animals.species[1]._id);
+ });
+
it('If the field does not exist, $inc should create it and set is value to the specified one (gh-12435)', async function() {
const schema = new mongoose.Schema({
name: String,
diff --git a/test/mocha-fixtures.js b/test/mocha-fixtures.js
index 2e537071c52..fba36267bcb 100644
--- a/test/mocha-fixtures.js
+++ b/test/mocha-fixtures.js
@@ -12,7 +12,7 @@ let mongoinstance;
// a replset-instance for running repl-set tests
let mongorreplset;
-// decide wheter to start a in-memory or not
+// decide whether to start MMS instances or use the existing URI's
const startMemoryInstance = !process.env.MONGOOSE_TEST_URI;
const startMemoryReplset = !process.env.MONGOOSE_REPLSET_URI;
@@ -24,11 +24,11 @@ module.exports.mochaGlobalSetup = async function mochaGlobalSetup() {
// set some options when running in a CI
if (process.env.CI) {
+ // this option is also set in the github-ci tests.yml, but this is just to ensure it is in any CI
process.env.MONGOMS_PREFER_GLOBAL_PATH = '1'; // set MMS to use "~/.cache/mongodb-binaries" even when the path does not yet exist
}
- if (startMemoryInstance) { // Config to decided if an mongodb-memory-server instance should be used
- // it's needed in global space, because we don't want to create a new instance every test-suite
+ if (startMemoryInstance) {
mongoinstance = await mms.MongoMemoryServer.create({ instance: { args: ['--setParameter', 'ttlMonitorSleepSecs=1'], storageEngine: 'wiredTiger' } });
instanceuri = mongoinstance.getUri();
} else {
@@ -39,7 +39,7 @@ module.exports.mochaGlobalSetup = async function mochaGlobalSetup() {
mongorreplset = await mms.MongoMemoryReplSet.create({ replSet: { count: 3, args: ['--setParameter', 'ttlMonitorSleepSecs=1'], storageEngine: 'wiredTiger' } }); // using 3 because even numbers can lead to vote problems
replseturi = mongorreplset.getUri();
} else {
- replseturi = '';
+ replseturi = process.env.MONGOOSE_REPLSET_URI;
}
process.env.MONGOOSE_TEST_URI = instanceuri;
@@ -47,7 +47,7 @@ module.exports.mochaGlobalSetup = async function mochaGlobalSetup() {
};
module.exports.mochaGlobalTeardown = async function mochaGlobalTeardown() {
- if (mongoinstance) { // Config to decided if an mongodb-memory-server instance should be used
+ if (mongoinstance) {
await mongoinstance.stop();
}
if (mongorreplset) {
diff --git a/test/types/middleware.test.ts b/test/types/middleware.test.ts
index 46c44d390e0..4b7b07d3437 100644
--- a/test/types/middleware.test.ts
+++ b/test/types/middleware.test.ts
@@ -92,6 +92,24 @@ schema.pre>('insertMany', function(next, docs: Array) {
next();
});
+schema.pre>('count', function(next) {});
+schema.post>('count', function(count, next) {
+ expectType(count);
+ next();
+});
+
+schema.pre>('estimatedDocumentCount', function(next) {});
+schema.post>('estimatedDocumentCount', function(count, next) {
+ expectType(count);
+ next();
+});
+
+schema.pre>('countDocuments', function(next) {});
+schema.post>('countDocuments', function(count, next) {
+ expectType(count);
+ next();
+});
+
schema.post>('findOneAndDelete', function(res, next) {
expectType(res);
next();
diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts
index 4a6275d7c94..1eddc97363d 100644
--- a/test/types/schema.test.ts
+++ b/test/types/schema.test.ts
@@ -574,9 +574,7 @@ export type AutoTypedSchemaType = {
// discriminator
const eventSchema = new Schema<{ message: string }>({ message: String }, { discriminatorKey: 'kind' });
const batchSchema = new Schema<{ name: string }>({ name: String }, { discriminatorKey: 'kind' });
-const discriminatedSchema = batchSchema.discriminator('event', eventSchema);
-
-expectType & { message: string }>>(discriminatedSchema);
+batchSchema.discriminator('event', eventSchema);
// discriminator statics
const eventSchema2 = new Schema({ message: String }, { discriminatorKey: 'kind', statics: { static1: function() {
@@ -585,9 +583,7 @@ const eventSchema2 = new Schema({ message: String }, { discriminatorKey: 'kind',
const batchSchema2 = new Schema({ name: String }, { discriminatorKey: 'kind', statics: { static2: function() {
return 1;
} } });
-const discriminatedSchema2 = batchSchema2.discriminator('event', eventSchema2);
-
-expectAssignable & { message: string }, Model, {}, {}, {}, { static1(): number; static2(): number; }>>(discriminatedSchema2);
+batchSchema2.discriminator('event', eventSchema2);
function gh11828() {
interface IUser {
diff --git a/types/index.d.ts b/types/index.d.ts
index 9acecd1fe65..3c1e7c2563e 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -215,7 +215,7 @@ declare module 'mongoose' {
/** Returns a copy of this schema */
clone(): T;
- discriminator(name: string, schema: DisSchema): DiscriminatorSchema;
+ discriminator(name: string, schema: DisSchema): this;
/** Returns a new schema that has the picked `paths` from this schema. */
pick(paths: string[], options?: SchemaOptions): T;
diff --git a/types/middlewares.d.ts b/types/middlewares.d.ts
index 0310ca0fa0a..b8e604f2dad 100644
--- a/types/middlewares.d.ts
+++ b/types/middlewares.d.ts
@@ -1,7 +1,7 @@
declare module 'mongoose' {
type MongooseDocumentMiddleware = 'validate' | 'save' | 'remove' | 'updateOne' | 'deleteOne' | 'init';
- type MongooseQueryMiddleware = 'count' | 'deleteMany' | 'deleteOne' | 'distinct' | 'find' | 'findOne' | 'findOneAndDelete' | 'findOneAndRemove' | 'findOneAndReplace' | 'findOneAndUpdate' | 'remove' | 'replaceOne' | 'update' | 'updateOne' | 'updateMany';
+ type MongooseQueryMiddleware = 'count' | 'estimatedDocumentCount' | 'countDocuments' | 'deleteMany' | 'deleteOne' | 'distinct' | 'find' | 'findOne' | 'findOneAndDelete' | 'findOneAndRemove' | 'findOneAndReplace' | 'findOneAndUpdate' | 'remove' | 'replaceOne' | 'update' | 'updateOne' | 'updateMany';
type MiddlewareOptions = { document?: boolean, query?: boolean };
type SchemaPreOptions = MiddlewareOptions;