Skip to content

Commit

Permalink
Merge branch '6.x' into 7.x
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Apr 10, 2024
2 parents 8a8bea5 + c00a715 commit bf70152
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 19 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
6.12.8 / 2024-04-10
===================
* fix(document): handle virtuals that are stored as objects but getter returns string with toJSON #14468 #14446
* fix(schematype): consistently set wasPopulated to object with `value` property rather than boolean #14418
* docs(model): add extra note about lean option for insertMany() skipping casting #14415 #14376

7.6.10 / 2024-03-13
===================
* docs(model): add extra note about lean option for insertMany() skipping casting #14415
Expand Down
15 changes: 12 additions & 3 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -1055,7 +1055,11 @@ Document.prototype.$set = function $set(path, val, type, options) {
if (path.$__isNested) {
path = path.toObject();
} else {
path = path._doc;
// This ternary is to support gh-7898 (copying virtuals if same schema)
// while not breaking gh-10819, which for some reason breaks if we use toObject()
path = path.$__schema === this.$__schema
? applyVirtuals(path, { ...path._doc })
: path._doc;
}
}
if (path == null) {
Expand Down Expand Up @@ -4029,6 +4033,7 @@ function applyVirtuals(self, json, options, toObjectOptions) {
? toObjectOptions.aliases
: true;

options = options || {};
let virtualsToApply = null;
if (Array.isArray(options.virtuals)) {
virtualsToApply = new Set(options.virtuals);
Expand All @@ -4045,7 +4050,6 @@ function applyVirtuals(self, json, options, toObjectOptions) {
return json;
}

options = options || {};
for (i = 0; i < numPaths; ++i) {
path = paths[i];

Expand Down Expand Up @@ -4126,7 +4130,12 @@ function applyGetters(self, json, options) {
for (let ii = 0; ii < plen; ++ii) {
part = parts[ii];
v = cur[part];
if (ii === last) {
// If we've reached a non-object part of the branch, continuing would
// cause "Cannot create property 'foo' on string 'bar'" error.
// Necessary for mongoose-intl plugin re: gh-14446
if (branch != null && typeof branch !== 'object') {
break;
} else if (ii === last) {
const val = self.$get(path);
branch[part] = clone(val, options);
if (Array.isArray(branch[part]) && schema.paths[path].$embeddedSchemaType) {
Expand Down
2 changes: 1 addition & 1 deletion lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -4659,7 +4659,7 @@ function _assign(model, vals, mod, assignmentOpts) {
}
// flag each as result of population
if (!lean) {
val.$__.wasPopulated = val.$__.wasPopulated || true;
val.$__.wasPopulated = val.$__.wasPopulated || { value: _val };
}
}
}
Expand Down
16 changes: 3 additions & 13 deletions lib/schema/documentarray.js
Original file line number Diff line number Diff line change
Expand Up @@ -447,19 +447,9 @@ DocumentArrayPath.prototype.cast = function(value, doc, init, prev, options) {

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

// Check if the document has a different schema (re gh-3701)
if (rawArray[i].$__ != null && !(rawArray[i] instanceof Constructor)) {
const spreadDoc = handleSpreadDoc(rawArray[i], true);
if (rawArray[i] !== spreadDoc) {
rawArray[i] = spreadDoc;
} else {
rawArray[i] = rawArray[i].toObject({
transform: false,
// Special case: if different model, but same schema, apply virtuals
// re: gh-7898
virtuals: rawArray[i].schema === Constructor.schema
});
}
const spreadDoc = handleSpreadDoc(rawArray[i], true);
if (rawArray[i] !== spreadDoc) {
rawArray[i] = spreadDoc;
}

if (rawArray[i] instanceof Subdocument) {
Expand Down
4 changes: 2 additions & 2 deletions lib/schematype.js
Original file line number Diff line number Diff line change
Expand Up @@ -1523,7 +1523,7 @@ SchemaType.prototype._castRef = function _castRef(value, doc, init) {
}

if (value.$__ != null) {
value.$__.wasPopulated = value.$__.wasPopulated || true;
value.$__.wasPopulated = value.$__.wasPopulated || { value: value._id };
return value;
}

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

return ret;
Expand Down
94 changes: 94 additions & 0 deletions test/document.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12961,6 +12961,100 @@ describe('document', function() {
doc.set({ nested: void 0 });
assert.strictEqual(doc.toObject().nested, void 0);
});

it('avoids depopulating populated subdocs underneath document arrays when copying to another document (gh-14418)', async function() {
const cartSchema = new mongoose.Schema({
products: [
{
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
},
quantity: Number
}
],
singleProduct: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
}
});
const purchaseSchema = new mongoose.Schema({
products: [
{
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
},
quantity: Number
}
],
singleProduct: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
}
});
const productSchema = new mongoose.Schema({
name: String
});

const Cart = db.model('Cart', cartSchema);
const Purchase = db.model('Purchase', purchaseSchema);
const Product = db.model('Product', productSchema);

const dbProduct = await Product.create({ name: 'Bug' });

const dbCart = await Cart.create({
products: [
{
product: dbProduct,
quantity: 2
}
],
singleProduct: dbProduct
});

const foundCart = await Cart.findById(dbCart._id).
populate('products.product singleProduct');

const purchaseFromDbCart = new Purchase({
products: foundCart.products,
singleProduct: foundCart.singleProduct
});
assert.equal(purchaseFromDbCart.products[0].product.name, 'Bug');
assert.equal(purchaseFromDbCart.singleProduct.name, 'Bug');
});

it('handles virtuals that are stored as objects but getter returns string with toJSON (gh-14446)', async function() {
const childSchema = new mongoose.Schema();

childSchema.virtual('name')
.set(function(values) {
for (const [lang, val] of Object.entries(values)) {
this.set(`name.${lang}`, val);
}
})
.get(function() {
return this.$__getValue(`name.${this.lang}`);
});

childSchema.add({ name: { en: { type: String }, de: { type: String } } });

const ChildModel = db.model('Child', childSchema);
const ParentModel = db.model('Parent', new mongoose.Schema({
children: [childSchema]
}));

const child = await ChildModel.create({ name: { en: 'Stephen', de: 'Stefan' } });
child.lang = 'en';
assert.equal(child.name, 'Stephen');

const parent = await ParentModel.create({
children: [{ name: { en: 'Stephen', de: 'Stefan' } }]
});
parent.children[0].lang = 'de';
const obj = parent.toJSON({ getters: true });
assert.equal(obj.children[0].name, 'Stefan');
});
});

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

0 comments on commit bf70152

Please sign in to comment.