Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions docs/site/HasMany-relation.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class Customer extends Entity {
})
name: string;

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

constructor(data: Partial<Customer>) {
Expand Down Expand Up @@ -85,7 +85,7 @@ as follows:
// import statements
class Customer extends Entity {
// constructor, properties, etc.
@hasMany(Order, {keyTo: 'custId'})
@hasMany(() => Order, {keyTo: 'custId'})
orders?: Order[];
}
```
Expand All @@ -99,44 +99,43 @@ 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.
<!-- TODO: EXPLAIN WHY WE NEED TO INJECT A GETTER HERE -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A TODO comment to address?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A TODO comment to address?

☝️

- Declare a property with the factory function type
`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
property on the source model) and target repository instance and assign it the
property mentioned above.
`HasManyAccessor<targetModel, typeof sourceModel.prototype.id>` on the source
repository class.
- call the `_createHasManyAccessorFor` function in the constructor of the source
repository class with the relation name (decorated relation property on the
source model) and target repository instance and assign it the property
mentioned above.

The following code snippet shows how it would look like:

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

```ts
import {Order, Customer} from '../models';
import {OrderRepository} from './order.repository.ts';
import {
DefaultCrudRepository,
juggler,
HasManyRepositoryFactory,
HasManyAccessor,
repository,
} from '@loopback/repository';
import {inject} from '@loopback/core';
import {inject, Getter} from '@loopback/core';

class CustomerRepository extends DefaultCrudRepository<
Customer,
typeof Customer.prototype.id
> {
public orders: HasManyRepositoryFactory<Order, typeof Customer.prototype.id>;
public orders: HasManyAccessor<Order, typeof Customer.prototype.id>;
constructor(
@inject('datasources.db') protected db: juggler.DataSource,
@repository(OrderRepository) orderRepository: OrderRepository,
@repository.getter('repositories.OrderRepository')
getOrderRepository: Getter<OrderRepository>,
) {
super(Customer, db);
this.orders = this._createHasManyRepositoryFactoryFor(
'orders',
orderRepository,
);
this.orders = this._createHasManyAccessorFor_('orders', getOrderRepository);
}
}
```
Expand Down
2 changes: 1 addition & 1 deletion docs/site/todo-list-tutorial-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ model. To `TodoList` model, add in the following property:
export class TodoList extends Entity {
// ...properties defined by the CLI...

@hasMany(Todo)
@hasMany(() => Todo, {keyTo: 'todoListId'})
todos?: Todo[];

// ...constructor def...
Expand Down
20 changes: 9 additions & 11 deletions docs/site/todo-list-tutorial-repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,38 +35,36 @@ we'll need to make two more additions:
- inject `TodoRepository` instance

Once the property type for `todos` have been defined, use
`this._createHasManyRepositoryFactoryFor` to assign it a repository contraining
factory function. Pass in the name of the relationship (`todos`) and the Todo
repository instance to constrain as the arguments for the function.
`this._createHasManyAccessorFor` to assign it a repository constraining factory
function. Pass in the name of the relationship (`todos`) and the Todo repository
instance to constrain as the arguments for the function.

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

```ts
import {
DefaultCrudRepository,
juggler,
HasManyRepositoryFactory,
HasManyAccessor,
repository,
} from '@loopback/repository';
import {TodoList, Todo} from '../models';
import {inject} from '@loopback/core';
import {inject, Getter} from '@loopback/core';
import {TodoRepository} from './todo.repository';

export class TodoListRepository extends DefaultCrudRepository<
TodoList,
typeof TodoList.prototype.id
> {
public todos: HasManyRepositoryFactory<Todo, typeof TodoList.prototype.id>;
public todos: HasManyAccessor<Todo, typeof TodoList.prototype.id>;

constructor(
@inject('datasources.db') protected datasource: juggler.DataSource,
@repository(TodoRepository) protected todoRepository: TodoRepository,
@repository.getter('repositories.TodoRepository')
protected getTodoRepository: Getter<TodoRepository>,
) {
super(TodoList, datasource);
this.todos = this._createHasManyRepositoryFactoryFor(
'todos',
todoRepository,
);
this.todos = this._createHasManyAccessorFor('todos', getTodoRepository);
}
}
```
Expand Down
2 changes: 1 addition & 1 deletion examples/todo-list/src/models/todo-list.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class TodoList extends Entity {
})
color?: string;

@hasMany(Todo)
@hasMany(() => Todo, {keyTo: 'todoListId'})
todos: Todo[];

