-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Description
Do you want to request a feature or report a bug?
BUG
What is the current behavior?
loadClass will add virtuals in following order:
- Extended class
- Class
applyGetters and applySetters will execute the functions in reverse order.
- Later added
- Earlier added
let v = value;
for (let l = this.getters.length - 1; l >= 0; l--) {
v = this.getters[l].call(doc, v, this, doc);
}
return v;
- TechnicalUser.displayAs -> name
- User.displayAs -> null
Because of the order mismatch the wrong value will be used.
I am not sure which order is the intended. I have also not testet what happens if a virtual with the same name is registered twice without loadClass (but if I have a look at the code line, the same problem should be reproducible).
If the current behavior is a bug, please provide the steps to reproduce.
Example Schemas (written in NestJS):
Base User Model:
@Schema({ discriminatorKey: 'type' })
@ObjectType()
export class User {
@Prop()
@Field((returns) => ObjectIdScalar, { name: 'id' })
_id: ObjectId;
@Field((type) => UserTypeEnum)
type: string;
@Field((type) => String, { nullable: true, name: 'displayAs' })
get displayAs() {
return null;
}
}
export const UserSchema = SchemaFactory.createForClass(User);
UserSchema.loadClass(User);
Extending TechnicalUser (shortened)
@Schema()
@ObjectType()
export class TechnicalUser extends User {
@Prop()
@Field()
name: string;
@Prop()
@Field()
identifier: string;
@Field((type) => String, { nullable: true })
get displayAs() {
return this.name;
}
}
export const TechnicalUserSchema = SchemaFactory.createForClass(TechnicalUser);
TechnicalUserSchema.loadClass(TechnicalUser);
The loadClass method will detect the displayAs virtual getters in both models.
But if I use extends in TechnicalUser, the execution order is the following:
- Detect prototype class (User)
- Add displayAs virtual getter from User (which returns null)
- Add displayAs virtual getter from TechnicalUser (which returns the name Prop)
Codelines:
- Recursive call for extended classes first: https://github.com/Automattic/mongoose/blob/master/lib/schema.js#L1903
- Add new virtual function: https://github.com/Automattic/mongoose/blob/master/lib/schema.js#L1930
The getters in VirtualType has these order:
- User.dispayAs
- TechnicalUser.displayAs
After fetching the user records by find without any condition, the getters will be applied in reversed order:
- TechnicalUser.displayAs -> name
- User.displayAs -> null
all functions will be executed and overwrites the previous return value.
https://github.com/Automattic/mongoose/blob/master/lib/virtualtype.js#L141-L153
What is the expected behavior?
In my opinion, there are two options:
- Early returns
- Reverse the execution order at getters
For early returns:
Replace https://github.com/Automattic/mongoose/blob/master/lib/virtualtype.js#L148-L152
let v = value;
for (let l = this.getters.length - 1; l >= 0; l--) {
v = this.getters[l].call(doc, v, this, doc);
}
return v;
with
let v = value;
for (let l = this.getters.length - 1; l >= 0; l--) {
return this.getters[l].call(doc, v, this, doc);
}
return v;
Or reverse the order (causes unnecessary calls):
let v = value;
for (let l = 0; l < this.getters.length; l++) {
v = this.getters[l].call(doc, v, this, doc);
}
return v;
For setters the reversed order does also makes no sense because the more specific return will also be overwritten.
What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.
From package-lock.json:
"node_modules/@nestjs/mongoose": {
"version": "7.2.4",
"resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-7.2.4.tgz",
"integrity": "sha512-NTE/IwijFUEJytPHLoHRYe0X5p16W1Awf8tm6I3mWsIUuBnSDfMyN0fy30uVaM/exYvCkMwEsAVpeSeKVPMHxg==",
"peerDependencies": {
"@nestjs/common": "^6.0.0 || ^7.0.0",
"@nestjs/core": "^6.0.0 || ^7.0.0",
"mongoose": "^5.11.15",
"reflect-metadata": "^0.1.12",
"rxjs": "^6.0.0"
}
},
"node_modules/mongoose": {
"version": "5.11.18",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.11.18.tgz",
"integrity": "sha512-RsrPR9nhkXZbO3ml0DcmdbfeMvFNhgFrP81S6o1P+lFnDTNEKYnGNRCIL+ojD69wj7H5jJaAdZ0SJ5IlKxCHqw==",
"dependencies": {
"@types/mongodb": "^3.5.27",
"bson": "^1.1.4",
"kareem": "2.3.2",
"mongodb": "3.6.4",
"mongoose-legacy-pluralize": "1.0.2",
"mpath": "0.8.3",
"mquery": "3.2.4",
"ms": "2.1.2",
"regexp-clone": "1.0.0",
"safe-buffer": "5.2.1",
"sift": "7.0.1",
"sliced": "1.0.1"
},
"engines": {
"node": ">=4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},