Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(populate): pass virtual to match function to allow merging match options #13477

Merged
merged 2 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/populate.md
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,29 @@ AuthorSchema.virtual('posts', {
});
```

You can overwrite the `match` option when calling `populate()` as follows.

```javascript
// Overwrite the `match` option specified in `AuthorSchema.virtual()` for this
// single `populate()` call.
await Author.findOne().populate({ path: posts, match: {} });
```

You can also set the `match` option to a function in your `populate()` call.
If you want to merge your `populate()` match option, rather than overwriting, use the following.

```javascript
await Author.findOne().populate({
path: posts,
// Add `isDeleted: false` to the virtual's default `match`, so the `match`
// option would be `{ tags: author.favoriteTags, isDeleted: false }`
match: (author, virtual) => ({
...virtual.options.match(author),
isDeleted: false
})
});
```

<h2 id="populating-maps"><a href="#populating-maps">Populating Maps</a></h2>

[Maps](schematypes.html#maps) are a type that represents an object with arbitrary
Expand Down
6 changes: 3 additions & 3 deletions lib/helpers/populate/getModelsMapForPopulate.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,13 +436,13 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
data.justOne = justOne;

// `match`
let match = get(options, 'match', null) ||
get(data, 'virtual.options.match', null) ||
const baseMatch = get(data, 'virtual.options.match', null) ||
get(data, 'virtual.options.options.match', null);
let match = get(options, 'match', null) || baseMatch;

let hasMatchFunction = typeof match === 'function';
if (hasMatchFunction) {
match = match.call(doc, doc);
match = match.call(doc, doc, data.virtual);
}

if (Array.isArray(localField) && Array.isArray(foreignField) && localField.length === foreignField.length) {
Expand Down
1 change: 1 addition & 0 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,7 @@ Schema.prototype.indexes = function() {
* @param {Boolean|Function} [options.justOne=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), will be a single doc or `null`. Otherwise, the populate virtual will be an array.
* @param {Boolean} [options.count=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), this populate virtual will contain the number of documents rather than the documents themselves when you `populate()`.
* @param {Function|null} [options.get=null] Adds a [getter](https://mongoosejs.com/docs/tutorials/getters-setters.html) to this virtual to transform the populated doc.
* @param {Object|Function} [options.match=null] Apply a default [`match` option to populate](https://mongoosejs.com/docs/populate.html#match), adding an additional filter to the populate query.
* @return {VirtualType}
*/

Expand Down
50 changes: 50 additions & 0 deletions test/model.populate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10454,4 +10454,54 @@ describe('model: populate:', function() {
assert.equal(normal.test.length, 0);
});
});

it('calls match function with virtual as parameter (gh-12443)', async function() {
const parentSchema = mongoose.Schema({ name: String });
parentSchema.virtual('children', {
ref: 'Child',
localField: '_id',
foreignField: 'parentId',
justOne: false,
match: {
isDeleted: false
}
});
const Parent = db.model('Parent', parentSchema);

const childSchema = mongoose.Schema({
name: String,
parentId: 'ObjectId',
isDeleted: Boolean
});
const Child = db.model('Child', childSchema);

const { _id } = await Parent.create({ name: 'Darth Vader' });
await Child.create([
{ name: 'Luke', parentId: _id, isDeleted: false },
{ name: 'Leia', parentId: _id, isDeleted: false },
{ name: 'Chad', parentId: _id, isDeleted: true }
]);

let doc = await Parent.findById(_id).populate({
path: 'children'
});
assert.deepStrictEqual(doc.children.map(c => c.name).sort(), ['Leia', 'Luke']);

doc = await Parent.findById(_id).populate({
path: 'children',
match: (_doc, virtual) => ({
...virtual.options.match,
name: /(Luke|Chad)/
})
});
assert.deepStrictEqual(doc.children.map(c => c.name).sort(), ['Luke']);

doc = await Parent.findById(_id).populate({
path: 'children',
match: () => ({
name: /(Luke|Chad)/
})
});
assert.deepStrictEqual(doc.children.map(c => c.name).sort(), ['Chad', 'Luke']);
});
});
2 changes: 1 addition & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ declare module 'mongoose' {
count?: boolean;

/** Add an extra match condition to `populate()`. */
match?: FilterQuery<any> | Function;
match?: FilterQuery<any> | ((doc: Record<string, any>, virtual?: this) => Record<string, any> | null);

/** Add a default `limit` to the `populate()` query. */
limit?: number;
Expand Down