diff --git a/lib/helpers/discriminator/applyEmbeddedDiscriminators.js b/lib/helpers/discriminator/applyEmbeddedDiscriminators.js index 9a04ecb072f..fa0c41c5be5 100644 --- a/lib/helpers/discriminator/applyEmbeddedDiscriminators.js +++ b/lib/helpers/discriminator/applyEmbeddedDiscriminators.js @@ -2,7 +2,7 @@ module.exports = applyEmbeddedDiscriminators; -function applyEmbeddedDiscriminators(schema, seen = new WeakSet()) { +function applyEmbeddedDiscriminators(schema, seen = new WeakSet(), overwriteExisting = false) { if (seen.has(schema)) { return; } @@ -16,13 +16,17 @@ function applyEmbeddedDiscriminators(schema, seen = new WeakSet()) { if (!schemaType.schema._applyDiscriminators) { continue; } - if (schemaType._appliedDiscriminators) { + if (schemaType._appliedDiscriminators && !overwriteExisting) { continue; } for (const discriminatorKey of schemaType.schema._applyDiscriminators.keys()) { const discriminatorSchema = schemaType.schema._applyDiscriminators.get(discriminatorKey); applyEmbeddedDiscriminators(discriminatorSchema, seen); - schemaType.discriminator(discriminatorKey, discriminatorSchema); + schemaType.discriminator( + discriminatorKey, + discriminatorSchema, + overwriteExisting ? { overwriteExisting: true } : null + ); } schemaType._appliedDiscriminators = true; } diff --git a/lib/helpers/model/discriminator.js b/lib/helpers/model/discriminator.js index f14e53a6b1c..d587c7930bb 100644 --- a/lib/helpers/model/discriminator.js +++ b/lib/helpers/model/discriminator.js @@ -21,7 +21,7 @@ const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = { * ignore */ -module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins, mergeHooks) { +module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins, mergeHooks, overwriteExisting) { if (!(schema && schema.instanceOfSchema)) { throw new Error('You must pass a valid discriminator Schema'); } @@ -205,7 +205,7 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu model.schema.discriminators[name] = schema; - if (model.discriminators[name] && !schema.options.overwriteModels) { + if (model.discriminators[name] && !schema.options.overwriteModels && !overwriteExisting) { throw new Error('Discriminator with name "' + name + '" already exists'); } diff --git a/lib/model.js b/lib/model.js index f5e4f12f967..55f922f62c2 100644 --- a/lib/model.js +++ b/lib/model.js @@ -23,6 +23,7 @@ const VersionError = require('./error/version'); const ParallelSaveError = require('./error/parallelSave'); const applyDefaultsHelper = require('./helpers/document/applyDefaults'); const applyDefaultsToPOJO = require('./helpers/model/applyDefaultsToPOJO'); +const applyEmbeddedDiscriminators = require('./helpers/discriminator/applyEmbeddedDiscriminators'); const applyHooks = require('./helpers/model/applyHooks'); const applyMethods = require('./helpers/model/applyMethods'); const applyProjection = require('./helpers/projection/applyProjection'); @@ -5001,6 +5002,15 @@ Model.__subclass = function subclass(conn, schema, collection) { Model.recompileSchema = function recompileSchema() { this.prototype.$__setSchema(this.schema); + + if (this.schema._applyDiscriminators != null) { + for (const disc of this.schema._applyDiscriminators.keys()) { + this.discriminator(disc, this.schema._applyDiscriminators.get(disc)); + } + } + + const overwriteExisting = true; + applyEmbeddedDiscriminators(this.schema, new WeakSet(), overwriteExisting); }; /** diff --git a/lib/schema/subdocument.js b/lib/schema/subdocument.js index 783279772a7..5116ed0cfa8 100644 --- a/lib/schema/subdocument.js +++ b/lib/schema/subdocument.js @@ -325,7 +325,7 @@ SchemaSubdocument.prototype.discriminator = function(name, schema, options) { schema = schema.clone(); } - schema = discriminator(this.caster, name, schema, value); + schema = discriminator(this.caster, name, schema, value, null, null, options.overwriteExisting); this.caster.discriminators[name] = _createConstructor(schema, this.caster); diff --git a/test/model.test.js b/test/model.test.js index 76e8341ec1b..ba0693eb214 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -7419,6 +7419,47 @@ describe('Model', function() { assert.equal(doc.myVirtual, 'Hello from myVirtual'); }); + it('supports recompiling model with new discriminators (gh-14444) (gh-14296)', function() { + // Define discriminated schema + const decoratorSchema = new Schema({ + type: { type: String, required: true } + }, { discriminatorKey: 'type' }); + + class Decorator { + whoAmI() { return 'I am BaseDeco'; } + } + decoratorSchema.loadClass(Decorator); + + // Define discriminated class before model is compiled + class Deco1 extends Decorator { whoAmI() { return 'I am Test1'; }} + const deco1Schema = new Schema({}).loadClass(Deco1); + deco1Schema.loadClass(Deco1); + decoratorSchema.discriminator('Test1', deco1Schema); + + // Define model that uses discriminated schema + const shopSchema = new Schema({ + item: { type: decoratorSchema, required: true } + }); + + class Shop {} + shopSchema.loadClass(Shop); + const shopModel = db.model('Test', shopSchema); + + // Define another discriminated class after the model is compiled + class Deco2 extends Decorator { whoAmI() { return 'I am Test2'; }} + const deco2Schema = new Schema({}).loadClass(Deco2); + deco2Schema.loadClass(Deco2); + decoratorSchema.discriminator('Test2', deco2Schema); + + shopModel.recompileSchema(); + + let instance = new shopModel({ item: { type: 'Test1' } }); + assert.equal(instance.item.whoAmI(), 'I am Test1'); + + instance = new shopModel({ item: { type: 'Test2' } }); + assert.equal(instance.item.whoAmI(), 'I am Test2'); + }); + it('inserts versionKey even if schema has `toObject.versionKey` set to false (gh-14344)', async function() { const schema = new mongoose.Schema( { name: String },