Skip to content

Commit

Permalink
feat: add navigational properties to todo-list example
Browse files Browse the repository at this point in the history
- Overwrote find and findById functions in TodoRepository, TodoListRepository, TodoListImage to include a hard-coded retrieval of related models. #3195 is for improvement of this
- Updated response schemas for controller methods find and findById to leverage getModelSchemaRef and includeRelations
- Updated TodoList tutorial

Co-authored-by: Miroslav Bajtoš <[email protected]>
  • Loading branch information
nabdelgadir and bajtos committed Jun 21, 2019
1 parent 3fdc0b1 commit fedb6a7
Show file tree
Hide file tree
Showing 19 changed files with 802 additions and 171 deletions.
87 changes: 85 additions & 2 deletions docs/site/tutorials/todo-list/todo-list-tutorial-controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,89 @@ Controller TodoList was created in src/controllers/

And voilà! We now have a set of basic APIs for todo-lists, just like that!

#### Inclusion of Related Models

In order to get our related `Todo`s for each `TodoList`, let's update the
`schema`.

In `src/models/todo-list.controller.ts`, first import `getModelSchemaRef` from
`@loopback/rest`.

Then update the following `schema`s in `responses`'s `content`:

{% include code-caption.html content="src/models/todo-list.controller.ts" %}

```ts
@get('/todo-lists', {
responses: {
'200': {
description: 'Array of TodoList model instances',
content: {
'application/json': {
schema: {
type: 'array',
items: getModelSchemaRef(TodoList, {includeRelations: true}),
},
},
},
},
},
})
async find(/*...*/) {/*...*/}

@get('/todo-lists/{id}', {
responses: {
'200': {
description: 'TodoList model instance',
content: {
'application/json': {
schema: getModelSchemaRef(TodoList, {includeRelations: true}),
},
},
},
},
})
async findById(/*...*/) {/*...*/}
```

Let's also update it in the `TodoController`:

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

```ts
@get('/todos', {
responses: {
'200': {
description: 'Array of Todo model instances',
content: {
'application/json': {
schema: {
type: 'array',
items: getModelSchemaRef(Todo, {includeRelations: true}),
},
},
},
},
},
})
})
async findTodos(/*...*/) {/*...*/}

@get('/todos/{id}', {
responses: {
'200': {
description: 'Todo model instance',
content: {
'application/json': {
schema: getModelSchemaRef(Todo, {includeRelations: true}),
},
},
},
},
})
async findTodoById(/*...*/) {/*...*/}
```

### Create TodoList's Todo controller

For the controller handling `Todos` of a `TodoList`, we'll start with an empty
Expand Down Expand Up @@ -196,8 +279,8 @@ export class TodoListTodoController {
}
```

Check out our todo-list example to see the full source code generated for
TodoListTodo controller:
Check out our `TodoList` example to see the full source code generated for the
`TodoListTodo` controller:
[src/controllers/todo-list-todo.controller.ts](https://github.com/strongloop/loopback-next/blob/master/examples/todo-list/src/controllers/todo-list-todo.controller.ts)

### Try it out
Expand Down
6 changes: 4 additions & 2 deletions docs/site/tutorials/todo-list/todo-list-tutorial-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ Model TodoList was created in src/models/
```

Now that we have our new model, we need to define its relation with the `Todo`
model. Add the following import statements and property to the `TodoList` model:
model. Add the following import statements and property to the `TodoList` model
and update the `TodoListRelations` interface to include `todos`:

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

Expand Down Expand Up @@ -101,7 +102,8 @@ suggests, `@hasMany()` informs LoopBack 4 that a todo list can have many todo
items.

To complement `TodoList`'s relationship to `Todo`, we'll add in the `todoListId`
property on the `Todo` model to define the relation on both ends:
property on the `Todo` model to define the relation on both ends, along with
updating the `TodoRelations` interface to include `todoList`:

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

