Skip to content

Commit bf70152

Browse files
committed
Merge branch '6.x' into 7.x
2 parents 8a8bea5 + c00a715 commit bf70152

File tree

6 files changed

+118
-19
lines changed

6 files changed

+118
-19
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
6.12.8 / 2024-04-10
2+
===================
3+
* fix(document): handle virtuals that are stored as objects but getter returns string with toJSON #14468 #14446
4+
* fix(schematype): consistently set wasPopulated to object with `value` property rather than boolean #14418
5+
* docs(model): add extra note about lean option for insertMany() skipping casting #14415 #14376
6+
17
7.6.10 / 2024-03-13
28
===================
39
* docs(model): add extra note about lean option for insertMany() skipping casting #14415

lib/document.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,7 +1055,11 @@ Document.prototype.$set = function $set(path, val, type, options) {
10551055
if (path.$__isNested) {
10561056
path = path.toObject();
10571057
} else {
1058-
path = path._doc;
1058+
// This ternary is to support gh-7898 (copying virtuals if same schema)
1059+
// while not breaking gh-10819, which for some reason breaks if we use toObject()
1060+
path = path.$__schema === this.$__schema
1061+
? applyVirtuals(path, { ...path._doc })
1062+
: path._doc;
10591063
}
10601064
}
10611065
if (path == null) {
@@ -4029,6 +4033,7 @@ function applyVirtuals(self, json, options, toObjectOptions) {
40294033
? toObjectOptions.aliases
40304034
: true;
40314035

4036+
options = options || {};
40324037
let virtualsToApply = null;
40334038
if (Array.isArray(options.virtuals)) {
40344039
virtualsToApply = new Set(options.virtuals);
@@ -4045,7 +4050,6 @@ function applyVirtuals(self, json, options, toObjectOptions) {
40454050
return json;
40464051
}
40474052

4048-
options = options || {};
40494053
for (i = 0; i < numPaths; ++i) {
40504054
path = paths[i];
40514055

@@ -4126,7 +4130,12 @@ function applyGetters(self, json, options) {
41264130
for (let ii = 0; ii < plen; ++ii) {
41274131
part = parts[ii];
41284132
v = cur[part];
4129-
if (ii === last) {
4133+
// If we've reached a non-object part of the branch, continuing would
4134+
// cause "Cannot create property 'foo' on string 'bar'" error.
4135+
// Necessary for mongoose-intl plugin re: gh-14446
4136+
if (branch != null && typeof branch !== 'object') {
4137+
break;
4138+
} else if (ii === last) {
41304139
const val = self.$get(path);
41314140
branch[part] = clone(val, options);
41324141
if (Array.isArray(branch[part]) && schema.paths[path].$embeddedSchemaType) {

lib/model.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4659,7 +4659,7 @@ function _assign(model, vals, mod, assignmentOpts) {
46594659
}
46604660
// flag each as result of population
46614661
if (!lean) {
4662-
val.$__.wasPopulated = val.$__.wasPopulated || true;
4662+
val.$__.wasPopulated = val.$__.wasPopulated || { value: _val };
46634663
}
46644664
}
46654665
}

lib/schema/documentarray.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -447,19 +447,9 @@ DocumentArrayPath.prototype.cast = function(value, doc, init, prev, options) {
447447

448448
const Constructor = getConstructor(this.casterConstructor, rawArray[i]);
449449

450-
// Check if the document has a different schema (re gh-3701)
451-
if (rawArray[i].$__ != null && !(rawArray[i] instanceof Constructor)) {
452-
const spreadDoc = handleSpreadDoc(rawArray[i], true);
453-
if (rawArray[i] !== spreadDoc) {
454-
rawArray[i] = spreadDoc;
455-
} else {
456-
rawArray[i] = rawArray[i].toObject({
457-
transform: false,
458-
// Special case: if different model, but same schema, apply virtuals
459-
// re: gh-7898
460-
virtuals: rawArray[i].schema === Constructor.schema
461-
});
462-
}
450+
const spreadDoc = handleSpreadDoc(rawArray[i], true);
451+
if (rawArray[i] !== spreadDoc) {
452+
rawArray[i] = spreadDoc;
463453
}
464454

465455
if (rawArray[i] instanceof Subdocument) {

lib/schematype.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,7 +1523,7 @@ SchemaType.prototype._castRef = function _castRef(value, doc, init) {
15231523
}
15241524

15251525
if (value.$__ != null) {
1526-
value.$__.wasPopulated = value.$__.wasPopulated || true;
1526+
value.$__.wasPopulated = value.$__.wasPopulated || { value: value._id };
15271527
return value;
15281528
}
15291529

@@ -1549,7 +1549,7 @@ SchemaType.prototype._castRef = function _castRef(value, doc, init) {
15491549
!doc.$__.populated[path].options.options ||
15501550
!doc.$__.populated[path].options.options.lean) {
15511551
ret = new pop.options[populateModelSymbol](value);
1552-
ret.$__.wasPopulated = true;
1552+
ret.$__.wasPopulated = { value: ret._id };
15531553
}
15541554

15551555
return ret;

test/document.test.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12961,6 +12961,100 @@ describe('document', function() {
1296112961
doc.set({ nested: void 0 });
1296212962
assert.strictEqual(doc.toObject().nested, void 0);
1296312963
});
12964+
12965+
it('avoids depopulating populated subdocs underneath document arrays when copying to another document (gh-14418)', async function() {
12966+
const cartSchema = new mongoose.Schema({
12967+
products: [
12968+
{
12969+
product: {
12970+
type: mongoose.Schema.Types.ObjectId,
12971+
ref: 'Product'
12972+
},
12973+
quantity: Number
12974+
}
12975+
],
12976+
singleProduct: {
12977+
type: mongoose.Schema.Types.ObjectId,
12978+
ref: 'Product'
12979+
}
12980+
});
12981+
const purchaseSchema = new mongoose.Schema({
12982+
products: [
12983+
{
12984+
product: {
12985+
type: mongoose.Schema.Types.ObjectId,
12986+
ref: 'Product'
12987+
},
12988+
quantity: Number
12989+
}
12990+
],
12991+
singleProduct: {
12992+
type: mongoose.Schema.Types.ObjectId,
12993+
ref: 'Product'
12994+
}
12995+
});
12996+
const productSchema = new mongoose.Schema({
12997+
name: String
12998+
});
12999+
13000+
const Cart = db.model('Cart', cartSchema);
13001+
const Purchase = db.model('Purchase', purchaseSchema);
13002+
const Product = db.model('Product', productSchema);
13003+
13004+
const dbProduct = await Product.create({ name: 'Bug' });
13005+
13006+
const dbCart = await Cart.create({
13007+
products: [
13008+
{
13009+
product: dbProduct,
13010+
quantity: 2
13011+
}
13012+
],
13013+
singleProduct: dbProduct
13014+
});
13015+
13016+
const foundCart = await Cart.findById(dbCart._id).
13017+
populate('products.product singleProduct');
13018+
13019+
const purchaseFromDbCart = new Purchase({
13020+
products: foundCart.products,
13021+
singleProduct: foundCart.singleProduct
13022+
});
13023+
assert.equal(purchaseFromDbCart.products[0].product.name, 'Bug');
13024+
assert.equal(purchaseFromDbCart.singleProduct.name, 'Bug');
13025+
});
13026+
13027+
it('handles virtuals that are stored as objects but getter returns string with toJSON (gh-14446)', async function() {
13028+
const childSchema = new mongoose.Schema();
13029+
13030+
childSchema.virtual('name')
13031+
.set(function(values) {
13032+
for (const [lang, val] of Object.entries(values)) {
13033+
this.set(`name.${lang}`, val);
13034+
}
13035+
})
13036+
.get(function() {
13037+
return this.$__getValue(`name.${this.lang}`);
13038+
});
13039+
13040+
childSchema.add({ name: { en: { type: String }, de: { type: String } } });
13041+
13042+
const ChildModel = db.model('Child', childSchema);
13043+
const ParentModel = db.model('Parent', new mongoose.Schema({
13044+
children: [childSchema]
13045+
}));
13046+
13047+
const child = await ChildModel.create({ name: { en: 'Stephen', de: 'Stefan' } });
13048+
child.lang = 'en';
13049+
assert.equal(child.name, 'Stephen');
13050+
13051+
const parent = await ParentModel.create({
13052+
children: [{ name: { en: 'Stephen', de: 'Stefan' } }]
13053+
});
13054+
parent.children[0].lang = 'de';
13055+
const obj = parent.toJSON({ getters: true });
13056+
assert.equal(obj.children[0].name, 'Stefan');
13057+
});
1296413058
});
1296513059

1296613060
describe('Check if instance function that is supplied in schema option is availabe', function() {

0 commit comments

Comments
 (0)