This is a four-part series: Part 1, Part 2, Part 3, Part 4
Before we get underway we need to update ember-data in our project to at
least 1.0.0-beta.8
. Open ember/bower.json
and if you have any version
less than 8 you’ll need to update to at least 8. If you are already on 8
or higher you won’t need to do anything.
Once you’ve made the change save the file and run bower install
from
the ember/
directory. If you are asked to choose between different
versions of ember-data make sure you choose the correct one.
In this part we’ll add Presentations to each of the Speaker pages. This means we’ll have to add a relationship between two models.
In ember/tests/integration/speakers-page-test.js
modify the test
“Should list all speakers and number of presentations”
// ember/tests/integration/speaker-page-test.js
test('Should list all speakers and number of presentations', function() {
visit('/speakers').then(function(assert) {
assert.equal(find('a:contains("Bugs Bunny (2)")').length, 1);
assert.equal(find('a:contains("Wile E. Coyote (1)")').length, 1);
assert.equal(find('a:contains("Yosemite Sam (3)")').length, 1);
});
});
The number in the parentheses will represent the number of presentations that this speaker has given.
Next we need to modify our beforeEach
function
// ember/tests/integration/speaker-page-test.js
var speakers = [
{ id: 1, name: 'Bugs Bunny', presentation_ids: [1,2] },
{ id: 2, name: 'Wile E. Coyote', presentation_ids: [3] },
{ id: 3, name: 'Yosemite Sam', presentation_ids: [4,5,6] }
];
var presentations = [
{ id: 1, title: "What's up with Docs?", speaker_id: 1 },
{ id: 2, title: "Of course, you know, this means war.", speaker_id: 1 },
{ id: 3, title: "Getting the most from the Acme categlog.", speaker_id: 2 },
{ id: 4, title: "Shaaaad up!", speaker_id: 3 },
{ id: 5, title: "Ah hates rabbits.", speaker_id: 3 },
{ id: 6, title: "The Great horni-todes", speaker_id: 3 }
];
server = new Pretender(function() {
this.get('/api/speakers', function(request) {
return [200, {"Content-Type": "application/json"}, JSON.stringify({speakers: speakers, presentations: presentations})];
});
this.get('/api/speakers/:id', function(request) {
var speaker = speakers.find(function(speaker) {
if (speaker.id === parseInt(request.params.id, 10)) {
return speaker;
}
});
return [200, {"Content-Type": "application/json"}, JSON.stringify({speaker: speaker, presentations: presentations})];
});
});
Completely replace the speakers
variable that was previously there. The only change to the API stub is that
presentations
is being added to the payload. The JSON here is the
style of JSON that ember-data expects to be emitted. We are returning a
payload that includes all speakers and presentations. The speaker
records include ids referencing the presentations associated.
We can now add the Presentation model to our Ember app:
// ember/app/models/presentation.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
speaker: DS.belongsTo('speaker')
});
We’ve told ember-data to expect the Presentation model to belong to the Speaker model. Let’s set the inverse relationship
// ember/app/models/speaker.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
presentations: DS.hasMany('presentation')
});
Modifying our existing Speaker model to add to relationship to its many Presentation models.
Finally to make this tests green we need to change our template:
// ember/app/templates/speakers/index.hbs
{{#each}}
{{~#link-to 'speakers.show' this}}
{{name}} ({{presentations.length}})
{{~/link-to}}
{{/each}}
Notice that we we can call regular JavaScript properties like length
on the association.
There is also a slight change that I’ve made to the link-to
. Adding
~
will tell Handlebars how to control
whitespace.
At this point our new test should be green. Lets add another.
// ember/tests/integration/speaker-page-test.js
test('Should list all presentations for a speaker', function(assert) {
visit('/speakers/1').then(function(assert) {
assert.equal(find('li:contains("What\'s up with Docs?")').length, 1);
assert.equal(find('li:contains("Of course, you know, this means war.")').length, 1);
});
});
This new test is asserting that when we visit a given speaker’s page all
of those speaker’s presentations will be listed. We first need to add
presentation data to the API stub (within our beforeEach
function) for visiting a speaker page.
// ember/tests/integration/speaker-page-test.js
this.get('/api/speakers/:id', function(request) {
var speaker = speakers.find(function(speaker) {
if (speaker.id === parseInt(request.params.id, 10)) {
return speaker;
}
});
var speakerPresentations = presentations.filter(function(presentation) {
if (presentation.speaker_id === speaker.id) {
return true;
}
});
return [200, {"Content-Type": "application/json"}, JSON.stringify({speaker: speaker, presentations: speakerPresentations})];
});
This modification of the previously existing stub will build a new payload object that includes the speaker matching the id requested and all of the presentations specific to that speaker.
Tying up this test is easy now, we just modify the Speaker’s show
template:
<h4>{{name}}</h4>
<h5>Presentations</h5>
<ul>
{{#each presentations}}
<li>{{title}}</li>
{{/each}}
</ul>
Now that we have a green test suite with our mocked out API let’s add the
real Rails endpoint. We’ll start by generating a new Presentation model.
Change to the rails/
directory in your project and run rails generate model presentation title:string speaker_id:integer
.
Next we’ll generate the serializer: rails generate serializer presentation
.
Let’s expand upon the rails/db/seeds.rb
file:
# rails/db/seeds.rb
bugs = Speaker.create(name: 'Bug Bunny')
wile = Speaker.create(name: 'Wile E. Coyote')
sam = Speaker.create(name: 'Yosemite Sam')
bugs.presentations.create(title: "What's up with Docs?")
bugs.presentations.create(title: "Of course, you know, this means war.")
wile.presentations.create(title: "Getting the most from the Acme categlog.")
sam.presentations.create(title: "Shaaaad up!")
sam.presentations.create(title: "Ah hates rabbits.")
sam.presentations.create(title: "The Great horni-todes")
Tell our Speaker
model that there is a relationship to Presentation
models:
# rails/app/models/speaker.rb
class Speaker < ActiveRecord::Base
has_many :presentations
end
Finally we need to modify the serializers.
# rails/app/serializers/presentation_serializer.rb
class PresentationSerializer < ActiveModel::Serializer
attributes :id, :title
end
# rails/app/serializers/speaker_serializer.rb
class SpeakerSerializer < ActiveModel::Serializer
embed :ids, include: true
attributes :id, :name
has_many :presentations
end
In the SpeakerSerializer
we have instructed the serializer to include
the associated Presentation
s.
Let’s reset the database and re-seed rake db:drop db:create db:migrate db:seed
Make sure you are running your Ember server with the proxy enabled:
ember server --proxy http://localhost:3000
Now you can hit your application and you should have a all of the necessary data.