Skip to content

Commit

Permalink
fixup! apply feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
biniam committed Jul 10, 2018
1 parent b71af99 commit a78de9a
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 46 deletions.
95 changes: 59 additions & 36 deletions docs/site/HasMany-relation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,31 @@ permalink: /doc/en/lb4/HasMany-relation.html
summary:
---

## Overview

A `hasMany` relation denotes a one-to-many connection of a model to another
model through referential integrity. The referential integrity is enforced by a
foreign key constraint on the target model which usually references a primary
key on the source model. This relation indicates that each instance of the
declaring or source model has zero or more instances of the target model.

## Defining a HasMany Relation

To add a `HasMany` relation to your LoopBack application, you need to perform
the following steps:

1. Add a property to your model to access related model instances.
2. Modify the source model repository class to provide access to a constrained
target model repository
3. Call the constrained target model repository CRUD APIs in your controller
methods

Model relations in LoopBack 4 are defined by having a decorator on a designated
relational property. This section describes how to define a `hasMany` relation
at the model level using the `hasMany` decorator. The relation constrains the
target repository by the foreign key property on its associated model.

The definition of the `hasMany` relation is inferred by using the `hasMany`
decorator. The decorator takes in the target model class constructor and
optionally a custom foreign key to store the relation metadata. The decorator
logic also designates the relation type and tries to infer the foreign key on
the target model (or `keyTo` in the relation metadata) default value (source
model name appended with `id` in camel case, also used in LoopBack 3). It also
calls `property.array()` to ensure that the type of the property is inferred
properly as an array of the target model instances.

The decorated property name is used as the relation name and stored as part of
the source model definition's relation metadata. The following example shows how
to define a `hasMany` relation on a source model `Customer`.
target repository by the foreign key property on its associated model. The
following example shows how to define a `hasMany` relation on a source model
`Customer`.

{% include code-caption.html content="/src/models/customer.model.ts" %}

Expand All @@ -43,29 +49,40 @@ export class Customer extends Entity {

@property({
type: 'string',
required: true,
})
name: string;

@hasMany(Order) orders: Order[];
}
```

The metadata generated for the above decoration is as follows:
@hasMany(Order) orders?: Order[];

```js
{
type: 2; // denotes hasMany relation (this is an enum value)
keyTo: customerId; // Order model should have this property declared
constructor(data: Partial<Customer>) {
super(data);
}
}
```

The property type metadata is also preserved as an array of type `Order` as part
of the decoration. Another usage of the decorator with a custom foreign key name
for the above example is as follows:
The definition of the `hasMany` relation is inferred by using the `hasMany`
decorator. The decorator takes in the target model class constructor and
optionally a custom foreign key to store the relation metadata. The decorator
logic also designates the relation type and tries to infer the foreign key on
the target model (or `keyTo` in the relation metadata) default value (source
model name appended with `id` in camel case, also used in LoopBack 3). It also
calls `property.array()` to ensure that the type of the property is inferred
properly as an array of the target model instances.

The decorated property name is used as the relation name and stored as part of
the source model definition's relation metadata. The property type metadata is
also preserved as an array of type `Order` as part of the decoration. Another
usage of the decorator with a custom foreign key name for the above example is
as follows:

```ts
// inside Customer model class
@hasMany(Order, {keyTo: 'custId'}) orders: Order[];
// import statements
class Customer extends Entity {
// constructor, properties, etc.
@hasMany(Order, {keyTo: 'custId'})
orders?: Order[];
}
```

## Configuring a HasMany relation
Expand All @@ -78,7 +95,7 @@ repository, the following are required:
- Use [Dependency Injection](Dependency-injection.md) to inject an instance of
the target repository in the constructor of your source repository class.
- Declare a property with the factory function type
`HasManyRepositoryFactory<TargetModel, typeof TargetModel.prototype.id>` on
`HasManyRepositoryFactory<targetModel, typeof sourceModel.prototype.id>` on
the source repository class.
- call the `_createHasManyRepositoryFactoryFor` function in the constructor of
the source repository class with the relation name (decorated relation propert
Expand Down Expand Up @@ -120,10 +137,14 @@ class CustomerRepository extends DefaultCrudRepository<
The following CRUD APIs are now available in the constrained target repository
factory `orders` for instances of `customerRepository`:

- `create` for creating a target model instance
- `find` finding target model instance(s)
- `delete` for deleting target model instance(s)
- `patch` for patching target model instance(s)
- `create` for creating a target model instance belonging to customer model
instance ([API Docs](TBD))
- `find` finding target model instance(s) belonging to customer model instance
([API Docs](TBD))
- `delete` for deleting target model instance(s) belonging to customer model
instance ([API Docs](TBD))
- `patch` for patching target model instance(s) belonging to customer model
instance ([API Docs](TBD))

## Using HasMany constrained repository in a controller

Expand All @@ -142,7 +163,7 @@ content="src/controllers/customer-orders.controller.ts" %}
```ts
import {post, param, requestBody} from '@loopback/rest';
import {customerRepository} from '../repositories/customer.repository.ts';
import {Order} from '../models/order.model.ts';
import {Customer, Order} from '../models/';

