Jul 13, 2015
Table of contents:
Last week we introduced Ember CLI Mirage to mock the API server. By mocking the API server it means we can effectively work with the Ember application in isolation during development and testing.
When it comes to shipping the application to production, we can just switch out the mock for the real server.
Last week we started to get the data from the “mock” server. An important part of the application we are building is the related “replies” to a task.
In today’s tutorial we will be looking at how we can use TDD to get to the point where we are also returning the replies for each task.
We are using TDD to develop this application and so the first thing we need to do is to write the test:
test("task has replies", function (assert) {
server.createList("task", 3);
server.createList("reply", 2, { task_id: 1 });
visit("/");
andThen(function () {
var replies = find(".task-list .task:eq(0) .task-replies");
assert.equal(replies.text(), 2);
});
});
In this test we’re grabbing the first task from the task list and then asserting that it has the correct number of replies.
If you run the test it should fail.
The first thing we need to do is to update the template to display the number of task replies. To do this we will add a new span
tag:
<span class="task-replies"></span>
Inside the span tag I’m going to access the replies
property and then call the length
method.
<span class="task-replies">{{task.replies.length}}</span>
In theory this will work because replies
should be an array
of replies as it should represent the Has Many
relationship between Tasks and Replies.
However, if you run the test, it should still fail.
So to access the replies
of a Task, we need to update the Ember Data model with the relationship:
import DS from "ember-data";
export default DS.Model.extend({
title: DS.attr("string"),
status: DS.attr("string"),
replies: DS.hasMany("reply"),
});
As you can see, we can implement the relationship using DS.hasMany()
. If this is new to you, check out my tutorial on Ember Data.
Next we need to generate the Reply model:
ember g model reply
For now I’m just going to implement the reverse side of the relation to keep things simple:
import DS from "ember-data";
export default DS.Model.extend({
task: DS.belongsTo("task"),
});
As a side note, you will need to update the unit tests to make them aware of the related models. For example, you need to update this block in the task-test.js
unit file:
moduleForModel("task", "Unit | Model | task", {
// Specify the other units that are required for this test.
needs: ["model:reply"],
});
In order to include replies as part of the task JSON, I’m going to use the JSON API approach of embedding the ids.
Ember Data uses serialisers as a way of providing an agnostic interface that can then be customised. In order to tell Ember Data to expect the replies, we need to generate a Task Serializer:
ember g serializer task
This will create a file called task.js
under app/serializers
.
Next we need to update the standard Serializer to look like this:
import DS from "ember-data";
export default DS.RESTSerializer.extend({
attrs: {
replies: { serialize: "ids" },
},
});
Last week we looked at using Ember CLI Mirage to return data from the mock server. In order to also return the replies, we’re going to have to tweak the work from last week a bit.
So for each task, we also need to grab the replies. I’m going to assume that whenever a task is returned, the replies should also be returned:
export default function () {
this.get("/api/tasks", function (db, request) {
var tasks, replies, related;
tasks = db.tasks.where(request.queryParams);
replies = [];
for (var i = 0; i < tasks.length; i++) {
related = db.replies.where({ task_id: tasks[i].id });
tasks[i].replies = related.map(function (reply) {
return reply.id;
});
replies = replies.concat(related);
}
return { tasks: tasks, replies: replies };
});
}
In this updated endpoint we grab all of the tasks as we did last week. Next we iterate over the task list and grab the replies for each task. Finally we return the tasks and replies as a JSON object.
You might want to do something fancy with a the included
query parameter to only return the replies if they are requested. I’m going to keep it simple and just return them regardless.
Finally we also need to create the Factory and Fixtures files to make hitting the endpoint in development and during testing actually work.
First we can create the fixtures file as we did with the tasks fixtures:
export default [
{
id: 1,
comment: "Hello world",
task_id: 1,
},
{
id: 2,
comment: "Hello world",
task_id: 1,
},
{
id: 3,
comment: "Hello world",
task_id: 1,
},
];
As you can see this is simply an array of JSON objects.
Next we can create the Factory:
import Mirage from "ember-cli-mirage";
export default Mirage.Factory.extend({
task_id: function (i) {
return i;
},
});
Again this is fairly simple at this stage. Here I’m setting the task_id
to be i
. When I actually use this Factory during the tests I will be more than likely overriding this value anyway.
Finally we can use the Factory to create the data during the test:
Now if you run the test again you should see it pass!
Returning all of the replies with a task could be inefficient if the number of replies for each task becomes a big number. It might be better to simply return an integer value, and then only get the replies for a single task.
In either case, we’ve covered a lot to do with related models and returning related data from Ember Mirage.
Hopefully this was a good example of building upon the basic Ember Data things you see in tutorials to see how it would actually work in real life.