Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(example-todo-list): use real relation resolvers #3774

Merged
merged 1 commit into from
Sep 25, 2019
Merged
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
88 changes: 23 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,24 @@ 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.
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:

First, add the following import:

```ts
import {InclusionResolver} from '@loopback/repository';
```

Next, in the constructor, add the following custom resolver:
{% include code-caption.html content="src/repositories/todo-list.repository.ts" %}

```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(
nabdelgadir marked this conversation as resolved.
Show resolved Hide resolved
'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 to register the resolver
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 @@ -151,36 +123,22 @@ your related `Todo`s, for example:
}
```

Let's do the same on the `TodoRepository`:
On the other end, the
[`BelongsToAccessor`](https://loopback.io/doc/en/lb4/apidocs.repository.belongstoaccessor.html)
also comes with an inclusion resolver property that we can register on the
`TodoRepository`. So, let's register this resolver to the `TodoRepository`
similar to how we did it for the `TodoListRepository`:

{% include code-caption.html content="src/repositories/todo.repository.ts" %}
nabdelgadir marked this conversation as resolved.
Show resolved Hide resolved

```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 to register the resolver
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),
});
});
});
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);
}
}