Skip to content
This repository has been archived by the owner on May 8, 2020. It is now read-only.

Commit

Permalink
feat(relations): Implement generics for relations to correctly type t…
Browse files Browse the repository at this point in the history
…he type lookup methods
  • Loading branch information
zakhenry committed Jul 18, 2016
1 parent e78267e commit 48cb903
Show file tree
Hide file tree
Showing 7 changed files with 31 additions and 43 deletions.
2 changes: 1 addition & 1 deletion src/common/metadata/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface PropertyDefinition {
export interface ModelMetadata {
storageKey?: string;
tableOptions?: TableOptions;
relations?: Map<RelationType, Map<string, Relation>>;
relations?: Map<RelationType, Map<string, Relation<any, any>>>;
storedProperties?: Map<string, PropertyDefinition>
identifierKey?: string;
timestamps?: {
Expand Down
19 changes: 10 additions & 9 deletions src/common/models/relations/belongsTo.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,34 @@
* @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
*
* @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<M extends AbstractModel, F extends AbstractModel>(foreignTypeGetter: ForeignRelationModelGetter<M, F>, viaProperty:ViaPropertyDefinition<F>, joinOptions?: RelationOptions): PropertyDecorator {
return (target: ModelConstructor<M>, 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));
};
}
5 changes: 1 addition & 4 deletions src/common/models/relations/hand.model.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ export class HandModel extends AbstractModel {
public name: string;

@HasOne(f => ThumbModel)
left: ThumbModel;

@HasOne(f => ThumbModel)
right: ThumbModel;
thumb: ThumbModel;

}
13 changes: 6 additions & 7 deletions src/common/models/relations/hasOne.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,30 @@
* @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
*
* @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<M extends AbstractModel, F extends AbstractModel>(foreignTypeGetter: ForeignRelationModelGetter<M, F>, joinOptions?: RelationOptions): PropertyDecorator {
return (target: ModelConstructor<M>, 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));
};
}
18 changes: 11 additions & 7 deletions src/common/models/relations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>) => ModelStatic<any>;
export type ForeignRelationModelGetter<T extends AbstractModel, F extends AbstractModel> = (thisStatic?: ModelStatic<T>|any) => ModelStatic<F>;

export type ViaPropertyDefinition<T> = (foreign: T) => any;

export class Relation {
export class Relation<M extends AbstractModel, F extends AbstractModel> {

constructor(public model:ModelStatic<any>, private foreignRelationModelGetter:ForeignRelationModelGetter, public databaseOptions?:any) {
constructor(public model: ModelStatic<M>,
private foreignRelationModelGetter: ForeignRelationModelGetter<M, F>,
public viaProperty: ViaPropertyDefinition<F>,
public databaseOptions?: any) {

}

public get foreign(){
public get foreign() {
return this.foreignRelationModelGetter(this.model);
}

Expand All @@ -40,7 +44,7 @@ export function initializeRelationMap(target: ModelConstructor<any>, 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());
}

Expand Down
13 changes: 1 addition & 12 deletions src/common/models/relations/relations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

});
Expand Down
4 changes: 1 addition & 3 deletions src/common/models/relations/thumb.model.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

}

0 comments on commit 48cb903

Please sign in to comment.