Skip to content

Commit 2046701

Browse files
committed
fix(repository): build relations based on their names
The current implementation uses property names as the key of relations and it's not compatible with legacy juggler. See #1909
1 parent 14d9419 commit 2046701

File tree

8 files changed

+74
-34
lines changed

8 files changed

+74
-34
lines changed

docs/site/BelongsTo-relation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export class OrderRepository extends DefaultCrudRepository<
130130
) {
131131
super(Order, db);
132132
this.customer = this._createBelongsToAccessorFor(
133-
'customerId',
133+
'customer',
134134
customerRepositoryGetter,
135135
);
136136
}

examples/todo-list/src/repositories/todo.repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class TodoRepository extends DefaultCrudRepository<
3030
super(Todo, dataSource);
3131

3232
this.todoList = this._createBelongsToAccessorFor(
33-
'todoListId',
33+
'todoList',
3434
todoListRepositoryGetter,
3535
);
3636
}

packages/repository/src/decorators/model.decorator.ts

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,35 +55,46 @@ export function model(definition?: Partial<ModelDefinitionSyntax>) {
5555
decorator(target);
5656

5757
// Build "ModelDefinition" and store it on model constructor
58-
const modelDef = new ModelDefinition(def);
59-
60-
const propertyMap: PropertyMap =
61-
MetadataInspector.getAllPropertyMetadata(
62-
MODEL_PROPERTIES_KEY,
63-
target.prototype,
64-
) || {};
58+
buildModelDefinition(target, def);
59+
};
60+
}
6561

66-
for (const p in propertyMap) {
67-
const propertyDef = propertyMap[p];
68-
const designType = MetadataInspector.getDesignTypeForProperty(
69-
target.prototype,
70-
p,
71-
);
72-
if (!propertyDef.type) {
73-
propertyDef.type = designType;
74-
}
75-
modelDef.addProperty(p, propertyDef);
62+
/**
63+
* Build model definition from decorations
64+
* @param target Target model class
65+
* @param def Model definition spec
66+
*/
67+
export function buildModelDefinition(
68+
target: Function & {definition?: ModelDefinition | undefined},
69+
def?: ModelDefinitionSyntax,
70+
) {
71+
// Check if the definition for this class has been built
72+
if (!def && target.definition && target.definition.name === target.name) {
73+
return target.definition;
74+
}
75+
const modelDef = new ModelDefinition(def || {name: target.name});
76+
const prototype = target.prototype;
77+
const propertyMap: PropertyMap =
78+
MetadataInspector.getAllPropertyMetadata(MODEL_PROPERTIES_KEY, prototype) ||
79+
{};
80+
for (const p in propertyMap) {
81+
const propertyDef = propertyMap[p];
82+
const designType = MetadataInspector.getDesignTypeForProperty(prototype, p);
83+
if (!propertyDef.type) {
84+
propertyDef.type = designType;
7685
}
77-
78-
target.definition = modelDef;
79-
80-
const relationMap: RelationDefinitionMap =
81-
MetadataInspector.getAllPropertyMetadata(
82-
RELATIONS_KEY,
83-
target.prototype,
84-
) || {};
85-
target.definition.relations = relationMap;
86-
};
86+
modelDef.addProperty(p, propertyDef);
87+
}
88+
target.definition = modelDef;
89+
const relationMeta: RelationDefinitionMap =
90+
MetadataInspector.getAllPropertyMetadata(RELATIONS_KEY, prototype) || {};
91+
const relations: RelationDefinitionMap = {};
92+
// Build an object keyed by relation names
93+
Object.values(relationMeta).forEach(r => {
94+
relations[r.name] = r;
95+
});
96+
target.definition.relations = relations;
97+
return modelDef;
8798
}
8899

