Skip to content

Commit 42bf516

Browse files
authored
Merge pull request #14131 from Automattic/vkarpov15/gh-14109
fix: allow adding discriminators using `Schema.prototype.discriminator()` to subdocuments after defining parent schema
2 parents e1426ca + 16e5308 commit 42bf516

File tree

5 files changed

+124
-12
lines changed

5 files changed

+124
-12
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
module.exports = applyEmbeddedDiscriminators;
4+
5+
function applyEmbeddedDiscriminators(schema, seen = new WeakSet()) {
6+
if (seen.has(schema)) {
7+
return;
8+
}
9+
seen.add(schema);
10+
for (const path of Object.keys(schema.paths)) {
11+
const schemaType = schema.paths[path];
12+
if (!schemaType.schema) {
13+
continue;
14+
}
15+
applyEmbeddedDiscriminators(schemaType.schema, seen);
16+
if (!schemaType.schema._applyDiscriminators) {
17+
continue;
18+
}
19+
for (const disc of schemaType.schema._applyDiscriminators.keys()) {
20+
schemaType.discriminator(disc, schemaType.schema._applyDiscriminators.get(disc));
21+
}
22+
}
23+
}

lib/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const sanitizeFilter = require('./helpers/query/sanitizeFilter');
3232
const isBsonType = require('./helpers/isBsonType');
3333
const MongooseError = require('./error/mongooseError');
3434
const SetOptionError = require('./error/setOptionError');
35+
const applyEmbeddedDiscriminators = require('./helpers/discriminator/applyEmbeddedDiscriminators');
3536

3637
const defaultMongooseSymbol = Symbol.for('mongoose:default');
3738

@@ -629,6 +630,8 @@ Mongoose.prototype._model = function(name, schema, collection, options) {
629630
}
630631
}
631632

633+
applyEmbeddedDiscriminators(schema);
634+
632635
return model;
633636
};
634637

lib/schema/SubdocumentPath.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,6 @@ function SubdocumentPath(schema, path, options) {
5555
this.$isSingleNested = true;
5656
this.base = schema.base;
5757
SchemaType.call(this, path, options, 'Embedded');
58-
if (schema._applyDiscriminators != null && !options?._skipApplyDiscriminators) {
59-
for (const disc of schema._applyDiscriminators.keys()) {
60-
this.discriminator(disc, schema._applyDiscriminators.get(disc));
61-
}
62-
}
6358
}
6459

