Skip to content

Commit

Permalink
Merge branch '6.7' into vkarpov15/gh-12368
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Oct 5, 2022
2 parents 0e8e9c4 + 136bd1d commit 2da76c3
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 32 deletions.
4 changes: 1 addition & 3 deletions docs/change-streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 58 additions & 11 deletions docs/models.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,8 +11,9 @@ reading documents from the underlying MongoDB database.
* [Deleting](#deleting)
* [Updating](#updating)
* [Change Streams](#change-streams)
* [Views](#views)

<h3 id="compiling"><a href="#compiling">Compiling your first model</a></h3>
<h2 id="compiling"><a href="#compiling">Compiling your first model</a></h3>

When you call `mongoose.model()` on a schema, Mongoose _compiles_ a model
for you.
Expand All @@ -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.
Expand Down Expand Up @@ -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.

Expand All @@ -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`.
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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: '[email protected]', roles: ['user'] },
{ name: 'Bill James', email: '[email protected]', 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).
26 changes: 21 additions & 5 deletions docs/subdocs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
```

<ul class="toc">
<li><a href="#what-is-a-subdocument-">What is a Subdocument?</a></li>
Expand Down
1 change: 1 addition & 0 deletions lib/schema/SubdocumentPath.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
29 changes: 29 additions & 0 deletions test/document.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions test/mocha-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 {
Expand All @@ -39,15 +39,15 @@ 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;
process.env.MONGOOSE_REPLSET_URI = replseturi;
};

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) {
Expand Down
18 changes: 18 additions & 0 deletions test/types/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,24 @@ schema.pre<Model<ITest>>('insertMany', function(next, docs: Array<ITest>) {
next();
});

schema.pre<Query<number, any>>('count', function(next) {});
schema.post<Query<number, any>>('count', function(count, next) {
expectType<number>(count);
next();
});

schema.pre<Query<number, any>>('estimatedDocumentCount', function(next) {});
schema.post<Query<number, any>>('estimatedDocumentCount', function(count, next) {
expectType<number>(count);
next();
});

schema.pre<Query<number, any>>('countDocuments', function(next) {});
schema.post<Query<number, any>>('countDocuments', function(count, next) {
expectType<number>(count);
next();
});

schema.post<Query<ITest, ITest>>('findOneAndDelete', function(res, next) {
expectType<ITest>(res);
next();
Expand Down
8 changes: 2 additions & 6 deletions test/types/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Schema<Omit<{ name: string }, 'message'> & { message: string }>>(discriminatedSchema);
batchSchema.discriminator('event', eventSchema);

// discriminator statics
const eventSchema2 = new Schema({ message: String }, { discriminatorKey: 'kind', statics: { static1: function() {
Expand All @@ -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<Schema<Omit<{ name: string }, 'message'> & { message: string }, Model<any>, {}, {}, {}, { static1(): number; static2(): number; }>>(discriminatedSchema2);
batchSchema2.discriminator('event', eventSchema2);

function gh11828() {
interface IUser {
Expand Down
2 changes: 1 addition & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ declare module 'mongoose' {
/** Returns a copy of this schema */
clone<T = this>(): T;

discriminator<DisSchema = Schema>(name: string, schema: DisSchema): DiscriminatorSchema<DocType, M, TInstanceMethods, TQueryHelpers, TVirtuals, TStaticMethods, DisSchema>;
discriminator<DisSchema = Schema>(name: string, schema: DisSchema): this;

/** Returns a new schema that has the picked `paths` from this schema. */
pick<T = this>(paths: string[], options?: SchemaOptions): T;
Expand Down
2 changes: 1 addition & 1 deletion types/middlewares.d.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down

0 comments on commit 2da76c3

Please sign in to comment.