export class CustomerOrdersController {
constructor(
Expand All @@ -152,10 +173,12 @@ export class CustomerOrdersController {

@post('/customers/{id}/order')
async createCustomerOrders(
@param.path.number('id') customerId: number,
@requestBody() orderData: Order, // FIXME(b-admike): should be Partial<Order> once https://github.com/strongloop/loopback-next/issues/1443 is fixed
@param.path.number('id') customerId: typeof Customer.prototype.id,
@requestBody() orderData: Order,
): Promise<Order> {
return await this.customerRepository.orders(customerId).create(orderData);
}
}
```

<!--- TODO: Add details about `orderData` variable typing issue --->
4 changes: 2 additions & 2 deletions packages/repository/src/repositories/relation.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
DefaultHasManyEntityCrudRepository,
} from './relation.repository';

export type HasManyRepositoryFactory<Target extends Entity, SourceID> = (
export type HasManyRepositoryFactory<SourceID, Target extends Entity> = (
fkValue: SourceID,
) => HasManyRepository<Target>;

Expand All @@ -35,7 +35,7 @@ export function createHasManyRepositoryFactory<
>(
relationMetadata: HasManyDefinition,
targetRepository: EntityCrudRepository<Target, TargetID>,
): HasManyRepositoryFactory<Target, SourceID> {
): HasManyRepositoryFactory<SourceID, Target> {
return function(fkValue: SourceID) {
const fkName = relationMetadata.keyTo;
if (!fkName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ describe('HasMany relation', () => {
typeof Customer.prototype.id
> {
public orders: HasManyRepositoryFactory<
Order,
typeof Customer.prototype.id
typeof Customer.prototype.id,
Order
>;

constructor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ describe('HasMany relation', () => {
typeof Customer.prototype.id
> {
public orders: HasManyRepositoryFactory<
Order,
typeof Customer.prototype.id
typeof Customer.prototype.id,
Order
>;
constructor(
@inject('datasources.db') protected db: juggler.DataSource,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ describe('HasMany relation', () => {
let reviewRepo: EntityCrudRepository<Review, typeof Review.prototype.id>;
let customerOrderRepo: HasManyRepository<Order>;
let customerAuthoredReviewFactoryFn: HasManyRepositoryFactory<
Review,
typeof Customer.prototype.id
typeof Customer.prototype.id,
Review
>;
let customerApprovedReviewFactoryFn: HasManyRepositoryFactory<
Review,
typeof Customer.prototype.id
typeof Customer.prototype.id,
Review
>;
let existingCustomerId: number;

Expand Down

0 comments on commit a78de9a

Please sign in to comment.