Skip to content

Commit 719e10b

Browse files
feat: inherit virtuals from parent classes
1 parent 19b935e commit 719e10b

File tree

4 files changed

+46
-28
lines changed

4 files changed

+46
-28
lines changed

lib/factories/virtuals.factory.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Type } from '@nestjs/common';
2+
import { isUndefined } from '@nestjs/common/utils/shared.utils';
23
import * as mongoose from 'mongoose';
34
import { TypeMetadataStorage } from '../storages/type-metadata.storage';
45

@@ -7,18 +8,27 @@ export class VirtualsFactory {
78
target: Type<TClass>,
89
schema: mongoose.Schema<TClass>,
910
): void {
10-
const virtuals = TypeMetadataStorage.getVirtualsMetadataByTarget(target);
11+
let parent = target;
1112

12-
virtuals.forEach(({ options, name, getter, setter }) => {
13-
const virtual = schema.virtual(name, options);
14-
15-
if (getter) {
16-
virtual.get(getter);
13+
while (!isUndefined(parent.prototype)) {
14+
if (parent === Function.prototype) {
15+
break;
1716
}
17+
const virtuals = TypeMetadataStorage.getVirtualsMetadataByTarget(parent);
1818

19-
if (setter) {
20-
virtual.set(setter);
21-
}
22-
});
19+
virtuals.forEach(({ options, name, getter, setter }) => {
20+
const virtual = schema.virtual(name, options);
21+
22+
if (getter) {
23+
virtual.get(getter);
24+
}
25+
26+
if (setter) {
27+
virtual.set(setter);
28+
}
29+
});
30+
31+
parent = Object.getPrototypeOf(parent);
32+
}
2333
}
2434
}

lib/storages/type-metadata.storage.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export class TypeMetadataStorageHost {
2626
return this.schemas.find((item) => item.target === target);
2727
}
2828

29+
getVirtualsMetadataByTarget<TClass>(targetFilter: Type<TClass>) {
30+
return this.virtuals.filter(({ target }) => target === targetFilter);
31+
}
32+
2933
private compileClassMetadata(metadata: SchemaMetadata) {
3034
const belongsToClass = isTargetEqual.bind(undefined, metadata);
3135

@@ -39,10 +43,6 @@ export class TypeMetadataStorageHost {
3943
) {
4044
return this.properties.filter(belongsToClass);
4145
}
42-
43-
getVirtualsMetadataByTarget<TClass>(targetFilter: Type<TClass>) {
44-
return this.virtuals.filter(({ target }) => target === targetFilter);
45-
}
4646
}
4747

4848
const globalRef = global as any;

tests/e2e/schema.factory.spec.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { VirtualTypeOptions } from 'mongoose';
21
import { Prop, Schema, SchemaFactory, Virtual } from '../../lib';
32

43
@Schema({ validateBeforeSave: false, _id: true, autoIndex: true })
@@ -70,7 +69,7 @@ describe('SchemaFactory', () => {
7069
);
7170
});
7271

73-
it('should define for schema a virtuals with options', () => {
72+
it('should add virtuals with corresponding options', () => {
7473
const {
7574
virtuals: { virtualPropsWithOptions },
7675
} = SchemaFactory.createForClass(ExampleClass) as any;
@@ -89,7 +88,7 @@ describe('SchemaFactory', () => {
8988
);
9089
});
9190

92-
it('should define for schema a virtual with getter/setter functions', () => {
91+
it('should add virtuals with corresponding getter and setter functions', () => {
9392
const {
9493
virtuals: { virtualPropsWithGetterSetterFunctions },
9594
} = SchemaFactory.createForClass(ExampleClass) as any;
@@ -103,4 +102,13 @@ describe('SchemaFactory', () => {
103102
}),
104103
);
105104
});
105+
106+
it('should inherit virtuals from parent classes', () => {
107+
@Schema()
108+
class ChildClass extends ExampleClass {}
109+
const { virtuals } = SchemaFactory.createForClass(ChildClass) as any;
110+
111+
expect(virtuals.virtualPropsWithOptions).toBeDefined();
112+
expect(virtuals.virtualPropsWithGetterSetterFunctions).toBeDefined();
113+
});
106114
});

tests/e2e/virtual.factory.spec.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe('VirtualsFactory', () => {
5959
};
6060

6161
beforeEach(() => {
62-
(schemaMock.virtual as any) = jest.fn(() => ({
62+
schemaMock.virtual = jest.fn(() => ({
6363
get: setVirtualGetterFunctionMock,
6464
set: setVirtualSetterFunctionMock,
6565
}));
@@ -70,23 +70,23 @@ describe('VirtualsFactory', () => {
7070
});
7171

7272
describe('Schema virtual definition', () => {
73-
it('should not define virtuals if there is no stored virtual definition', () => {
73+
it('should not define any virtuals if no virtual definitions are stored', () => {
7474
TypeMetadataStorage['virtuals'] = [];
7575

7676
VirtualsFactory.inspect(targetConstructorMock, schemaMock);
7777

78-
expect(schemaMock.virtual).toHaveBeenCalledTimes(0);
78+
expect(schemaMock.virtual).not.toHaveBeenCalled();
7979
});
8080

81-
it('should not define virtuals if there is no stored virtual definition linked to schema model', () => {
81+
it('should not define virtuals if there are no stored virtual definitions linked to the schema model', () => {
8282
TypeMetadataStorage['virtuals'] = [virtualMetadataNotLikedToModelMock];
8383

8484
VirtualsFactory.inspect(targetConstructorMock, schemaMock);
8585

86-
expect(schemaMock.virtual).toHaveBeenCalledTimes(0);
86+
expect(schemaMock.virtual).not.toHaveBeenCalled();
8787
});
8888

89-
it('should defines virtual for each stored virtualMetadata linked to schema model', () => {
89+
it('should define virtuals for each stored virtual metadata linked to the schema model', () => {
9090
TypeMetadataStorage['virtuals'] = [
9191
virtualMetadataWithOnlyRequiredAttributesMock,
9292
virtualMetadataNotLikedToModelMock,
@@ -105,19 +105,19 @@ describe('VirtualsFactory', () => {
105105
});
106106
});
107107

108-
describe('Schema virtual getter/setter definition', () => {
109-
it('should not call the getter/setter definition method if no getter/setter defined in the stored virtual metadata linked to the schema model', () => {
108+
describe('Schema virtual getter/setter definitions', () => {
109+
it('should not call the getter/setter methods if no getter/setter is defined in the stored virtual metadata linked to the schema model', () => {
110110
TypeMetadataStorage['virtuals'] = [
111111
virtualMetadataWithOptionsMock,
112112
] as VirtualMetadataInterface[];
113113

114114
VirtualsFactory.inspect(targetConstructorMock, schemaMock);
115115

116-
expect(setVirtualGetterFunctionMock).toHaveBeenCalledTimes(0);
117-
expect(setVirtualSetterFunctionMock).toHaveBeenCalledTimes(0);
116+
expect(setVirtualGetterFunctionMock).not.toHaveBeenCalled();
117+
expect(setVirtualSetterFunctionMock).not.toHaveBeenCalled();
118118
});
119119

120-
it('should call the getter/setter definition method for each stored virtuals metadata with defined getter/setter linked to the schema model', () => {
120+
it('should invoke the getter/setter methods for each stored virtual metadata with defined getter/setter linked to the schema model', () => {
121121
TypeMetadataStorage['virtuals'] = [
122122
virtualMetadataWithOptionsMock,
123123
virtualMetadataWithGetterMock,

0 commit comments

Comments
 (0)