From 48cb903af8c98d3ae8d76e753fa2850f4fe7feaf Mon Sep 17 00:00:00 2001 From: Zak Henry Date: Mon, 18 Jul 2016 18:06:30 +0100 Subject: [PATCH] feat(relations): Implement generics for relations to correctly type the type lookup methods --- src/common/metadata/metadata.ts | 2 +- .../models/relations/belongsTo.decorator.ts | 19 ++++++++++--------- .../models/relations/hand.model.fixture.ts | 5 +---- .../models/relations/hasOne.decorator.ts | 13 ++++++------- src/common/models/relations/index.ts | 18 +++++++++++------- src/common/models/relations/relations.spec.ts | 13 +------------ .../models/relations/thumb.model.fixture.ts | 4 +--- 7 files changed, 31 insertions(+), 43 deletions(-) diff --git a/src/common/metadata/metadata.ts b/src/common/metadata/metadata.ts index b5500f6..0f7f089 100644 --- a/src/common/metadata/metadata.ts +++ b/src/common/metadata/metadata.ts @@ -19,7 +19,7 @@ export interface PropertyDefinition { export interface ModelMetadata { storageKey?: string; tableOptions?: TableOptions; - relations?: Map>; + relations?: Map>>; storedProperties?: Map identifierKey?: string; timestamps?: { diff --git a/src/common/models/relations/belongsTo.decorator.ts b/src/common/models/relations/belongsTo.decorator.ts index d1243b8..38993b7 100644 --- a/src/common/models/relations/belongsTo.decorator.ts +++ b/src/common/models/relations/belongsTo.decorator.ts @@ -2,12 +2,15 @@ * @module common */ /** End Typedoc Module Declaration */ -import { ModelStatic } from '../model'; -import { initializeRelationMap, ForeignRelationModelGetter, Relation } from './index'; +import { ModelStatic, ModelConstructor, AbstractModel } from '../model'; +import { + initializeRelationMap, ForeignRelationModelGetter, Relation, + ViaPropertyDefinition +} from './index'; import { RelationOptions } from 'typeorm/decorator/options/RelationOptions'; /** - * Defines the relationship between the current model and a foreign model vial the decorated key + * Defines the relationship between the current model and a foreign model via the decorated key * * Example: * ```typescript @@ -15,20 +18,18 @@ import { RelationOptions } from 'typeorm/decorator/options/RelationOptions'; * @Model * class Thumb extends AbstractModel { * - * @BelongsTo(f => HandModel) + * @BelongsTo(f => HandModel, hand => hand.handId) * public hand: HandModel; * - * public handId: number; * } * * ``` - * Foreign model property is only required if there is no type annotation */ -export function BelongsTo(foreignTypeGetter: ForeignRelationModelGetter, joinOptions?: RelationOptions): PropertyDecorator { - return (target: any, propertyKey: string) => { +export function BelongsTo(foreignTypeGetter: ForeignRelationModelGetter, viaProperty:ViaPropertyDefinition, joinOptions?: RelationOptions): PropertyDecorator { + return (target: ModelConstructor, propertyKey: string) => { initializeRelationMap(target, 'belongsTo'); target.constructor.__metadata.relations.get('belongsTo') - .set(propertyKey, new Relation(target.constructor, foreignTypeGetter, joinOptions)); + .set(propertyKey, new Relation(target.constructor, foreignTypeGetter, viaProperty, joinOptions)); }; } diff --git a/src/common/models/relations/hand.model.fixture.ts b/src/common/models/relations/hand.model.fixture.ts index 2cc6b4c..13336d1 100644 --- a/src/common/models/relations/hand.model.fixture.ts +++ b/src/common/models/relations/hand.model.fixture.ts @@ -12,9 +12,6 @@ export class HandModel extends AbstractModel { public name: string; @HasOne(f => ThumbModel) - left: ThumbModel; - - @HasOne(f => ThumbModel) - right: ThumbModel; + thumb: ThumbModel; } diff --git a/src/common/models/relations/hasOne.decorator.ts b/src/common/models/relations/hasOne.decorator.ts index 70db2ea..94afdf7 100644 --- a/src/common/models/relations/hasOne.decorator.ts +++ b/src/common/models/relations/hasOne.decorator.ts @@ -2,12 +2,12 @@ * @module common */ /** End Typedoc Module Declaration */ -import { ModelStatic } from '../model'; +import { ModelStatic, AbstractModel, ModelConstructor } from '../model'; import { initializeRelationMap, ForeignRelationModelGetter, Relation } from './index'; import { RelationOptions } from 'typeorm/decorator/options/RelationOptions'; /** - * Defines the relationship between the current model and a foreign model vial the decorated key + * Defines the relationship between the current model and a foreign model via the decorated key * * Example: * ```typescript @@ -15,18 +15,17 @@ import { RelationOptions } from 'typeorm/decorator/options/RelationOptions'; * @Model * class Hand extends AbstractModel { * - * @HasOne() + * @HasOne(f => ThumbModel) * public thumb: ThumbModel; * } * * ``` - * Foreign model property is only required if there is no type annotation */ -export function HasOne(foreignTypeGetter: ForeignRelationModelGetter, joinOptions?: RelationOptions): PropertyDecorator { - return (target: any, propertyKey: string) => { +export function HasOne(foreignTypeGetter: ForeignRelationModelGetter, joinOptions?: RelationOptions): PropertyDecorator { + return (target: ModelConstructor, propertyKey: string) => { initializeRelationMap(target, 'hasOne'); target.constructor.__metadata.relations.get('hasOne') - .set(propertyKey, new Relation(target.constructor, foreignTypeGetter, joinOptions)); + .set(propertyKey, new Relation(target.constructor, foreignTypeGetter, null, joinOptions)); }; } diff --git a/src/common/models/relations/index.ts b/src/common/models/relations/index.ts index 21759bf..d7ab15a 100644 --- a/src/common/models/relations/index.ts +++ b/src/common/models/relations/index.ts @@ -2,26 +2,30 @@ * @module common */ /** End Typedoc Module Declaration */ -import { ModelStatic, ModelConstructor } from '../model'; +import { ModelStatic, ModelConstructor, AbstractModel } from '../model'; import { initializeMetadata } from '../../metadata/metadata'; export type RelationType = 'hasOne' | 'hasMany' | 'belongsTo' | 'belongsToMany'; - /** * This is a crude method to two-way register the type of binding for relations. This is to overcome * a limitation of Typescripts design-time decorators and node's module resolution. * @see https://github.com/Microsoft/TypeScript/issues/4521 */ -export type ForeignRelationModelGetter = (thisStatic?:ModelStatic) => ModelStatic; +export type ForeignRelationModelGetter = (thisStatic?: ModelStatic|any) => ModelStatic; + +export type ViaPropertyDefinition = (foreign: T) => any; -export class Relation { +export class Relation { - constructor(public model:ModelStatic, private foreignRelationModelGetter:ForeignRelationModelGetter, public databaseOptions?:any) { + constructor(public model: ModelStatic, + private foreignRelationModelGetter: ForeignRelationModelGetter, + public viaProperty: ViaPropertyDefinition, + public databaseOptions?: any) { } - public get foreign(){ + public get foreign() { return this.foreignRelationModelGetter(this.model); } @@ -40,7 +44,7 @@ export function initializeRelationMap(target: ModelConstructor, type: Relat target.constructor.__metadata.relations = new Map(); } - if (!target.constructor.__metadata.relations.has(type)){ + if (!target.constructor.__metadata.relations.has(type)) { target.constructor.__metadata.relations.set(type, new Map()); } diff --git a/src/common/models/relations/relations.spec.ts b/src/common/models/relations/relations.spec.ts index abb11ac..2185609 100644 --- a/src/common/models/relations/relations.spec.ts +++ b/src/common/models/relations/relations.spec.ts @@ -12,18 +12,7 @@ describe('Model Relations', () => { expect(relations) .toBeDefined(); expect(relations.get('hasOne') - .get('left').foreign) - .toEqual(ThumbModel); - - }); - - it('registers hasOne relationship correctly when the type is not passed', () => { - - const model = new HandModel(); - - const relations = model.getMetadata().relations; - expect(relations.get('hasOne') - .get('right').foreign) + .get('thumb').foreign) .toEqual(ThumbModel); }); diff --git a/src/common/models/relations/thumb.model.fixture.ts b/src/common/models/relations/thumb.model.fixture.ts index 0f1e440..ffbd24a 100644 --- a/src/common/models/relations/thumb.model.fixture.ts +++ b/src/common/models/relations/thumb.model.fixture.ts @@ -11,9 +11,7 @@ export class ThumbModel extends AbstractModel { public name: string; - @BelongsTo(f => HandModel) + @BelongsTo(f => HandModel, hand => hand.handId) public hand: HandModel; - public handId: string; - }