Skip to content

Commit

Permalink
Merge branch '7.x'
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Apr 11, 2024
2 parents 2d4cbb8 + c97c060 commit 480c3f3
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 24 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
7.6.11 / 2024-04-11
===================
* fix(populate): avoid match function filtering out null values in populate result #14518
* fix(schema): support setting discriminator options in Schema.prototype.discriminator() #14493 #14448
* fix(schema): deduplicate idGetter so creating multiple models with same schema doesn't result in multiple id getters #14492 #14457

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

8.3.1 / 2024-04-08
==================
* fix(document): make update minimization unset property rather than setting to null #14504 #14445
Expand Down
15 changes: 12 additions & 3 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,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 @@ -4087,6 +4091,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 @@ -4103,7 +4108,6 @@ function applyVirtuals(self, json, options, toObjectOptions) {
return json;
}

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

Expand Down Expand Up @@ -4184,7 +4188,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
7 changes: 5 additions & 2 deletions lib/helpers/discriminator/applyEmbeddedDiscriminators.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ function applyEmbeddedDiscriminators(schema, seen = new WeakSet(), overwriteExis
continue;
}
for (const discriminatorKey of schemaType.schema._applyDiscriminators.keys()) {
const discriminatorSchema = schemaType.schema._applyDiscriminators.get(discriminatorKey);
const {
schema: discriminatorSchema,
options
} = schemaType.schema._applyDiscriminators.get(discriminatorKey);
applyEmbeddedDiscriminators(discriminatorSchema, seen);
schemaType.discriminator(
discriminatorKey,
discriminatorSchema,
overwriteExisting ? { overwriteExisting: true } : null
overwriteExisting ? { ...options, overwriteExisting: true } : options
);
}
schemaType._appliedDiscriminators = true;
Expand Down
2 changes: 1 addition & 1 deletion lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -4749,7 +4749,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
6 changes: 5 additions & 1 deletion lib/mongoose.js
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,11 @@ Mongoose.prototype._model = function(name, schema, collection, options) {

if (schema._applyDiscriminators != null) {
for (const disc of schema._applyDiscriminators.keys()) {
model.discriminator(disc, schema._applyDiscriminators.get(disc));
const {
schema: discriminatorSchema,
options
} = schema._applyDiscriminators.get(disc);
model.discriminator(disc, discriminatorSchema, options);
}
}

Expand Down
10 changes: 8 additions & 2 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -626,12 +626,18 @@ Schema.prototype.defaultOptions = function(options) {
*
* @param {String} name the name of the discriminator
* @param {Schema} schema the discriminated Schema
* @param {Object} [options] discriminator options
* @param {String} [options.value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
* @param {Boolean} [options.clone=true] By default, `discriminator()` clones the given `schema`. Set to `false` to skip cloning.
* @param {Boolean} [options.overwriteModels=false] by default, Mongoose does not allow you to define a discriminator with the same name as another discriminator. Set this to allow overwriting discriminators with the same name.
* @param {Boolean} [options.mergeHooks=true] By default, Mongoose merges the base schema's hooks with the discriminator schema's hooks. Set this option to `false` to make Mongoose use the discriminator schema's hooks instead.
* @param {Boolean} [options.mergePlugins=true] By default, Mongoose merges the base schema's plugins with the discriminator schema's plugins. Set this option to `false` to make Mongoose use the discriminator schema's plugins instead.
* @return {Schema} the Schema instance
* @api public
*/
Schema.prototype.discriminator = function(name, schema) {
Schema.prototype.discriminator = function(name, schema, options) {
this._applyDiscriminators = this._applyDiscriminators || new Map();
this._applyDiscriminators.set(name, schema);
this._applyDiscriminators.set(name, { schema, options });

return this;
};
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 @@ SchemaDocumentArray.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 @@ -1542,7 +1542,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 @@ -1568,7 +1568,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
93 changes: 93 additions & 0 deletions test/document.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13234,7 +13234,100 @@ describe('document', function() {

const savedDocSecond = await Test.findById(doc.id).orFail();
assert.deepStrictEqual(savedDocSecond.toObject({ minimize: false }).sub, {});
});

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');
});
});

Expand Down
18 changes: 18 additions & 0 deletions test/schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe('schema', function() {
},
b: { $type: String }
}, { typeKey: '$type' });
db.deleteModel(/Test/);
NestedModel = db.model('Test', NestedSchema);
});

Expand Down Expand Up @@ -3219,4 +3220,21 @@ describe('schema', function() {
assert.equal(schema.path('tags.$').caster.instance, 'String'); // actually Mixed
assert.equal(schema.path('subdocs.$').casterConstructor.schema.path('name').instance, 'String'); // actually Mixed
});
it('handles discriminator options with Schema.prototype.discriminator (gh-14448)', async function() {
const eventSchema = new mongoose.Schema({
name: String
}, { discriminatorKey: 'kind' });
const clickedEventSchema = new mongoose.Schema({ element: String });
eventSchema.discriminator(
'Test2',
clickedEventSchema,
{ value: 'click' }
);
const Event = db.model('Test', eventSchema);
const ClickedModel = db.model('Test2');

const doc = await Event.create({ kind: 'click', element: '#hero' });
assert.equal(doc.element, '#hero');
assert.ok(doc instanceof ClickedModel);
});
});

0 comments on commit 480c3f3

Please sign in to comment.