Skip to content

Commit

Permalink
feat(example-todo-list): use real relation resolvers
Browse files Browse the repository at this point in the history
hasMany, belongsTo, and hasOne factories have an inclusion resolver property that we can leverage in this example
  • Loading branch information
nabdelgadir committed Sep 25, 2019
1 parent 8dfdf58 commit 0d41f5e
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 133 deletions.
82 changes: 17 additions & 65 deletions docs/site/tutorials/todo-list/todo-list-tutorial-repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,52 +87,22 @@ export class TodoListRepository extends DefaultCrudRepository<
### Inclusion of Related Models
To get the related `Todo` objects for each `TodoList`, we can use register a
custom
[`InclusionResolver`](https://loopback.io/doc/en/lb4/apidocs.repository.inclusionresolver.html)
in the `TodoList` repository.
First, add the following import:
```ts
import {InclusionResolver} from '@loopback/repository';
```
Next, in the constructor, add the following custom resolver:
To get the related `Todo` objects for each `TodoList`, we can register the
inclusion resolver that comes with the
[`HasManyRepositoryFactory`](https://loopback.io/doc/en/lb4/apidocs.repository.hasmanyrepository.html).
We need to register this resolver to the repository class, which we can do as
follows:
```ts
export class TodoListRepository extends DefaultCrudRepository</*..*/> {
// ...
constructor(
//db, relation factories setup
// add the following code to build a custom resolver
const todosResolver: InclusionResolver<TodoList, Todo> = async todoLists => {
const todos: Todo[][] = [];
for (const todoList of todoLists) {
const todo = await this.todos(todoList.id).find();
todos.push(todo);
}
return todos;
};
// the resolver needs to be registered before using
this.registerInclusionResolver('todos', todosResolver);
)
}
```
this.todos = this.createHasManyRepositoryFactoryFor(
'todos',
todoRepositoryGetter,
);
After that, we need to register this resolver to the repository class, which we
can do as follows:
```ts
this.registerInclusionResolver('todos', todosResolver);
// Add this line
this.registerInclusionResolver('todos', this.todos.inclusionResolver);
```
{% include note.html content="
This is a temporary implementation until we implement our relation resolvers. See [GitHub issue #3450](https://github.com/strongloop/loopback-next/issues/3450) for details.
" %}
Now when you get a `TodoList`, a `todos` property will be included that contains
your related `Todo`s, for example:
Expand All @@ -156,31 +126,13 @@ Let's do the same on the `TodoRepository`:
{% include code-caption.html content="src/repositories/todo.repository.ts" %}
```ts
// .. other imports
import {InclusionResolver} from '@loopback/repository';
```
```ts
export class TodoRepository extends DefaultCrudRepository</*..*/> {
// ...
constructor(
//db, factories setup
this.todoList = this.createBelongsToAccessorFor(
'todoList',
todoListRepositoryGetter,
);

// add the following code to build/register a custom resolver
const todoListResolver: InclusionResolver<Todo, TodoList> = async todos => {
const todoLists = [];

for (const todo of todos) {
const todoList = await this.todoList(todo.id);
todoLists.push(todoList);
}

return todoLists;
};

this.registerInclusionResolver('todoList', todoListResolver);
)
}
// Add this line
this.registerInclusionResolver('todoList', this.todoList.inclusionResolver);
```
We're now ready to expose `TodoList` and its related `Todo` API through the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,20 @@ describe('TodoListImageRepository', () => {
todoList: toJSON(list),
});
});

