From 9370452aee88ddcf5284ef50e19e9ebda6714dac Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 22 Mar 2023 14:36:08 -0400 Subject: [PATCH 1/3] fix(schema): make creating top-level virtual underneath subdocument equivalent to creating virtual on the subdocument Fix #13189 Re: #8210 Re: #8198 --- lib/schema.js | 2 +- test/model.populate.test.js | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/schema.js b/lib/schema.js index 2ed2bd3c97f..2a1867cd824 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -2217,7 +2217,7 @@ Schema.prototype.virtual = function(name, options) { } // Workaround for gh-8198: if virtual is under document array, make a fake - // virtual. See gh-8210 + // virtual. See gh-8210, gh-13189 const parts = name.split('.'); let cur = parts[0]; for (let i = 0; i < parts.length - 1; ++i) { diff --git a/test/model.populate.test.js b/test/model.populate.test.js index 3e6cbf79848..b4b4adbd213 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -7934,13 +7934,19 @@ describe('model: populate:', function() { assert.equal(res.nested.events[0].nestedLayer.users_$[0].name, 'test'); }); - it('accessing populate virtual prop (gh-8198)', async function() { + it('accessing populate virtual prop (gh-13189) (gh-8198)', async function() { const FooSchema = new Schema({ name: String, children: [{ barId: { type: Schema.Types.ObjectId, ref: 'Test' }, quantity: Number - }] + }], + child: { + barId: { + type: 'ObjectId', + ref: 'Test' + } + } }); FooSchema.virtual('children.bar', { ref: 'Test', @@ -7948,6 +7954,12 @@ describe('model: populate:', function() { foreignField: '_id', justOne: true }); + FooSchema.virtual('child.bar', { + ref: 'Test', + localField: 'child.barId', + foreignField: '_id', + justOne: true + }); const BarSchema = Schema({ name: String }); const Foo = db.model('Test1', FooSchema); const Bar = db.model('Test', BarSchema); @@ -7955,10 +7967,18 @@ describe('model: populate:', function() { const bar = await Bar.create({ name: 'bar' }); const foo = await Foo.create({ name: 'foo', - children: [{ barId: bar._id, quantity: 1 }] + children: [{ barId: bar._id, quantity: 1 }], + child: { + barId: bar._id + } }); - const foo2 = await Foo.findById(foo._id).populate('children.bar'); + const foo2 = await Foo.findById(foo._id).populate('children.bar child.bar'); assert.equal(foo2.children[0].bar.name, 'bar'); + assert.equal(foo2.child.bar.name, 'bar'); + + const asObject = foo2.toObject({ virtuals: true }); + assert.equal(asObject.children[0].bar.name, 'bar'); + assert.equal(asObject.child.bar.name, 'bar'); }); describe('gh-8247', function() { From bbec1869b76b1677ea61f0c05ab5c8421f218320 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 22 Mar 2023 14:43:26 -0400 Subject: [PATCH 2/3] actual fix for #13189 --- lib/schema.js | 6 +++++- test/model.populate.test.js | 15 +++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/schema.js b/lib/schema.js index 2a1867cd824..73716ca7f20 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -2221,7 +2221,11 @@ Schema.prototype.virtual = function(name, options) { const parts = name.split('.'); let cur = parts[0]; for (let i = 0; i < parts.length - 1; ++i) { - if (this.paths[cur] != null && this.paths[cur].$isMongooseDocumentArray) { + if (this.paths[cur] == null) { + continue; + } + + if (this.paths[cur].$isMongooseDocumentArray || this.paths[cur].$isSingleNested) { const remnant = parts.slice(i + 1).join('.'); this.paths[cur].schema.virtual(remnant, options); break; diff --git a/test/model.populate.test.js b/test/model.populate.test.js index b4b4adbd213..b5d0795aa6c 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -7941,12 +7941,12 @@ describe('model: populate:', function() { barId: { type: Schema.Types.ObjectId, ref: 'Test' }, quantity: Number }], - child: { + child: new Schema({ barId: { type: 'ObjectId', ref: 'Test' } - } + }) }); FooSchema.virtual('children.bar', { ref: 'Test', @@ -7954,11 +7954,10 @@ describe('model: populate:', function() { foreignField: '_id', justOne: true }); - FooSchema.virtual('child.bar', { + FooSchema.virtual('child.bars', { ref: 'Test', localField: 'child.barId', - foreignField: '_id', - justOne: true + foreignField: '_id' }); const BarSchema = Schema({ name: String }); const Foo = db.model('Test1', FooSchema); @@ -7972,13 +7971,13 @@ describe('model: populate:', function() { barId: bar._id } }); - const foo2 = await Foo.findById(foo._id).populate('children.bar child.bar'); + const foo2 = await Foo.findById(foo._id).populate('children.bar child.bars'); assert.equal(foo2.children[0].bar.name, 'bar'); - assert.equal(foo2.child.bar.name, 'bar'); + assert.equal(foo2.child.bars[0].name, 'bar'); const asObject = foo2.toObject({ virtuals: true }); assert.equal(asObject.children[0].bar.name, 'bar'); - assert.equal(asObject.child.bar.name, 'bar'); + assert.equal(asObject.child.bars[0].name, 'bar'); }); describe('gh-8247', function() { From 2739950639294f27c4d703726c32521a4bda1082 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 22 Mar 2023 16:17:50 -0400 Subject: [PATCH 3/3] style: fix lint --- lib/schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/schema.js b/lib/schema.js index 73716ca7f20..c501792c529 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -2224,7 +2224,7 @@ Schema.prototype.virtual = function(name, options) { if (this.paths[cur] == null) { continue; } - + if (this.paths[cur].$isMongooseDocumentArray || this.paths[cur].$isSingleNested) { const remnant = parts.slice(i + 1).join('.'); this.paths[cur].schema.virtual(remnant, options);