Expand Down
139 changes: 139 additions & 0 deletions docs/site/tutorials/todo-list/todo-list-tutorial-repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,145 @@ export class TodoListRepository extends DefaultCrudRepository<
}
```
### Inclusion of Related Models
To get the related `Todo` object for each `TodoList`, we have to override the
`find` and `findById` functions.
First add the following imports:
```ts
import {Filter, Options} from '@loopback/repository';
import {TodoListWithRelations} from '../models';
```
Add the following two functions after the constructor:
{% include code-caption.html content="src/repositories/todo-list.repository.ts" %}
```ts
async find(
filter?: Filter<TodoList>,
options?: Options,
): Promise<TodoListWithRelations[]> {
// Prevent juggler for applying "include" filter
// Juggler is not aware of LB4 relations
const include = filter && filter.include;
filter = {...filter, include: undefined};
const result = await super.find(filter, options);
// poor-mans inclusion resolver, this should be handled by DefaultCrudRepo
// and use `inq` operator to fetch related todos in fewer DB queries
// this is a temporary implementation, please see
// https://github.com/strongloop/loopback-next/issues/3195
if (include && include.length && include[0].relation === 'todos') {
await Promise.all(
result.map(async r => {
r.todos = await this.todos(r.id).find();
}),
);
}
return result;
}
async findById(
id: typeof TodoList.prototype.id,
filter?: Filter<TodoList>,
options?: Options,
): Promise<TodoListWithRelations> {
// Prevent juggler for applying "include" filter
// Juggler is not aware of LB4 relations
const include = filter && filter.include;
filter = {...filter, include: undefined};
const result = await super.findById(id, filter, options);
// poor-mans inclusion resolver, this should be handled by DefaultCrudRepo
// and use `inq` operator to fetch related todos in fewer DB queries
// this is a temporary implementation, please see
// https://github.com/strongloop/loopback-next/issues/3195
if (include && include.length && include[0].relation === 'todos') {
result.todos = await this.todos(result.id).find();
}
return result;
}
```
Now when you get a `TodoList`, a `todos` property will be included that contains
your related `Todo`s, for example:
```json
{
"id": 2,
"title": "My daily chores",
"todos": [
{
"id": 3,
"title": "play space invaders",
"desc": "Become the very best!",
"todoListId": 2
}
]
}
```
Let's do the same on the `TodoRepository`:
{% include code-caption.html content="src/repositories/todo.repository.ts" %}
```ts
async find(
filter?: Filter<Todo>,
options?: Options,
): Promise<TodoWithRelations[]> {
// Prevent juggler for applying "include" filter
// Juggler is not aware of LB4 relations
const include = filter && filter.include;
filter = {...filter, include: undefined};

const result = await super.find(filter, options);

// poor-mans inclusion resolver, this should be handled by DefaultCrudRepo
// and use `inq` operator to fetch related todo-lists in fewer DB queries
// this is a temporary implementation, please see
// https://github.com/strongloop/loopback-next/issues/3195
if (include && include.length && include[0].relation === 'todoList') {
await Promise.all(
result.map(async r => {
r.todoList = await this.todoList(r.id);
}),
);
}

return result;
}

