Skip to content

Commit

Permalink
feat(repository): allow custom keyFrom for hasmany/hasone
Browse files Browse the repository at this point in the history
  • Loading branch information
Agnes Lin authored and agnes512 committed Dec 30, 2019
1 parent cbdba15 commit 58efff9
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 64 deletions.
4 changes: 3 additions & 1 deletion docs/site/BelongsTo-relation.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ export interface OrderRelations {
}
```

{% include important.html content="LB4 doesn't support composite keys for now. e.g joining two tables with more than one source key. Related GitHub issue: [Composite primary/foreign keys](https://github.com/strongloop/loopback-next/issues/1830)" %}

## Configuring a belongsTo relation

The configuration and resolution of a `belongsTo` relation takes place at the
Expand Down Expand Up @@ -461,5 +463,5 @@ custom scope once you have the inclusion resolver of each relation set up.
Check[HasMany - Query multiple relations](HasMany-relation.md#query-multiple-relations)
for the usage and examples.

{% include important.html content="There are some limitations of inclusion:. <br/><br/> We don’t support recursive inclusion of related models. Related GH issue: [Recursive inclusion of related models](https://github.com/strongloop/loopback-next/issues/3454). <br/><br/> It doesn’t split numbers of queries. Related GH issue: [Support inq splitting](https://github.com/strongloop/loopback-next/issues/3444). <br/><br/> It might not work well with ObjectId of MongoDB. Related GH issue: [Spike: robust handling of ObjectID type for MongoDB](https://github.com/strongloop/loopback-next/issues/3456).
{% include important.html content="There are some limitations of inclusion:. <br/>We don’t support recursive inclusion of related models. Related GH issue: [Recursive inclusion of related models](https://github.com/strongloop/loopback-next/issues/3454). <br/>It doesn’t split numbers of queries. Related GH issue: [Support inq splitting](https://github.com/strongloop/loopback-next/issues/3444). <br/>It might not work well with ObjectId of MongoDB. Related GH issue: [Spike: robust handling of ObjectID type for MongoDB](https://github.com/strongloop/loopback-next/issues/3456).
" %}
58 changes: 53 additions & 5 deletions docs/site/HasMany-relation.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,10 @@ export class Order extends Entity {
}
```

Notice that if you decorate the corresponding foreign key of the target model
with `@belongsTo`, you also need to specify the `belongsTo` relation name in the
`name` field of its relation metadata. See [BelongsTo](BelongsTo-relation.md)
for more details.
Notice that if you decorate the corresponding customized foreign key of the
target model with `@belongsTo`, you also need to specify the `belongsTo`
relation name in the `name` field of its relation metadata. See
[BelongsTo](BelongsTo-relation.md) for more details.

```ts
// import statements
Expand All @@ -265,6 +265,54 @@ export class Order extends Entity {
}
```

If you need to use another attribute other than the id property to be the source
key, customizing `keyFrom` field would allow you to do so:

```ts
export class Customer extends Entity {
@property({
type: 'number',
id: true,
})
id: number;

// if you'd like to use this property as the source id
// of a certain relation that relates to a model `Foo`
@property({
type: 'number',
})
authorId: number; // not primary key

@hasMany(() => Review, {keyFrom: 'authorId'})
reviews?: Review[];

@hasMany(() => Order)
orders?: Order[];

// ..constructor
}
}
```