6560
/*!

lib/schema/documentarray.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,6 @@ function DocumentArrayPath(key, schema, options, schemaOptions) {
8888

8989
this.$embeddedSchemaType.caster = this.Constructor;
9090
this.$embeddedSchemaType.schema = this.schema;
91-
92-
if (schema._applyDiscriminators != null && !options?._skipApplyDiscriminators) {
93-
for (const disc of schema._applyDiscriminators.keys()) {
94-
this.discriminator(disc, schema._applyDiscriminators.get(disc));
95-
}
96-
}
9791
}
9892

9993
/**
@@ -528,7 +522,7 @@ DocumentArrayPath.prototype.cast = function(value, doc, init, prev, options) {
528522

529523
DocumentArrayPath.prototype.clone = function() {
530524
const options = Object.assign({}, this.options);
531-
const schematype = new this.constructor(this.path, this.schema, { ...options, _skipApplyDiscriminators: true }, this.schemaOptions);
525+
const schematype = new this.constructor(this.path, this.schema, options, this.schemaOptions);
532526
schematype.validators = this.validators.slice();
533527
if (this.requiredValidator !== undefined) {
534528
schematype.requiredValidator = this.requiredValidator;

test/document.test.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12657,6 +12657,103 @@ describe('document', function() {
1265712657
);
1265812658
});
1265912659

12660+
it('handles embedded discriminators defined using Schema.prototype.discriminator after defining schema (gh-14109) (gh-13898)', async function() {
12661+
const baseNestedDiscriminated = new Schema({
12662+
type: { type: Number, required: true }
12663+
}, { discriminatorKey: 'type' });
12664+
12665+
class BaseClass {
12666+
whoAmI() {
12667+
return 'I am baseNestedDiscriminated';
12668+
}
12669+
}
12670+
12671+
baseNestedDiscriminated.loadClass(BaseClass);
12672+
12673+
class NumberTyped extends BaseClass {
12674+
whoAmI() {
12675+
return 'I am NumberTyped';
12676+
}
12677+
}
12678+
12679+
class StringTyped extends BaseClass {
12680+
whoAmI() {
12681+
return 'I am StringTyped';
12682+
}
12683+
}
12684+
12685+
const containsNestedSchema = new Schema({
12686+
nestedDiscriminatedTypes: { type: [baseNestedDiscriminated], required: true }
12687+
});
12688+
12689+
// After `containsNestedSchema`, in #13898 test these were before `containsNestedSchema`
12690+
baseNestedDiscriminated.discriminator(1, new Schema({}).loadClass(NumberTyped));
12691+
baseNestedDiscriminated.discriminator('3', new Schema({}).loadClass(StringTyped));
12692+
12693+
class ContainsNested {
12694+
whoAmI() {
12695+
return 'I am ContainsNested';
12696+
}
12697+
}
12698+
containsNestedSchema.loadClass(ContainsNested);
12699+
12700+
const Test = db.model('Test', containsNestedSchema);
12701+
const instance = await Test.create({ type: 1, nestedDiscriminatedTypes: [{ type: 1 }, { type: '3' }] });
12702+
assert.deepStrictEqual(
12703+
instance.nestedDiscriminatedTypes.map(i => i.whoAmI()),
12704+
['I am NumberTyped', 'I am StringTyped']
12705+
);
12706+
});
12707+
12708+
it('handles embedded discriminators on nested path defined using Schema.prototype.discriminator (gh-14109) (gh-13898)', async function() {
12709+
const baseNestedDiscriminated = new Schema({
12710+
type: { type: Number, required: true }
12711+
}, { discriminatorKey: 'type' });
12712+
12713+
class BaseClass {
12714+
whoAmI() {
12715+
return 'I am baseNestedDiscriminated';
12716+
}
12717+
}
12718+
12719+
baseNestedDiscriminated.loadClass(BaseClass);
12720+
12721+
class NumberTyped extends BaseClass {
12722+
whoAmI() {
12723+
return 'I am NumberTyped';
12724+
}
12725+
}
12726+
12727+
class StringTyped extends BaseClass {
12728+
whoAmI() {
12729+
return 'I am StringTyped';
12730+
}
12731+
}
12732+
12733+
const containsNestedSchema = new Schema({
12734+
nestedDiscriminatedTypes: { type: [baseNestedDiscriminated], required: true }
12735+
});
12736+
12737+
baseNestedDiscriminated.discriminator(1, new Schema({}).loadClass(NumberTyped));
12738+
baseNestedDiscriminated.discriminator('3', new Schema({}).loadClass(StringTyped));
12739+
12740+
const l2Schema = new Schema({ l3: containsNestedSchema });
12741+
const l1Schema = new Schema({ l2: l2Schema });
12742+
12743+
const Test = db.model('Test', l1Schema);
12744+
const instance = await Test.create({
12745+
l2: {
12746+
l3: {
12747+
nestedDiscriminatedTypes: [{ type: 1 }, { type: '3' }]
12748+
}
12749+
}
12750+
});
12751+
assert.deepStrictEqual(
12752+
instance.l2.l3.nestedDiscriminatedTypes.map(i => i.whoAmI()),
12753+
['I am NumberTyped', 'I am StringTyped']
12754+
);
12755+
});
12756+
1266012757
it('can use `collection` as schema name (gh-13956)', async function() {
1266112758
const schema = new mongoose.Schema({ name: String, collection: String });
1266212759
const Test = db.model('Test', schema);

0 commit comments

Comments
 (0)