it('includes TodoList in findOne result', async () => {
const list = await givenTodoListInstance(todoListRepo, {});
const image = await givenTodoListImageInstance(todoListImageRepo, {
todoListId: list.id,
});

const response = await todoListImageRepo.findOne({
include: [{relation: 'todoList'}],
});

expect(toJSON(response)).to.deepEqual({
...toJSON(image),
todoList: toJSON(list),
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {
givenEmptyDatabase,
givenTodoInstance,
givenTodoListImageInstance,
givenTodoListInstance,
testdb,
} from '../helpers';
Expand All @@ -23,6 +24,10 @@ describe('TodoListRepository', () => {
async () => todoListImageRepo,
);
todoRepo = new TodoRepository(testdb, async () => todoListRepo);
todoListImageRepo = new TodoListImageRepository(
testdb,
async () => todoListRepo,
);
});

beforeEach(givenEmptyDatabase);
Expand Down Expand Up @@ -56,4 +61,89 @@ describe('TodoListRepository', () => {
todos: [toJSON(todo)],
});
});

it('includes Todos in findOne method result', async () => {
const list = await givenTodoListInstance(todoListRepo);
const todo = await givenTodoInstance(todoRepo, {todoListId: list.id});

const response = await todoListRepo.findOne({
where: {id: list.id},
include: [{relation: 'todos'}],
});

expect(toJSON(response)).to.deepEqual({
...toJSON(list),
todos: [toJSON(todo)],
});
});

it('includes TodoListImage in find method result', async () => {
const list = await givenTodoListInstance(todoListRepo);
const image = await givenTodoListImageInstance(todoListImageRepo, {
todoListId: list.id,
});

const response = await todoListRepo.find({
include: [{relation: 'image'}],
});

expect(toJSON(response)).to.deepEqual([
{
...toJSON(list),
image: toJSON(image),
},
]);
});

it('includes TodoListImage in findById method result', async () => {
const list = await givenTodoListInstance(todoListRepo);
const image = await givenTodoListImageInstance(todoListImageRepo, {
todoListId: list.id,
});

const response = await todoListRepo.findById(list.id, {
include: [{relation: 'image'}],
});

expect(toJSON(response)).to.deepEqual({
...toJSON(list),
image: toJSON(image),
});
});

it('includes TodoListImage in findOne method result', async () => {
const list = await givenTodoListInstance(todoListRepo);
const image = await givenTodoListImageInstance(todoListImageRepo, {
todoListId: list.id,
});

const response = await todoListRepo.findOne({
include: [{relation: 'image'}],
});

expect(toJSON(response)).to.deepEqual({
...toJSON(list),
image: toJSON(image),
});
});

it('includes both Todos and TodoListImage in find method result', async () => {
const list = await givenTodoListInstance(todoListRepo);
const todo = await givenTodoInstance(todoRepo, {todoListId: list.id});
const image = await givenTodoListImageInstance(todoListImageRepo, {
todoListId: list.id,
});

const response = await todoListRepo.find({
include: [{relation: 'image'}, {relation: 'todos'}],
});

expect(toJSON(response)).to.deepEqual([
{
...toJSON(list),
image: toJSON(image),
todos: [toJSON(todo)],
},
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,18 @@ describe('TodoRepository', () => {
todoList: toJSON(list),
});
});

it('includes TodoList in findOne method result', async () => {
const list = await givenTodoListInstance(todoListRepo);
const todo = await givenTodoInstance(todoRepo, {todoListId: list.id});

const response = await todoRepo.findOne({
include: [{relation: 'todoList'}],
});

expect(toJSON(response)).to.deepEqual({
...toJSON(todo),
todoList: toJSON(list),
});
});
});
20 changes: 2 additions & 18 deletions examples/todo-list/src/repositories/todo-list-image.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {Getter, inject} from '@loopback/core';
import {
BelongsToAccessor,
DefaultCrudRepository,
InclusionResolver,
repository,
} from '@loopback/repository';
import {DbDataSource} from '../datasources';
Expand All @@ -29,27 +28,12 @@ export class TodoListImageRepository extends DefaultCrudRepository<
protected todoListRepositoryGetter: Getter<TodoListRepository>,
) {
super(TodoListImage, dataSource);

this.todoList = this.createBelongsToAccessorFor(
'todoList',
todoListRepositoryGetter,
);

// this is a temporary implementation until
// https://github.com/strongloop/loopback-next/issues/3450 is landed
const todoListResolver: InclusionResolver<
TodoListImage,
TodoList
> = async images => {
const todoLists = [];

for (const image of images) {
const todoList = await this.todoList(image.id);
todoLists.push(todoList);
}

return todoLists;
};

this.registerInclusionResolver('todoList', todoListResolver);
this.registerInclusionResolver('todoList', this.todoList.inclusionResolver);
}
}
37 changes: 3 additions & 34 deletions examples/todo-list/src/repositories/todo-list.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
DefaultCrudRepository,
HasManyRepositoryFactory,
HasOneRepositoryFactory,
InclusionResolver,
juggler,
repository,
} from '@loopback/repository';
Expand Down Expand Up @@ -38,50 +37,20 @@ export class TodoListRepository extends DefaultCrudRepository<
protected todoListImageRepositoryGetter: Getter<TodoListImageRepository>,
) {
super(TodoList, dataSource);

this.todos = this.createHasManyRepositoryFactoryFor(
'todos',
todoRepositoryGetter,
);

// this is a temporary implementation until
// https://github.com/strongloop/loopback-next/issues/3450 is landed
const todosResolver: InclusionResolver<
TodoList,
Todo
> = async todoLists => {
const todos: Todo[][] = [];
for (const todoList of todoLists) {
const todo = await this.todos(todoList.id).find();
todos.push(todo);
}

return todos;
};

this.registerInclusionResolver('todos', todosResolver);
this.registerInclusionResolver('todos', this.todos.inclusionResolver);

this.image = this.createHasOneRepositoryFactoryFor(
'image',
todoListImageRepositoryGetter,
);

// this is a temporary implementation until
// https://github.com/strongloop/loopback-next/issues/3450 is landed
const imageResolver: InclusionResolver<
TodoList,
TodoListImage
> = async todoLists => {
const images = [];

for (const todoList of todoLists) {
const image = await this.image(todoList.id).get();
images.push(image);
}

return images;
};

this.registerInclusionResolver('image', imageResolver);
this.registerInclusionResolver('image', this.image.inclusionResolver);
}

public findByTitle(title: string) {
Expand Down
17 changes: 1 addition & 16 deletions examples/todo-list/src/repositories/todo.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {Getter, inject} from '@loopback/core';
import {
BelongsToAccessor,
DefaultCrudRepository,
InclusionResolver,
juggler,
repository,
} from '@loopback/repository';
Expand Down Expand Up @@ -35,20 +34,6 @@ export class TodoRepository extends DefaultCrudRepository<
'todoList',
todoListRepositoryGetter,
);

// this is a temporary implementation until
// https://github.com/strongloop/loopback-next/issues/3450 is landed
const todoListResolver: InclusionResolver<Todo, TodoList> = async todos => {
const todoLists = [];

for (const todo of todos) {
const todoList = await this.todoList(todo.id);
todoLists.push(todoList);
}

return todoLists;
};

this.registerInclusionResolver('todoList', todoListResolver);
this.registerInclusionResolver('todoList', this.todoList.inclusionResolver);
}
}

0 comments on commit 0d41f5e

Please sign in to comment.