constructor(data?: Partial<TodoList>) {
Expand Down
14 changes: 6 additions & 8 deletions examples/todo-list/src/repositories/todo-list.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,25 @@
import {
DefaultCrudRepository,
juggler,
HasManyRepositoryFactory,
HasManyAccessor,
repository,
} from '@loopback/repository';
import {TodoList, Todo} from '../models';
import {inject} from '@loopback/core';
import {inject, Getter} from '@loopback/core';
import {TodoRepository} from './todo.repository';

export class TodoListRepository extends DefaultCrudRepository<
TodoList,
typeof TodoList.prototype.id
> {
public todos: HasManyRepositoryFactory<Todo, typeof TodoList.prototype.id>;
public todos: HasManyAccessor<Todo, typeof TodoList.prototype.id>;

constructor(
@inject('datasources.db') protected datasource: juggler.DataSource,
@repository(TodoRepository) protected todoRepository: TodoRepository,
@repository.getter('repositories.TodoRepository')
protected getTodoRepository: Getter<TodoRepository>,
) {
super(TodoList, datasource);
this.todos = this._createHasManyRepositoryFactoryFor(
'todos',
todoRepository,
);
this.todos = this._createHasManyAccessorFor('todos', getTodoRepository);
}
}
5 changes: 4 additions & 1 deletion packages/repository-json-schema/src/build-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ModelMetadataHelper,
PropertyDefinition,
ModelDefinition,
resolveType,
} from '@loopback/repository';
import {MetadataInspector} from '@loopback/context';
import {
Expand Down Expand Up @@ -124,6 +125,7 @@ export function metaToJsonProperty(meta: PropertyDefinition): JSONSchema {
propertyType = stringTypeToWrapper(propertyType);

if (isComplexType(propertyType)) {
propertyType = resolveType(propertyType);
Object.assign(propDef, {$ref: `#/definitions/${propertyType.name}`});
} else {
Object.assign(propDef, {
Expand Down Expand Up @@ -171,12 +173,13 @@ export function modelToJsonSchema(ctor: Function): JSONSchema {
result.properties[p] = metaToJsonProperty(metaProperty);

// populating JSON Schema 'definitions'
const referenceType = isArrayType(metaProperty.type as string | Function)
let referenceType = isArrayType(metaProperty.type as string | Function)
? // shimks: ugly type casting; this should be replaced by logic to throw
// error if itemType/type is not a string or a function
(metaProperty.itemType as string | Function)
: (metaProperty.type as string | Function);
if (typeof referenceType === 'function' && isComplexType(referenceType)) {
referenceType = resolveType(referenceType);
const propSchema = getJsonSchema(referenceType);

if (propSchema && Object.keys(propSchema).length > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {model, property} from '@loopback/repository';
import {
model,
property,
belongsTo,
hasMany,
Entity,
} from '@loopback/repository';
import {
modelToJsonSchema,
JSON_SCHEMA_KEY,
Expand Down Expand Up @@ -370,6 +376,84 @@ describe('build-schema', () => {
expectValidJsonSchema(jsonSchema);
});

it('properly converts decorated custom array type with a resolver', () => {
@model()
class CustomType {
@property()
prop: string;
}

@model()
class TestModel {
@property.array(() => CustomType)
cusType: CustomType[];
}

const jsonSchema = modelToJsonSchema(TestModel);
expect(jsonSchema.properties).to.deepEqual({
cusType: {
type: 'array',
items: {$ref: '#/definitions/CustomType'},
},
});
expect(jsonSchema.definitions).to.deepEqual({
CustomType: {
title: 'CustomType',
properties: {
prop: {
type: 'string',
},
},
},
});
expectValidJsonSchema(jsonSchema);
});

it('properly converts decorated models with hasMany and belongsTo', () => {
@model()
class Order extends Entity {
@property({id: true})
id: number;
@belongsTo(() => Customer)
customerId: number;
}

@model()
class Customer extends Entity {
@property({id: true})
id: number;
@hasMany(() => Order)
orders: Order[];
}

const orderSchema = modelToJsonSchema(Order);
const customerSchema = modelToJsonSchema(Customer);
expect(orderSchema.properties).to.deepEqual({
id: {type: 'number'},
customerId: {type: 'number'},
});
expect(customerSchema.properties).to.deepEqual({
id: {type: 'number'},
orders: {
type: 'array',
items: {$ref: '#/definitions/Order'},
},
});
expect(customerSchema.definitions).to.deepEqual({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add checks for orderSchema.definitions and calling expectValidJsonSchema on customerSchema for this test?

Order: {
title: 'Order',
properties: {
id: {
type: 'number',
},
customerId: {type: 'number'},
},
},
});

expectValidJsonSchema(orderSchema);
});

it('creates definitions only at the root level of the schema', () => {
@model()
class CustomTypeFoo {
Expand Down
20 changes: 17 additions & 3 deletions packages/repository-json-schema/test/unit/build-schema.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {expect} from '@loopback/testlab';
import {isComplexType, stringTypeToWrapper, metaToJsonProperty} from '../..';

describe('build-schema', () => {
class CustomType {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only used by 2 tests, any reason we shouldn't just declare this within the test it blocks?

Copy link
Contributor Author

@shimks shimks Aug 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really. I can revert the change Nvm, apparently there are other tests that require CustomType


describe('stringTypeToWrapper', () => {
context('when given primitive types in string', () => {
it('returns String for "string"', () => {
Expand Down Expand Up @@ -76,7 +78,6 @@ describe('build-schema', () => {
});

it('returns true if any other wrappers are passed in', () => {
class CustomType {}
expect(isComplexType(CustomType)).to.eql(true);
});
});
Expand Down Expand Up @@ -107,12 +108,17 @@ describe('build-schema', () => {
});

it('converts complex types', () => {
class CustomType {}
expect(metaToJsonProperty({type: CustomType})).to.eql({
$ref: '#/definitions/CustomType',
});
});

it('converts complex types with resolver', () => {
expect(metaToJsonProperty({type: () => CustomType})).to.eql({
$ref: '#/definitions/CustomType',
});
});

it('converts primitive arrays', () => {
expect(metaToJsonProperty({type: Array, itemType: Number})).to.eql({
type: 'array',
Expand All @@ -121,11 +127,19 @@ describe('build-schema', () => {
});

it('converts arrays of custom types', () => {
class CustomType {}
expect(metaToJsonProperty({type: Array, itemType: CustomType})).to.eql({
type: 'array',
items: {$ref: '#/definitions/CustomType'},
});
});

it('converts array types with resolver', () => {
expect(
metaToJsonProperty({type: Array, itemType: () => CustomType}),
).to.eql({
type: 'array',
items: {$ref: '#/definitions/CustomType'},
});
});
});
});
2 changes: 1 addition & 1 deletion packages/repository/examples/models/order.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ class Order extends Entity {
id: string;
customerId: string;

@belongsTo()
@belongsTo(() => Customer)
customer: Customer;
}
Loading