Skip to content

Commit

Permalink
feat: inherit virtuals from parent classes
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Jan 29, 2025
1 parent 19b935e commit 719e10b
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 28 deletions.
30 changes: 20 additions & 10 deletions lib/factories/virtuals.factory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Type } from '@nestjs/common';
import { isUndefined } from '@nestjs/common/utils/shared.utils';
import * as mongoose from 'mongoose';
import { TypeMetadataStorage } from '../storages/type-metadata.storage';

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

virtuals.forEach(({ options, name, getter, setter }) => {
const virtual = schema.virtual(name, options);

if (getter) {
virtual.get(getter);
while (!isUndefined(parent.prototype)) {
if (parent === Function.prototype) {
break;
}
const virtuals = TypeMetadataStorage.getVirtualsMetadataByTarget(parent);

if (setter) {
virtual.set(setter);
}
});
virtuals.forEach(({ options, name, getter, setter }) => {
const virtual = schema.virtual(name, options);

if (getter) {
virtual.get(getter);
}

if (setter) {
virtual.set(setter);
}
});

parent = Object.getPrototypeOf(parent);
}
}
}
8 changes: 4 additions & 4 deletions lib/storages/type-metadata.storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export class TypeMetadataStorageHost {
return this.schemas.find((item) => item.target === target);
}

getVirtualsMetadataByTarget<TClass>(targetFilter: Type<TClass>) {
return this.virtuals.filter(({ target }) => target === targetFilter);
}

private compileClassMetadata(metadata: SchemaMetadata) {
const belongsToClass = isTargetEqual.bind(undefined, metadata);

Expand All @@ -39,10 +43,6 @@ export class TypeMetadataStorageHost {
) {
return this.properties.filter(belongsToClass);
}

getVirtualsMetadataByTarget<TClass>(targetFilter: Type<TClass>) {
return this.virtuals.filter(({ target }) => target === targetFilter);
}
}

const globalRef = global as any;
Expand Down
14 changes: 11 additions & 3 deletions tests/e2e/schema.factory.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { VirtualTypeOptions } from 'mongoose';
import { Prop, Schema, SchemaFactory, Virtual } from '../../lib';

@Schema({ validateBeforeSave: false, _id: true, autoIndex: true })
Expand Down Expand Up @@ -70,7 +69,7 @@ describe('SchemaFactory', () => {
);
});

it('should define for schema a virtuals with options', () => {
it('should add virtuals with corresponding options', () => {
const {
virtuals: { virtualPropsWithOptions },
} = SchemaFactory.createForClass(ExampleClass) as any;
Expand All @@ -89,7 +88,7 @@ describe('SchemaFactory', () => {
);
});

it('should define for schema a virtual with getter/setter functions', () => {
it('should add virtuals with corresponding getter and setter functions', () => {
const {
virtuals: { virtualPropsWithGetterSetterFunctions },
} = SchemaFactory.createForClass(ExampleClass) as any;
Expand All @@ -103,4 +102,13 @@ describe('SchemaFactory', () => {
}),
);
});

it('should inherit virtuals from parent classes', () => {
@Schema()
class ChildClass extends ExampleClass {}
const { virtuals } = SchemaFactory.createForClass(ChildClass) as any;

expect(virtuals.virtualPropsWithOptions).toBeDefined();
expect(virtuals.virtualPropsWithGetterSetterFunctions).toBeDefined();
});
});
22 changes: 11 additions & 11 deletions tests/e2e/virtual.factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('VirtualsFactory', () => {
};

beforeEach(() => {
(schemaMock.virtual as any) = jest.fn(() => ({
schemaMock.virtual = jest.fn(() => ({
get: setVirtualGetterFunctionMock,
set: setVirtualSetterFunctionMock,
}));
Expand All @@ -70,23 +70,23 @@ describe('VirtualsFactory', () => {
});

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

VirtualsFactory.inspect(targetConstructorMock, schemaMock);

expect(schemaMock.virtual).toHaveBeenCalledTimes(0);
expect(schemaMock.virtual).not.toHaveBeenCalled();
});

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

VirtualsFactory.inspect(targetConstructorMock, schemaMock);

expect(schemaMock.virtual).toHaveBeenCalledTimes(0);
expect(schemaMock.virtual).not.toHaveBeenCalled();
});

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

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

VirtualsFactory.inspect(targetConstructorMock, schemaMock);

expect(setVirtualGetterFunctionMock).toHaveBeenCalledTimes(0);
expect(setVirtualSetterFunctionMock).toHaveBeenCalledTimes(0);
expect(setVirtualGetterFunctionMock).not.toHaveBeenCalled();
expect(setVirtualSetterFunctionMock).not.toHaveBeenCalled();
});

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

0 comments on commit 719e10b

Please sign in to comment.