async findById(
id: typeof Todo.prototype.id,
filter?: Filter<Todo>,
options?: Options,
): Promise<TodoWithRelations> {
// Prevent juggler for applying "include" filter
// Juggler is not aware of LB4 relations
const include = filter && filter.include;
filter = {...filter, include: undefined};

const result = await super.findById(id, filter, options);

// poor-mans inclusion resolver, this should be handled by DefaultCrudRepo
// and use `inq` operator to fetch related todo-lists in fewer DB queries
// this is a temporary implementation, please see
// https://github.com/strongloop/loopback-next/issues/3195
if (include && include.length && include[0].relation === 'todoList') {
result.todoList = await this.todoList(result.id);
}

return result;
}
```
We're now ready to expose `TodoList` and its related `Todo` API through the
[controller](todo-list-tutorial-controller.md).
Expand Down
2 changes: 1 addition & 1 deletion examples/todo-list/data/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"TodoList": {
"1": "{\"title\":\"Sith lord's check list\",\"lastModified\":\"a long time ago\",\"id\":1}",
"2": "{\"title\":\"My daily chores\",,\"lastModified\":\"2018-07-13\",\"id\":2}"
"2": "{\"title\":\"My daily chores\",\"lastModified\":\"2018-07-13\",\"id\":2}"
}
}
}
1 change: 1 addition & 0 deletions examples/todo-list/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@loopback/build": "^2.0.1",
"@loopback/eslint-config": "^1.1.2",
"@loopback/http-caching-proxy": "^1.1.3",
"@loopback/repository": "^1.8.0",
"@loopback/testlab": "^1.6.1",
"@types/lodash": "^4.14.134",
"@types/node": "^10.14.9",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,33 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {
Client,
createRestAppClient,
expect,
givenHttpServerConfig,
toJSON,
} from '@loopback/testlab';
import {Client, createRestAppClient, expect, toJSON} from '@loopback/testlab';
import {TodoListApplication} from '../../application';
import {TodoList, TodoListImage} from '../../models/';
import {TodoListRepository, TodoListImageRepository} from '../../repositories/';
import {givenTodoListImage, givenTodoList} from '../helpers';
import {TodoListImageRepository, TodoListRepository} from '../../repositories/';
import {
givenRunningApplicationWithCustomConfiguration,
givenTodoListImage,
givenTodoListInstance,
givenTodoListRepositories,
} from '../helpers';

describe('TodoListApplication', () => {
let app: TodoListApplication;
let client: Client;
let todoListImageRepo: TodoListImageRepository;
let todoListRepo: TodoListRepository;
let todoListImageRepo: TodoListImageRepository;

let persistedTodoList: TodoList;

before(givenRunningApplicationWithCustomConfiguration);
before(async () => {
app = await givenRunningApplicationWithCustomConfiguration();
});
after(() => app.stop());

before(givenTodoListImageRepository);
before(givenTodoListRepository);
before(async () => {
({todoListRepo, todoListImageRepo} = await givenTodoListRepositories(app));
});
before(() => {
client = createRestAppClient(app);
});
Expand All @@ -38,7 +40,7 @@ describe('TodoListApplication', () => {
});

beforeEach(async () => {
persistedTodoList = await givenTodoListInstance();
persistedTodoList = await givenTodoListInstance(todoListRepo);
});

it('creates image for a todoList', async () => {
Expand Down Expand Up @@ -84,34 +86,6 @@ describe('TodoListApplication', () => {
============================================================================
*/

async function givenRunningApplicationWithCustomConfiguration() {
app = new TodoListApplication({
rest: givenHttpServerConfig(),
});

await app.boot();

/**
* Override default config for DataSource for testing so we don't write
* test data to file when using the memory connector.
*/
app.bind('datasources.config.db').to({
name: 'db',
connector: 'memory',
});

// Start Application
await app.start();
}

async function givenTodoListImageRepository() {
todoListImageRepo = await app.getRepository(TodoListImageRepository);
}

async function givenTodoListRepository() {
todoListRepo = await app.getRepository(TodoListRepository);
}

async function givenTodoListImageInstanceOfTodoList(
id: typeof TodoList.prototype.id,
todoListImage?: Partial<TodoListImage>,
Expand All @@ -120,8 +94,4 @@ describe('TodoListApplication', () => {
delete data.todoListId;
return await todoListRepo.image(id).create(data);
}

async function givenTodoListInstance(todoList?: Partial<TodoList>) {
return await todoListRepo.create(givenTodoList(todoList));
}
});
Loading

0 comments on commit fedb6a7

Please sign in to comment.