Notice that if you decorate the corresponding foreign key of the target model
with `@belongsTo`, you also need to specify the `keyTo` field of its relation
metadata. See [BelongsTo](BelongsTo-relation.md#relation-metadata) for more
details.

```ts
// import statements
@model()
export class Review extends Entity {
// constructor, properties, etc.

// specify the keyTo if the source key is not the id property
@belongsTo(() => Customer, {keyTo: 'authorId'})
customerId: number; // default foreign key name
}
```

{% include important.html content="LB4 doesn't support composite keys for now. e.g joining two tables with more than one source key. Related GitHub issue: [Composite primary/foreign keys](https://github.com/strongloop/loopback-next/issues/1830)" %}

If you need to use _different names for models and database columns_, to use
`my_orders` as db column name other than `orders` for example, the following
setting would allow you to do so:
Expand Down Expand Up @@ -664,5 +712,5 @@ The `Where` clause above filters the result of `orders`.
{% include tip.html content="Make sure that you have all inclusion resolvers that you need REGISTERED, and
all relation names should be UNIQUE."%}

{% include important.html content="There are some limitations of inclusion:. <br/><br/> We don’t support recursive inclusion of related models. Related GH issue: [Recursive inclusion of related models](https://github.com/strongloop/loopback-next/issues/3454). <br/><br/> It doesn’t split numbers of queries. Related GH issue: [Support inq splitting](https://github.com/strongloop/loopback-next/issues/3444). <br/><br/> It might not work well with ObjectId of MongoDB. Related GH issue: [Spike: robust handling of ObjectID type for MongoDB](https://github.com/strongloop/loopback-next/issues/3456).
{% include important.html content="There are some limitations of inclusion:. <br/>We don’t support recursive inclusion of related models. Related GH issue: [Recursive inclusion of related models](https://github.com/strongloop/loopback-next/issues/3454). <br/>It doesn’t split numbers of queries. Related GH issue: [Support inq splitting](https://github.com/strongloop/loopback-next/issues/3444). <br/>It might not work well with ObjectId of MongoDB. Related GH issue: [Spike: robust handling of ObjectID type for MongoDB](https://github.com/strongloop/loopback-next/issues/3456).
" %}
58 changes: 53 additions & 5 deletions docs/site/hasOne-relation.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,10 @@ export class Supplier extends Entity {
}
```

Notice that if you decorate the corresponding foreign key of the target model
with `@belongsTo`, you also need to specify the `belongTo` relation name in the
`name` field of its relation metadata. See [BelongsTo](BelongsTo-relation.md)
for more details.
Notice that if you decorate the corresponding customized foreign key of the
target model with `@belongsTo`, you also need to specify the `belongTo` relation
name in the `name` field of its relation metadata. See
[BelongsTo](BelongsTo-relation.md) for more details.

```ts
// import statements
Expand All @@ -201,6 +201,54 @@ export class Account extends Entity {
}
```

If you need to use another attribute other than the id property to be the source
key, customizing `keyFrom` field would allow you to do so:

```ts
export class Supplier extends Entity {
@property({
type: 'number',
id: true,
})
id: number;

// if you'd like to use this property as the source id
// of a certain relation that relates to a model `Foo`
@property({
type: 'number',
})
supplier_id: number; // not primary key

@hasOne(() => Account)
account?: Account;

@hasOne(() => Manufacturer, {keyFrom: 'supplier_id'})
manufacturer ?: Manufacturer;

// ..constructor
}
}
```

Notice that if you decorate the corresponding foreign key of the target model
with `@belongsTo`, you also need to specify the `keyTo` field of its relation
metadata. See [BelongsTo](BelongsTo-relation.md#relation-metadata) for more
details.

```ts
// import statements
@model()
export class Manufacturer extends Entity {
// constructor, properties, etc.

// specify the keyTo if the source key is not the id property
@belongsTo(() => Supplier, {keyTo: 'supplier_id'})
supplierId: number; // default foreign key name
}
```

{% include important.html content="LB4 doesn't support composite keys for now. e.g joining two tables with more than one source key. Related GitHub issue: [Composite primary/foreign keys](https://github.com/strongloop/loopback-next/issues/1830)" %}

If you need to use _different names for models and database columns_, to use
`suppAccount` as db column name instead of `account` for example, the following
setting would allow you to do so:
Expand Down Expand Up @@ -482,5 +530,5 @@ custom scope once you have the inclusion resolver of each relation set up.
Check[HasMany - Query multiple relations](HasMany-relation.md#query-multiple-relations)
for the usage and examples.

{% include important.html content="There are some limitations of inclusion:. <br/><br/> We don’t support recursive inclusion of related models. Related GH issue: [Recursive inclusion of related models](https://github.com/strongloop/loopback-next/issues/3454). <br/><br/> It doesn’t split numbers of queries. Related GH issue: [Support inq splitting](https://github.com/strongloop/loopback-next/issues/3444). <br/><br/> It might not work well with ObjectId of MongoDB. Related GH issue: [Spike: robust handling of ObjectID type for MongoDB](https://github.com/strongloop/loopback-next/issues/3456).
{% include important.html content="There are some limitations of inclusion:. <br/>We don’t support recursive inclusion of related models. Related GH issue: [Recursive inclusion of related models](https://github.com/strongloop/loopback-next/issues/3454). <br/>It doesn’t split numbers of queries. Related GH issue: [Support inq splitting](https://github.com/strongloop/loopback-next/issues/3444). <br/>It might not work well with ObjectId of MongoDB. Related GH issue: [Spike: robust handling of ObjectID type for MongoDB](https://github.com/strongloop/loopback-next/issues/3456).
" %}
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ export function belongsToInclusionResolverAcceptance(
const expected = {
...order,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
customer: {
...thor,
parentId: features.emptyValue,
Expand Down Expand Up @@ -115,8 +114,7 @@ export function belongsToInclusionResolverAcceptance(
{
...thorOrder,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
customer: {
...thor,
parentId: features.emptyValue,
Expand All @@ -125,8 +123,7 @@ export function belongsToInclusionResolverAcceptance(
{
...odinOrder,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
customer: {
...odin,
parentId: features.emptyValue,
Expand Down Expand Up @@ -154,8 +151,7 @@ export function belongsToInclusionResolverAcceptance(
const expected = {
...odinOrder,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
customer: {
...odin,
parentId: features.emptyValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,11 @@ export function belongsToRelationAcceptance(
it('can find shipment of order with a custom foreign key name', async () => {
const shipment = await shipmentRepo.create({
name: 'Tuesday morning shipment',
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: 999,
});
const order = await orderRepo.create({
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: shipment.id,
shipmentInfo: shipment.shipment_id,
description: 'Order that is shipped Tuesday morning',
});
const result = await orderRepo.shipment(order.id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
CustomerRepository,
Order,
OrderRepository,
ShipmentRepository,
} from '../fixtures/models';
import {givenBoundCrudRepositories} from '../helpers';

Expand All @@ -38,12 +39,13 @@ export function hasManyInclusionResolverAcceptance(
before(deleteAllModelsInDefaultDataSource);
let customerRepo: CustomerRepository;
let orderRepo: OrderRepository;
let shipmentRepo: ShipmentRepository;

before(
withCrudCtx(async function setupRepository(ctx: CrudTestContext) {
// this helper should create the inclusion resolvers and also
// register inclusion resolvers for us
({customerRepo, orderRepo} = givenBoundCrudRepositories(
({customerRepo, orderRepo, shipmentRepo} = givenBoundCrudRepositories(
ctx.dataSource,
repositoryClass,
features,
Expand All @@ -57,6 +59,7 @@ export function hasManyInclusionResolverAcceptance(
beforeEach(async () => {
await customerRepo.deleteAll();
await orderRepo.deleteAll();
await shipmentRepo.deleteAll();
});

it('throws an error if tries to query nonexists relation names', async () => {
Expand Down Expand Up @@ -90,8 +93,7 @@ export function hasManyInclusionResolverAcceptance(
{
...thorOrder,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
},
],
}),
Expand Down Expand Up @@ -125,14 +127,12 @@ export function hasManyInclusionResolverAcceptance(
{
...thorOrderMjolnir,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
},
{
...thorOrderPizza,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
},
],
parentId: features.emptyValue,
Expand All @@ -144,8 +144,7 @@ export function hasManyInclusionResolverAcceptance(
{
...odinOrderCoffee,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
},
],
},
Expand Down Expand Up @@ -179,14 +178,45 @@ export function hasManyInclusionResolverAcceptance(
{
...odinOrder,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
},
],
};
expect(toJSON(result)).to.deepEqual(toJSON(expected));
});

it('returns related models with non-id property as a source key(keyFrom)', async () => {
const shipment = await shipmentRepo.create({
name: 'non-id prop as keyFrom relation',
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: 999,
});
const order = await orderRepo.create({
// foreign key is a non-id property
shipmentInfo: shipment.shipment_id,
description: 'foreign key not id property',
});

const found = await shipmentRepo.find({
include: [{relation: 'shipmentOrders'}],
});

expect(toJSON(found)).containDeep(
toJSON([
{
...shipment,
shipmentOrders: [
{
...order,
isShipped: features.emptyValue,
customerId: features.emptyValue,
},
],
},
]),
);
});

skipIf(
features.hasRevisionToken,
it,
Expand Down Expand Up @@ -219,8 +249,7 @@ export function hasManyInclusionResolverAcceptance(
{
...odinPizza,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
},
],
};
Expand Down Expand Up @@ -268,8 +297,7 @@ export function hasManyInclusionResolverAcceptance(
{
...odinPizza,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
},
],
},
Expand Down Expand Up @@ -309,8 +337,7 @@ export function hasManyInclusionResolverAcceptance(
{
...odinPizza,
isShipped: features.emptyValue,
// eslint-disable-next-line @typescript-eslint/camelcase
shipment_id: features.emptyValue,
shipmentInfo: features.emptyValue,
},
],
},
Expand Down
Loading

0 comments on commit 58efff9

Please sign in to comment.