Skip to content

Commit

Permalink
fix(repository): build relations based on their names
Browse files Browse the repository at this point in the history
The current implementation uses property names as the key of
relations and it's not compatible with legacy juggler.

See #1909
  • Loading branch information
raymondfeng committed Oct 30, 2018
1 parent 14d9419 commit 2046701
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 34 deletions.
2 changes: 1 addition & 1 deletion docs/site/BelongsTo-relation.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class OrderRepository extends DefaultCrudRepository<
) {
super(Order, db);
this.customer = this._createBelongsToAccessorFor(
'customerId',
'customer',
customerRepositoryGetter,
);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/todo-list/src/repositories/todo.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class TodoRepository extends DefaultCrudRepository<
super(Todo, dataSource);

this.todoList = this._createBelongsToAccessorFor(
'todoListId',
'todoList',
todoListRepositoryGetter,
);
}
Expand Down
65 changes: 38 additions & 27 deletions packages/repository/src/decorators/model.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,35 +55,46 @@ export function model(definition?: Partial<ModelDefinitionSyntax>) {
decorator(target);

// Build "ModelDefinition" and store it on model constructor
const modelDef = new ModelDefinition(def);

const propertyMap: PropertyMap =
MetadataInspector.getAllPropertyMetadata(
MODEL_PROPERTIES_KEY,
target.prototype,
) || {};
buildModelDefinition(target, def);
};
}

for (const p in propertyMap) {
const propertyDef = propertyMap[p];
const designType = MetadataInspector.getDesignTypeForProperty(
target.prototype,
p,
);
if (!propertyDef.type) {
propertyDef.type = designType;
}
modelDef.addProperty(p, propertyDef);
/**
* Build model definition from decorations
* @param target Target model class
* @param def Model definition spec
*/
export function buildModelDefinition(
target: Function & {definition?: ModelDefinition | undefined},
def?: ModelDefinitionSyntax,
) {
// Check if the definition for this class has been built
if (!def && target.definition && target.definition.name === target.name) {
return target.definition;
}
const modelDef = new ModelDefinition(def || {name: target.name});
const prototype = target.prototype;
const propertyMap: PropertyMap =
MetadataInspector.getAllPropertyMetadata(MODEL_PROPERTIES_KEY, prototype) ||
{};
for (const p in propertyMap) {
const propertyDef = propertyMap[p];
const designType = MetadataInspector.getDesignTypeForProperty(prototype, p);
if (!propertyDef.type) {
propertyDef.type = designType;
}

target.definition = modelDef;

const relationMap: RelationDefinitionMap =
MetadataInspector.getAllPropertyMetadata(
RELATIONS_KEY,
target.prototype,
) || {};
target.definition.relations = relationMap;
};
modelDef.addProperty(p, propertyDef);
}
target.definition = modelDef;
const relationMeta: RelationDefinitionMap =
MetadataInspector.getAllPropertyMetadata(RELATIONS_KEY, prototype) || {};
const relations: RelationDefinitionMap = {};
// Build an object keyed by relation names
Object.values(relationMeta).forEach(r => {
relations[r.name] = r;
});
target.definition.relations = relations;
return modelDef;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ export function belongsTo<T extends Entity>(
// default values, can be customized by the caller
{
keyFrom: decoratedKey,
name: relationName,
},
// properties provided by the caller
definition,
// properties enforced by the decorator
{
type: RelationType.belongsTo,
name: relationName,
source: decoratedTarget.constructor,
target: targetResolver,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ export function hasMany<T extends Entity>(

const meta: HasManyDefinition = Object.assign(
// default values, can be customized by the caller
{},
{name: key},
// properties provided by the caller
definition,
// properties enforced by the decorator
{
type: RelationType.hasMany,
name: key,
source: decoratedTarget.constructor,
target: targetResolver,
},
Expand Down
5 changes: 4 additions & 1 deletion packages/repository/src/relations/relation.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {PropertyDecoratorFactory} from '@loopback/context';
import {Model, RelationDefinitionMap} from '../model';
import {RelationType} from './relation.types';
import {buildModelDefinition} from '../decorators';

export const RELATIONS_KEY = 'loopback:relations';

Expand All @@ -28,7 +29,9 @@ export function relation(definition?: Object) {
export function getModelRelations(
modelCtor: typeof Model,
): RelationDefinitionMap {
return (modelCtor.definition && modelCtor.definition.relations) || {};
// Build model definitions if `@model` is missing
const modelDef = buildModelDefinition(modelCtor);
return (modelDef && modelDef.relations) || {};
}

//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class OrderRepository extends DefaultCrudRepository<
) {
super(Order, db);
this.customer = this._createBelongsToAccessorFor(
'customerId',
'customer',
customerRepositoryGetter,
);
}
Expand Down
27 changes: 27 additions & 0 deletions packages/repository/test/unit/decorator/relation.decorator.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('relation decorator', () => {
addressBookId: number;
}

@model()
class AddressBook extends Entity {
id: number;

Expand Down Expand Up @@ -57,6 +58,15 @@ describe('relation decorator', () => {
type: Array,
itemType: () => Address,
});

expect(AddressBook.definition.relations).to.eql({
addresses: {
type: RelationType.hasMany,
name: 'addresses',
source: AddressBook,
target: () => Address,
},
});
});

it('takes in both complex property type and hasMany metadata', () => {
Expand Down Expand Up @@ -126,6 +136,7 @@ describe('relation decorator', () => {
@property({id: true})
id: number;
}
@model()
class Address extends Entity {
@belongsTo(() => AddressBook)
addressBookId: number;
Expand All @@ -139,6 +150,13 @@ describe('relation decorator', () => {
type: Number,
},
});
expect(Address.definition.relations).to.containDeep({
addressBook: {
keyFrom: 'addressBookId',
name: 'addressBook',
type: 'belongsTo',
},
});
});

it('assigns it to target key', () => {
Expand Down Expand Up @@ -170,13 +188,15 @@ describe('relation decorator', () => {
});

it('accepts explicit keyFrom and keyTo', () => {
@model()
class Address extends Entity {
addressId: number;
street: string;
province: string;
@belongsTo(() => AddressBook, {
keyFrom: 'aForeignKey',
keyTo: 'aPrimaryKey',
name: 'address-book',
})
addressBookId: number;
}
Expand All @@ -194,6 +214,13 @@ describe('relation decorator', () => {
keyFrom: 'aForeignKey',
keyTo: 'aPrimaryKey',
});
expect(Address.definition.relations).to.containDeep({
'address-book': {
type: 'belongsTo',
keyFrom: 'aForeignKey',
keyTo: 'aPrimaryKey',
},
});
});
});
});
Expand Down

0 comments on commit 2046701

Please sign in to comment.