Skip to content

Commit be441a0

Browse files
committed
fix(schema): fix dangling reference to virtual in tree after removeVirtual()
Fix #13085
1 parent fbb1f5d commit be441a0

File tree

3 files changed

+65
-1
lines changed

3 files changed

+65
-1
lines changed

lib/schema.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2334,6 +2334,11 @@ Schema.prototype.removeVirtual = function(path) {
23342334
for (const virtual of path) {
23352335
delete this.paths[virtual];
23362336
delete this.virtuals[virtual];
2337+
if (virtual.indexOf('.') !== -1) {
2338+
mpath.unset(virtual, this.tree);
2339+
} else {
2340+
delete this.tree[virtual];
2341+
}
23372342
}
23382343
}
23392344
return this;

test/model.populate.test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10966,6 +10966,61 @@ describe('model: populate:', function() {
1096610966
assert.equal(person.stories[0].title, 'Casino Royale');
1096710967
});
1096810968

10969+
it('supports removing and then recreating populate virtual using schema clone (gh-13085)', async function() {
10970+
const personSch = new mongoose.Schema(
10971+
{
10972+
firstName: { type: mongoose.SchemaTypes.String, required: true },
10973+
surname: { type: mongoose.SchemaTypes.String, trim: true },
10974+
nat: { type: mongoose.SchemaTypes.String, required: true, uppercase: true, minLength: 2, maxLength: 2 }
10975+
},
10976+
{ strict: true, timestamps: true }
10977+
);
10978+
personSch.virtual('nationality', {
10979+
localField: 'nat',
10980+
foreignField: 'key',
10981+
ref: 'Nat',
10982+
justOne: true
10983+
});
10984+
let Person = db.model('Person', personSch.clone(), 'people');
10985+
10986+
const natSch = new mongoose.Schema(
10987+
{
10988+
key: { type: mongoose.SchemaTypes.String, uppercase: true, index: true, minLength: 2, maxLength: 2 },
10989+
desc: { type: mongoose.SchemaTypes.String, trim: true }
10990+
},
10991+
{ strict: true }
10992+
);
10993+
const Nat = db.model('Nat', natSch);
10994+
let n = new Nat({ key: 'ES', desc: 'Spain' });
10995+
await n.save();
10996+
n = new Nat({ key: 'IT', desc: 'Italy' });
10997+
await n.save();
10998+
n = new Nat({ key: 'FR', desc: 'French' });
10999+
await n.save();
11000+
11001+
let p = new Person({ firstName: 'Pepe', surname: 'Pérez', nat: 'it' });
11002+
await p.save();
11003+
p = new Person({ firstName: 'Paco', surname: 'Matinez', nat: 'es' });
11004+
await p.save();
11005+
p = new Person({ firstName: 'John', surname: 'Doe', nat: 'us' });
11006+
await p.save();
11007+
11008+
personSch.removeVirtual('nationality');
11009+
personSch.virtual('nationality', {
11010+
localField: 'nat',
11011+
foreignField: 'key',
11012+
ref: 'Nat',
11013+
justOne: true
11014+
});
11015+
Person = db.model('Person', personSch.clone(), 'people', { overwriteModels: true });
11016+
11017+
const peopleList = await Person.find().
11018+
sort({ firstName: 1 }).
11019+
populate({ path: 'nationality', match: { desc: 'Spain' } });
11020+
assert.deepStrictEqual(peopleList.map(p => p.nationality?.key), [undefined, 'ES', undefined]);
11021+
11022+
});
11023+
1096911024

1097011025
describe('strictPopulate', function() {
1097111026
it('reports full path when throwing `strictPopulate` error with deep populate (gh-10923)', async function() {

test/schema.test.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3016,9 +3016,13 @@ describe('schema', function() {
30163016
assert.ok(schema.virtuals.foo);
30173017
schema.removeVirtual('foo');
30183018
assert.ok(!schema.virtuals.foo);
3019+
assert.ok(!schema.tree.foo);
3020+
3021+
schema.virtual('foo').get(v => v || 99);
3022+
30193023
const Test = db.model('gh-8397', schema);
30203024
const doc = new Test({ name: 'Test' });
3021-
assert.equal(doc.foo, undefined);
3025+
assert.equal(doc.foo, 99);
30223026
});
30233027

30243028
it('should allow deleting multiple virtuals gh-8397', async function() {

0 commit comments

Comments
 (0)