89100
/**

packages/repository/src/relations/belongs-to/belongs-to.decorator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ export function belongsTo<T extends Entity>(
3939
// default values, can be customized by the caller
4040
{
4141
keyFrom: decoratedKey,
42+
name: relationName,
4243
},
4344
// properties provided by the caller
4445
definition,
4546
// properties enforced by the decorator
4647
{
4748
type: RelationType.belongsTo,
48-
name: relationName,
4949
source: decoratedTarget.constructor,
5050
target: targetResolver,
5151
},

packages/repository/src/relations/has-many/has-many.decorator.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,12 @@ export function hasMany<T extends Entity>(
2525

2626
const meta: HasManyDefinition = Object.assign(
2727
// default values, can be customized by the caller
28-
{},
28+
{name: key},
2929
// properties provided by the caller
3030
definition,
3131
// properties enforced by the decorator
3232
{
3333
type: RelationType.hasMany,
34-
name: key,
3534
source: decoratedTarget.constructor,
3635
target: targetResolver,
3736
},

packages/repository/src/relations/relation.decorator.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import {PropertyDecoratorFactory} from '@loopback/context';
77
import {Model, RelationDefinitionMap} from '../model';
88
import {RelationType} from './relation.types';
9+
import {buildModelDefinition} from '../decorators';
910

1011
export const RELATIONS_KEY = 'loopback:relations';
1112

@@ -28,7 +29,9 @@ export function relation(definition?: Object) {
2829
export function getModelRelations(
2930
modelCtor: typeof Model,
3031
): RelationDefinitionMap {
31-
return (modelCtor.definition && modelCtor.definition.relations) || {};
32+
// Build model definitions if `@model` is missing
33+
const modelDef = buildModelDefinition(modelCtor);
34+
return (modelDef && modelDef.relations) || {};
3235
}
3336

3437
//

packages/repository/test/fixtures/repositories/order.repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class OrderRepository extends DefaultCrudRepository<
2929
) {
3030
super(Order, db);
3131
this.customer = this._createBelongsToAccessorFor(
32-
'customerId',
32+
'customer',
3333
customerRepositoryGetter,
3434
);
3535
}

packages/repository/test/unit/decorator/relation.decorator.unit.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe('relation decorator', () => {
2929
addressBookId: number;
3030
}
3131

32+
@model()
3233
class AddressBook extends Entity {
3334
id: number;
3435

@@ -57,6 +58,15 @@ describe('relation decorator', () => {
5758
type: Array,
5859
itemType: () => Address,
5960
});
61+
62+
expect(AddressBook.definition.relations).to.eql({
63+
addresses: {
64+
type: RelationType.hasMany,
65+
name: 'addresses',
66+
source: AddressBook,
67+
target: () => Address,
68+
},
69+
});
6070
});
6171

6272
it('takes in both complex property type and hasMany metadata', () => {
@@ -126,6 +136,7 @@ describe('relation decorator', () => {
126136
@property({id: true})
127137
id: number;
128138
}
139+
@model()
129140
class Address extends Entity {
130141
@belongsTo(() => AddressBook)
131142
addressBookId: number;
@@ -139,6 +150,13 @@ describe('relation decorator', () => {
139150
type: Number,
140151
},
141152
});
153+
expect(Address.definition.relations).to.containDeep({
154+
addressBook: {
155+
keyFrom: 'addressBookId',
156+
name: 'addressBook',
157+
type: 'belongsTo',
158+
},
159+
});
142160
});
143161

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

172190
it('accepts explicit keyFrom and keyTo', () => {
191+
@model()
173192
class Address extends Entity {
174193
addressId: number;
175194
street: string;
176195
province: string;
177196
@belongsTo(() => AddressBook, {
178197
keyFrom: 'aForeignKey',
179198
keyTo: 'aPrimaryKey',
199+
name: 'address-book',
180200
})
181201
addressBookId: number;
182202
}
@@ -194,6 +214,13 @@ describe('relation decorator', () => {
194214
keyFrom: 'aForeignKey',
195215
keyTo: 'aPrimaryKey',
196216
});
217+
expect(Address.definition.relations).to.containDeep({
218+
'address-book': {
219+
type: 'belongsTo',
220+
keyFrom: 'aForeignKey',
221+
keyTo: 'aPrimaryKey',
222+
},
223+
});
197224
});
198225
});
199226
});

0 commit comments

Comments
 (0)