Skip to content

Commit b729474

Browse files
authored
Merge pull request #14500 from Automattic/vkarpov15/gh-14444
fix(model): make `Model.recompileSchema()` also re-apply discriminators
2 parents 7cf76cc + 1e8753f commit b729474

File tree

6 files changed

+64
-7
lines changed

6 files changed

+64
-7
lines changed

lib/helpers/discriminator/applyEmbeddedDiscriminators.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module.exports = applyEmbeddedDiscriminators;
44

5-
function applyEmbeddedDiscriminators(schema, seen = new WeakSet()) {
5+
function applyEmbeddedDiscriminators(schema, seen = new WeakSet(), overwriteExisting = false) {
66
if (seen.has(schema)) {
77
return;
88
}
@@ -16,13 +16,17 @@ function applyEmbeddedDiscriminators(schema, seen = new WeakSet()) {
1616
if (!schemaType.schema._applyDiscriminators) {
1717
continue;
1818
}
19-
if (schemaType._appliedDiscriminators) {
19+
if (schemaType._appliedDiscriminators && !overwriteExisting) {
2020
continue;
2121
}
2222
for (const discriminatorKey of schemaType.schema._applyDiscriminators.keys()) {
2323
const discriminatorSchema = schemaType.schema._applyDiscriminators.get(discriminatorKey);
2424
applyEmbeddedDiscriminators(discriminatorSchema, seen);
25-
schemaType.discriminator(discriminatorKey, discriminatorSchema);
25+
schemaType.discriminator(
26+
discriminatorKey,
27+
discriminatorSchema,
28+
overwriteExisting ? { overwriteExisting: true } : null
29+
);
2630
}
2731
schemaType._appliedDiscriminators = true;
2832
}

lib/helpers/model/discriminator.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
2121
* ignore
2222
*/
2323

24-
module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins, mergeHooks) {
24+
module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins, mergeHooks, overwriteExisting) {
2525
if (!(schema && schema.instanceOfSchema)) {
2626
throw new Error('You must pass a valid discriminator Schema');
2727
}
@@ -205,7 +205,7 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
205205

206206
model.schema.discriminators[name] = schema;
207207

208-
if (model.discriminators[name] && !schema.options.overwriteModels) {
208+
if (model.discriminators[name] && !schema.options.overwriteModels && !overwriteExisting) {
209209
throw new Error('Discriminator with name "' + name + '" already exists');
210210
}
211211

lib/model.js

+9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const VersionError = require('./error/version');
2323
const ParallelSaveError = require('./error/parallelSave');
2424
const applyDefaultsHelper = require('./helpers/document/applyDefaults');
2525
const applyDefaultsToPOJO = require('./helpers/model/applyDefaultsToPOJO');
26+
const applyEmbeddedDiscriminators = require('./helpers/discriminator/applyEmbeddedDiscriminators');
2627
const applyHooks = require('./helpers/model/applyHooks');
2728
const applyMethods = require('./helpers/model/applyMethods');
2829
const applyProjection = require('./helpers/projection/applyProjection');
@@ -5031,6 +5032,14 @@ Model.__subclass = function subclass(conn, schema, collection) {
50315032

50325033
Model.recompileSchema = function recompileSchema() {
50335034
this.prototype.$__setSchema(this.schema);
5035+
5036+
if (this.schema._applyDiscriminators != null) {
5037+
for (const disc of this.schema._applyDiscriminators.keys()) {
5038+
this.discriminator(disc, this.schema._applyDiscriminators.get(disc));
5039+
}
5040+
}
5041+
5042+
applyEmbeddedDiscriminators(this.schema, new WeakSet(), true);
50345043
};
50355044

50365045
/**

lib/schema/subdocument.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ SchemaSubdocument.prototype.discriminator = function(name, schema, options) {
325325
schema = schema.clone();
326326
}
327327

328-
schema = discriminator(this.caster, name, schema, value);
328+
schema = discriminator(this.caster, name, schema, value, null, null, options.overwriteExisting);
329329

330330
this.caster.discriminators[name] = _createConstructor(schema, this.caster);
331331

lib/schemaType.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,7 @@ SchemaType.prototype.validateAll = function(validators) {
878878
*
879879
* schema.path('name').validate({
880880
* validator: function() { throw new Error('Oops!'); },
881-
* // `errors['name']` will be "Oops!"
881+
* // `errors['name'].message` will be "Oops!"
882882
* message: function(props) { return props.reason.message; }
883883
* });
884884
*

test/model.test.js

+44
Original file line numberDiff line numberDiff line change
@@ -7419,6 +7419,50 @@ describe('Model', function() {
74197419
assert.equal(doc.myVirtual, 'Hello from myVirtual');
74207420
});
74217421

7422+
it('supports recompiling model with new discriminators (gh-14444) (gh-14296)', function() {
7423+
// Define discriminated schema
7424+
const decoratorSchema = new Schema({
7425+
type: { type: String, required: true }
7426+
}, { discriminatorKey: 'type' });
7427+
7428+
class Decorator {
7429+
whoAmI() { return 'I am BaseDeco'; }
7430+
}
7431+
decoratorSchema.loadClass(Decorator);
7432+
7433+
// Define discriminated class before model is compiled
7434+
class Deco1 extends Decorator { whoAmI() { return 'I am Test1'; }}
7435+
const deco1Schema = new Schema({});
7436+
deco1Schema.loadClass(Deco1);
7437+
decoratorSchema.discriminator('Test1', deco1Schema);
7438+
7439+
// Define model that uses discriminated schema
7440+
const shopSchema = new Schema({
7441+
item: { type: decoratorSchema, required: true }
7442+
});
7443+
const shopModel = db.model('Test', shopSchema);
7444+
7445+
// Define another discriminated class after the model is compiled
7446+
class Deco2 extends Decorator { whoAmI() { return 'I am Test2'; }}
7447+
const deco2Schema = new Schema({});
7448+
deco2Schema.loadClass(Deco2);
7449+
decoratorSchema.discriminator('Test2', deco2Schema);
7450+
7451+
let instance = new shopModel({ item: { type: 'Test1' } });
7452+
assert.equal(instance.item.whoAmI(), 'I am Test1');
7453+
7454+
instance = new shopModel({ item: { type: 'Test2' } });
7455+
assert.equal(instance.item.whoAmI(), 'I am BaseDeco');
7456+
7457+
shopModel.recompileSchema();
7458+
7459+
instance = new shopModel({ item: { type: 'Test1' } });
7460+
assert.equal(instance.item.whoAmI(), 'I am Test1');
7461+
7462+
instance = new shopModel({ item: { type: 'Test2' } });
7463+
assert.equal(instance.item.whoAmI(), 'I am Test2');
7464+
});
7465+
74227466
it('inserts versionKey even if schema has `toObject.versionKey` set to false (gh-14344)', async function() {
74237467
const schema = new mongoose.Schema(
74247468
{ name: String },

0 commit comments

Comments
 (0)