0% found this document useful (0 votes)
219 views

EmberJS Testing On Rails

Testing EmberJS app in Ruby On Rails

Uploaded by

Jaime Rocha
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
219 views

EmberJS Testing On Rails

Testing EmberJS app in Ruby On Rails

Uploaded by

Jaime Rocha
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 105

Ember.

js - Testing on Rails
Martin Feckie
This book is for sale at http://leanpub.com/emberjs-testingonrails
This version was published on 2014-05-05

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
2014 Martin Feckie - RN, MHSM

Contents
Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Erratum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Introduction . . . . . . . . . . . . . .
A Bit About My Motivation . . . .
First There Was Rails . . . . . . . .
Along Came jQuery . . . . . . . . .
Javascript IV -Ember - A New Hope
Testing to the Rescue . . . . . . . .

.
.
.
.
.
.

4
4
5
5
6
7

Design Choices, the Golden Path and Why Didnt You Include / Use / Show X, Y, Z? . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

Chapter One - Bootstrapping Our App . . . .


What are we going to build? . . . . . . . . .
Prerequisites . . . . . . . . . . . . . . . . . .
Generating Our App . . . . . . . . . . . . .
Getting Rid of Some Junk . . . . . . . . . . .
Some Additional Development Dependencies
Install Ember . . . . . . . . . . . . . . . . .

.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

Chapter Three - Routing Specs . . . .


QUnit Specs (or Are They Tests?) .
Creating a Contacts Route . . . . .
And So a Problem Presents Itself . .
An Abstraction Trying to Get Out?

.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

17
17
19

.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

Chapter Two - Testing Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


RSpec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Teaspoon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

9
9
9
9
10
12
13

.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

24
24
27
28
30

Chapter Four - Getting Started With Data Rendering


Models . . . . . . . . . . . . . . . . . . . . . . . . .
Unit Testing Our Model . . . . . . . . . . . . . . . .
An Unexpected Problem . . . . . . . . . . . . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

32
33
35
37

CONTENTS

Chapter Five - Nested Routes . . . . . . .


Defining Routes Within Resources . . . .
Rendering the Individual Contact . . . .
Refactor Tests . . . . . . . . . . . . . . .
An Alternate Way of Viewing Our Specs
Navigating Around the App . . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

39
40
41
41
42
43

Chapter Six - Persisting Data With Rails .


Setting Up the Backend . . . . . . . . . .
Serializing the Data . . . . . . . . . . . .
Connecting the Backend to the Frontend!
More Routes Necessary . . . . . . . . . .
Adding Contacts . . . . . . . . . . . . .
Deleting a Contact . . . . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

45
45
50
51
52
54
62

Chapter 7 - Relationships in Ember .


A Place to Store Emails . . . . . . .
Testing Relationships . . . . . . . .
Rendering Relationships . . . . . .
Wiring Up the Relationships to Rails
An Email Model . . . . . . . . . . .
Contact Relationship . . . . . . . .
Email Relationship . . . . . . . . .
Email Controller and Routes . . . .
Serializing and Relationships . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

66
67
68
69
71
72
74
75
76
80

Chapter 8 - Unit Testing Computed Properties . . . . . . . . . . . . . . . . . . . . . . . . .

82

Chapter 9 - Creating and Updating Emails on the Frontend


Faking HTTP Requests . . . . . . . . . . . . . . . . . . . .
Reimplementing RESTAdapter in Tests . . . . . . . . . . .
Saving Associations . . . . . . . . . . . . . . . . . . . . . .
Dealing With Failed Saves . . . . . . . . . . . . . . . . . .
Testing JSON Responses in Rails . . . . . . . . . . . . . . .

86
86
89
94
96
99

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

Acknowledgements
I would like to acknowledge the special contribution of Javier Cadiz for the extremely useful feedback
and for test driving the book.
The Ruby Rogues and their amazing guests. Ive consumed every episode (many more than once)
and have even listened to them whilst snowboarding! Their content is constantly excellent.
The JavaScript Jabber podcast for more wonderful topics and guests.
Douglas Crockfords series Crockford on JavaScript should be watched by anyone interested in
the language, entertaining, insightful and really brilliant at explaining the good bits and the bits to
worry about.
Sandi Metz for Practical Object Oriented Design in Ruby and every one of her talks available on the
internet.
Ryan Bates for the RailsCasts series. Giving such a great resource away is a true act of altruism.
http://www.yuiblog.com/crockford/

Erratum
Chapter 7
Contact class should destroy emails when contact is destroyed.
contact.rb
1
2
3

class Contact < ActiveRecord::Base


has_many :emails, dependent: :destroy
end

Chapter 7
Serializer should not include has_one :contact
email_serializer.rb
1
2
3

class EmailSerializer < ApplicationSerializer


attributes :id, :address, :contact_id
end

Chapter 7
contact_id was missing from model.
New file - /app/assets/javascripts/unit/models/email.js
1
2
3
4

AddressBook.Email = DS.Model.extend({
address: DS.attr('string'),
contact_id: DS.attr('number')
});

Chapter 7
contact_id missing form FIXTURES.

Erratum

spec/javascripts/spec_helper.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

var resetFixtures = function () {


AddressBook.Contact.FIXTURES = [
{
id: 1,
first_name: 'Dave',
last_name: 'Crack',
emails: [1,2]
},
//.....
];
AddressBook.Email.FIXTURES = [
{
id: 1,
address: '[email protected]',
contact_id: 1
},
{
id: 2,
address: '[email protected]',
contact_id: 1
}
];
};

Introduction
A Bit About My Motivation
Ive been very interested in the rise of Ember.js and front end web frameworks in general. I recently
made an app for a customer using Angular.js for front-end rendering. There is a very clear focus on
testing in Angular and I found it really easy to build a test suite. Integrating with Rails was much
more of a challenge however. I began to look at Ember and gave it a good go. At the time I found
that persisting data was a real challenge as was integrating with Rails, this was a big disappointment
and in the end I kept going with Angular and was able to produce a well tested app I was happy
with. Angular, however, is much more free form than Ember and presents lots of opportunities for
differences of opinion and approach. Not necessarily a bad thing, but if Im producing an app Id
like another developer to be able to come along and have a very good idea of where to find things
and Ember presents a much higher opportunity to achieve this.
Ive continued to watch the development of Ember and LOVE where its going, the passion and
talent of its developers is amazing and Im so grateful for what they are doing. Most of my initial
difficulties around persistence have been well and truly resolved. Im still, however, frustrated with
the lack of documentation about testing beyond integration tests. There are heaps of blog posts our
there and people have put a lot of work into finding ways to test Ember apps. The problem is, with
all the API changes, many are outdated and simply dont apply any more. That shouldnt be taken
as a criticism of any of the work the authors have put in, Im truly grateful they did.
I had desperately hoped to find a book that would tell me how to do test driven development with
Ember, but it still hasnt eventuated. Ive spent months playing around with different configurations
and setups with Rails and Ember and came to the realisation that in creating my own setup Ive
learned a lot. As a result I thought I would try and write the book I would like to have read!
Im not gonna suggest for a minute that everything Ive put forward is perfect, Im sure its not and
there will definitely be others who will come up with better ideas in the future. Im content that
what Ive put forward is a good starting point. Im very open to feedback and dialogue and want to
create something that helps. If I succeed in helping, please tell others, if not please tell me!

Introduction

A Note on Copyright
If you bought this book or were given a copy by me, skip this!
If you didnt buy this book and found it on a file sharing site, please have a think about it.
This book is a labour of love for me and an attempt to share countless hours of experience
with others. If you feel that your need is so great that you must read without paying,
contact me and I will see what I can do to provide you with a legal copy that receives
regular updates.
Karma is a wonderful thing and if you do choose to take my book without paying, then I
wish you well and I hope that you find it useful and it helps you develop in your career. If
so, please consider buying a copy for someone else who would benefit.

First There Was Rails


If youve even thought about purchasing this book, Im sure youre familiar with Ruby on Rails. As
a framework its done much to drive best-practices in web application development, providing users
with a truly open source framework very different from some of the proprietary systems on offer.
The true beauty of working on a Rails app is that any part of the framework that you dont like, you
can change! Monkey patch away, but be warned, straying from the golden path is something that
can really cause you long term pain.
To combat some of the perils of free form development Rails provides us with a convention over
configuration philosophy. If you do things the Rails way then things tend to flow very easily and
your pain is minimized!
One of the conventions that I love the most is the focus on testing. There are plenty that argue that
the style of Test Drive Development provided by Rails is invalid. The argument goes that it doesnt
follow the test first style recommend by many. When you run
1

$> rails generate model Thing name:string

you get pre populated tests for your newly generated model. Personally, I think thats a good thing
for new developers and its easily disabled by seasoned developers.
And so it was that all was good in the world, we rendered on the server, pushed to the client and
everyone was happy. But then

Along Came jQuery


Despite masses of critics, there can be no denying that javascript won the language wars (in terms of
availability at least!). Its used on more devices, in more places that any other language. We can spend
hours bike shedding the topics, or accept reality and find good ways to work with the language.

Introduction

As the web has developed and javascript won the war, there became an increasing need to do more
stuff on the client side. We started to see people doing evil things with javascript - anyone remember
the pop-up laden websites of the early 2000s? Not only did javascript facilitate annoying pop-ups
but also helped malicious actions.
Having said all that, some people found really creative uses for javascript and one of the most
successful early ideas was jQuery. jQuery provided developers with a straightforward way to
interact with the Document Object Model (DOM). Developers being lazy (in a good way) found
the $ abstraction very useful.
Javascript DOM selection vs jQuery
1
2
3
4
5
6
7

document.getElementByClassName('container')
document.getElementById('someID')
document.getElementByTagName('p')
// vs
$('.container')
$('#someId')
$('p')

In and of itself, the $ abstraction doesnt do anything to speed up the interface, but does aid developers in providing a one stop shop for interacting with the DOM. Not needing to change methods
speeds development so we dont have to do document.getElementById or document.getElementByTagName,
we can simply adjust the call inside the $ abstraction.
jQuery allowed developers to provide a bit more structure to their javascript, but once an application
grew to a reasonable size, it became a fight to keep the code clean and developers would often
experience call-back hell .

Javascript IV -Ember - A New Hope


I knew that Ember.js was going to be special the moment I looked at the early website. I cant believe
how excited I was by the bindings! Oh the bindings! I type it here and its updated there, live and I
can persist it!!! Woo hoo.
Its not surprising that Ember is such a great framework, after all its got Yeduda Katz (of jQuery,
Rails and much more), Tom Dale. The other core contributors are amazing as are the hundreds of
other whove made contributions. Ive got a tremendous amount of respect for the Ember team, their
commitment to getting it right and their willingness to acknowledge when things went wrong (early
stage ember-data anyone?) is impressive.
Call-back hell is a term used to refer to the problem to heavily nested code, often detected through its particular triangular shape. http:
//callbackhell.com/

Introduction

I can also see the direction the project is going in and can see huge strides in performance and
convenience with the release of 1.0 . The six week release cycle will see the speed of the framework
improve and minor bugs and problems resolved (though already the benchmarks are impressive in
comparison to other frameworks )
The learning curve for any framework following the convention over configuration pathway is
almost always huge. This is certainly the case with Ember. The initial excitement of being able to
do so much, with so little code soon gives way to the frustration of an error caused by a misnamed
class or incorrect pluralisation.

Testing to the Rescue


Testing is a great way for us to explore the framework and I hope to provide a robust guide to allow
you to get up and going with an environment that provides rapid feedback and some good strategies
for testing your Ember.js apps.

A word of warning on the code


examples
The Leanpub platform automatically creates a \ at the end of long lines to indicate
wrapping. These obviously shouldnt be copied and I havent found a way to turn them
off. Please be cautious when following the examples.

Although artificial benchmarks are frowned upon, heres some interesting comparisons with backbone.js http://jsfiddle.net/jashkenas/CGSd5/.
Much more impressive is the future with HTMLBars comparing with React.js, Backbone and raw javascript when animating elements. http://jsfiddle.
net/Ut2X6/. HTMLBars is a very exciting potential improvement to Handlebars https://github.com/tildeio/htmlbars

Design Choices, the Golden Path and


Why Didnt You Include / Use / Show
X, Y, Z?
Ive put out a few releases of the book and so far have received positive feedback, however Ive also
had some questions that I feel are worth responding to.

Why Didnt You Use Third Party Libraries to Cover BDD, Such as
Pavlov?
The philosophy Im coming from with the book is that getting as close to the metal as possible will
give you the knowledge and confidence to use third party libraries because you will learn what they
are abstracting away. Knowing the hooks they use and the methods they leverage will allow you to
troubleshoot and make the trade offs you feel are worth it.

Why Dont You Use Factories Instead of Fixtures in Ember?


Basically, none of the things were going to use fixtures with require any dynamic attributes. Fixtures
are lightweight and provided for free by Ember. I chose not to add in the extra work to provide
dynamic objects.

You Know That You Can Run Multiple Suites of Tests With
Teaspoon?
Yes, in the first draft of the book I utilised this feature of Teaspoon, but found that there was a
conflict with Guard that led to tests getting stuck in one place, leading to false positive / negative
notifications via Growl. As a result, I chose not to use the feature because I valued reliability higher
than separation of unit and integration tests. The issue with sticking may well get ironed out with
future (or even current) releases of Guard / Teaspoon, so by all means use the feature yourself.

The Golden Path


Ive chosen to use the same tools as much as possible as the Ember core team. If you prefer to use
Jasmine, Mocha, Chai, Pavlov etc. then go for it. Using QUnit would not be my first choice, but it is
well integrated with Ember, well supported on Stackoverflow and other sources of knowledge and
provides a great baseline. This is really about getting started with training wheels, if youre ready
to go without them, take them off an go!

Chapter One - Bootstrapping Our App


What are we going to build?
In this chapter we will get started with a brand new app and build a continuous testing setup that
allows us to create our app in confidence.
Come back and insert diagram of whats gonna be built and explain some choices further

Prerequisites
In order to follow along you will need to have the following installed
Rails 4.1.0
sqlite3
PhantomJS http://phantomjs.org/ - Definitely download a precompiled binary, unless you
want to spend at least 30 minutes compiling form source!

Generating Our App


Lets get started by initialising a new app. Well be sticking with sqlite3 for this app because we dont
have to worry about user permissions and interfering with any postgres or mysql setup you may
have on your system.
1

$> rails new address-book -T

The -T flags tells Rails not to include MiniTest, as well be using RSpec
Next well initialise a git repository.

Chapter One - Bootstrapping Our App

10

Initializing git repository


1
2
3
4

$>
$>
$>
$>

cd address-book
git init
git add .
git commit -m 'initial commit'

Getting Rid of Some Junk


One issue that well run into is that turbolinks is included by default in Rails 4 and causes a significant
number of conflicts, well also avoid using CoffeeScript because there are aspects of its syntax that
dont comfortably fit with Ember code constructs . Well also get rid of jbuilder.
Removing unnecessary Gems from Gemfile
1

source 'https://rubygems.org'

2
3
4
5
6
7
8
9
10

gem
gem
gem
gem
gem
gem
gem
gem

'rails', '4.1.0'
'sqlite3'
'sass-rails', '~> 4.0.0'
'uglifier', '>= 1.3.0'
'coffee-rails', '~> 4.0.0'
'jquery-rails'
'turbolinks'
'jbuilder', '~> 1.2'

11
12
13
14
15

group :doc do
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', require: false
end

16
17
18
19

group :development do
gem 'spring'
end

Linux users might need a javascript runtime which can be resolved by adding two extra gems execjs and therubyracer
Ember depends on jQuery version 1.7, 1.8, 1.9, 1.10, or 2.0. The jquery-rails gem doesnt always match
these versions, so lets make sure it uses the correct on by setting the gem version. Gem version 3.0.3
will give us jQuery 1.10 .
Computed properties, for example arent valid CoffeeScript syntax, though there are workarounds http://discuss.emberjs.com/t/coffeescriptand-ember/2028 and a project that creates an Ember specific wrapper for CoffeeScript http://emberscript.com/

Chapter One - Bootstrapping Our App

11

Specifying the JQuery version


1

gem 'jquery-rails', '3.0.3'

Next, well remove turbo link and jquery-ujs from the asset pipeline
app/assets/javascripts/application.js
1
2
3
4

//=
//=
//=
//=

require jquery
require jquery_ujs
require turbolinks
require_tree .

And finally well remove reference to it in our templates.


app/views/layouts/application.html.erb
1
2
3
4
5
6
7
8
9

<head>
<title>AddressBook</title>
<%= stylesheet_link_tag
"application", media: "all", "data-turbolinks-track"\
=> true %>
<%= stylesheet_link_tag
"application", media: "all"%>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= javascript_include_tag "application"%>
<%= csrf_meta_tags %>
</head>

Now well remove the old gems using bundler. Note the --local flag, this tells bundler to only look
at things already installed locally and avoids hitting rubygems. This can be a great speedup, but
obviously doesnt help if your dont have a particular gem already available locally.
Running bundler to remove unnecessary gems
1

$> bundle install --local

And finally well commit our changes

Chapter One - Bootstrapping Our App

12

Committing changes
1
2

$> git add .


$> git commit -m 'Goodbye turbolinks'

Some Additional Development Dependencies


Well now add bunch of things that make error tracking better when developing our app.
Quiet assets is great for reducing the amount of noise in your server log during development.
Better errors gives you much better ways of dealing with errors, bringing up an interactive
console in browser when an error occurs. Being able to dig around in the live code in this
style really helps to track down bugs.
Meta request allows integration with RailsPanel in Google Chrome, which gives lots of useful
information about your requests, DB hits, and template rendering. I recommend you install
this from the Chrome web store if you do much Rails development.
Binding of caller allow us to use a Read Evaluate Print Loop (REPL) in the interactive console
for better_errors and helps with inspection of objects in the console.
Add the following to your Gemfile:
Gemfile
1
2
3
4
5
6
7

group
gem
gem
gem
gem
gem
end

:development do
'spring'
'quiet_assets'
'meta_request'
'better_errors'
'binding_of_caller'

Then:

See this great screencast by Ryan Bates on Better Errors and RailsPanel http://railscasts.com/episodes/402-better-errors-railspanel

Chapter One - Bootstrapping Our App

13

Install and commit


1
2
3

$> bundle install


$> git add .
$> git commit -m 'Add error handling gems'

Install Ember
Lets get on now and install Ember. The core team have very kindly provided a great gem to help us
integrate Ember with rails.
Lets add it to our Gemfile.
Adding Ember to our Gemfile
1
2

gem 'ember-rails'
gem 'ember-source', '1.5.0'

Ember-rails provides us with a lot of development dependencies such as the Ember.js files in
development and production versions, a precompilation pathway for Handlebars templates and
ActiveModel::Serializer, which well use heavily for passing data back and forth between the server.
After we run bundler, we can use the build in generator to get Embers prerequisites installed.
Bootstrapping Ember
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

$> rails generate ember:bootstrap


insert app/assets/javascripts/application.js
create app/assets/javascripts/models
create app/assets/javascripts/models/.gitkeep
create app/assets/javascripts/controllers
create app/assets/javascripts/controllers/.gitkeep
create app/assets/javascripts/views
create app/assets/javascripts/views/.gitkeep
create app/assets/javascripts/routes
create app/assets/javascripts/routes/.gitkeep
create app/assets/javascripts/helpers
create app/assets/javascripts/helpers/.gitkeep
create app/assets/javascripts/components
create app/assets/javascripts/components/.gitkeep
create app/assets/javascripts/templates
create app/assets/javascripts/templates/.gitkeep
create app/assets/javascripts/templates/components

Chapter One - Bootstrapping Our App

create
create
create
create
create
create

18
19
20
21
22
23

14

app/assets/javascripts/templates/components/.gitkeep
app/assets/javascripts/mixins
app/assets/javascripts/mixins/.gitkeep
app/assets/javascripts/address_book.js
app/assets/javascripts/router.js
app/assets/javascripts/store.js

Whoah! Thats a whole bunch of setup thats been done for us! What youll see from this is the
influence of convention over configuration. Theres a place for everything and everything should
be in its place! At this stage, most of these files are only for place holding (.gitkeep tells git keep the
folder in the source control even if it is empty), except of the .js files. Well see what they are about
in depth later.

Views vs Templates
What you know as a view in Rails is almost certainly a template in Ember! Its pretty easy
to get caught out with these distinctions and Im sure youll find yourself putting files in
the wrong place as you learn. If a page isnt rendering as you expected, ask yourself if the
file is the correct directory.

Next well ensure that we have the necessary Ember javascripts available.
Ember javascript dowload
1

$> rails g ember:install

This will go off and download the development and production javascripts necessary for Ember.
Now that weve got Ember available there are a few files that need updated to get us to a good base
point.
We can now delete the app/view/layouts folder and its contents (application.html.erb).

You made me update it and now youve made me


delete it!
Yeah, some folks like to play along as we go, so I wanted to ensure that everything still works in
between commits. Removing it earlier would have cause a render error, but also you now know
how to get rid of turbolinks if you want to use a different application structure in the future.

.
Next, update your routes

Chapter One - Bootstrapping Our App

15

config/routes.rb
1
2
3
4

Rails.application.routes.draw do
root to: 'assets#index'
get 'assets/index'
end

With that route defined, well need to create an associated controller.


app/controllers/assets_controller.rb
1
2
3
4

class AssetsController < ApplicationController


def index
end
end

We will also need a Rails view to render, so go ahead and create one.
app/views/assets/index.html.erb
1
2
3
4
5
6
7
8
9
10
11

<!DOCTYPE html>
<html>
<head>
<title>Address book</title>
<%= stylesheet_link_tag
"application", :media => "all" %>
<%= csrf_meta_tags %>
</head>
<body>
<%= javascript_include_tag "application" %>
</body>
</html>

When using ember-rails youre default starting point for rendering is a handlebars file called
application.hbs. This file will be rendered as your root route. Lets create it.
app/assets/javascripts/templates/application.hbs
1

<h1>Welcome to the Address Book</h1>

2
3

{{outlet}}

Ok, let check that all is well, fire up a server (rails s) and see for yourself. If all is well in the world,
you should see:

Chapter One - Bootstrapping Our App

Welcome to the Address Book

If all is well, lets commit the changes.


Committing our Ember install
1
2

$> git add . --all


$> git commit -m 'Ember install'

If not, its time for some debugging! See you when you get back.

16

Chapter Two - Testing Setup


Well be using a combination of RSpec, QUnit, Guard, PhantomJS and Teaspoon for our testing
environment. Its taken a great deal of exploration to get to this combination and I now feel
comfortable sharing this setup with you.

Why not use Jasmine or {insert other framework


here}?
Things Ive tried that didnt go so well (and why I abandoned them):
jasmine-rails (not enough community support for jasmine and Ember, so difficult to get
advice and examples)
karma test runner (Didnt like having to run two separate test suites)
capybara + selenium (Capybara is good, but quite verbose and doesnt do too well with
asynchronous testing)
capybara + PhantomJS (Same as capybara + selenium)
I have used jasmine as my tool of choice for javascript testing when using Angular.js because I
particularly like the describe, it does something style of test writing. It feels very comfortable
and close to RSpec style. I spent a considerable period trying to get my setup going with jasmine,
but in the end found that using the same tools as Ember core provided a happier experience and
there many more examples on the web to explore.
At the end of the day, you can use whatever you like with enough effort, but is it really worth it???

.
For those not familiar with PhantomJS, its a headless browser based on Webkit. It allows us a very
nice way to run tests without having to open a browser and constantly refresh the page.

RSpec
Most rails developers will be familiar with RSpec and the opinions of DHH, I tend to disagree and
find that RSpec provides a great way of thinking about my apps and as a result the tests flow from
my brain to the text editor very simply.
Getting started with RSpec is very straightforward and we will also turn off automatic spec
generation to avoid unnecessary kruft! Well also use FactoryGirl to for generating fixtures during
tests.
For more information on DHHs opinion http://www.rubyinside.com/dhh-offended-by-rspec-debate-4610.html

Chapter Two - Testing Setup

18

Add rspec-rails to Gemfile


1
2
3
4
5

group
gem
gem
gem
end

:test, :development do
'factory_girl_rails'
'rspec-rails'
'spring-commands-rspec'

From Rails 4.1, spring is included by default, which is an amazing speed booster because it preloads
your enviroment for you, significantly reducing the time taken to run migrations, generators and
spec. By default it doesnt support RSpec, but the spring-commands-rspec gem sorts that our for us.
Well now stop Rails from auto generating view, helper, controller and routing specs. Whilst the
built in routing and controller specs can be useful, Ive found that they dont work so well with
namespaced routes and controllers which we will use later. As a result I found that correcting them
took longer than writing from scratch. We will now update the application initialiser to prevent it
from auto generating some specs when we use rails generators.
config/application.rb
1
2
3
4
5
6
7
8
9

module AddressBook
class Application < Rails::Application
config.generators do |g|
g.test_framework :rspec, fixtures: true, view_specs: false,
helper_specs: false, controller_specs: false, routing_specs: false
g.factory_girl true
end
end
end

Now we can bootstrap RSpec.


Bootstrapping RSpec
1
2

bundle install
rails g rspec:install

Well commit these changes now.

For more information on generators http://guides.rubyonrails.org/generators.html and http://railscasts.com/episodes/216-generators-in-rails-3

Chapter Two - Testing Setup

19

Committing changes
1
2

git add . -A
git commit -m 'Adding in RSpec'

Teaspoon
Teaspoon provides both a beautiful an elegant way interface for javascript testing on Rails, theres
also a plugin for Guard which means we can make automated testing very straightforward.
I particularly like keeping my unit tests separate from my integration tests. Teaspoon has the notation
of suites allowing us to make simple switches. We can use fixtures or interact with live data from
our server.
Another one of the niceties of Teaspoon is that QUnit, Jasmine and Mocha are shipped by default,
so no extra dependencies to manage.
So, lets get started with Teaspoon, by adding it to our Gemfile inside the :test, :development
group. Well also take the opportunity to add in Guard and optionally Growl.
Gemfile
1
2
3
4
5
6
7
8
9
10

group :test, :development do


# ...
gem 'growl' # Optional
gem 'guard-rspec'
gem 'guard-teaspoon', '0.0.4'
gem 'teaspoon', '0.7.9'
gem 'spring-commands-teaspoon'
gem "phantomjs", ">= 1.8.1.1" # this is optional if the phantomjs binary is ins\
talled on your system
end

We add in the support for spring commands with Teaspoon also to help with speeding up tests.
After running bundler well initialise Teaspoon.

Learn more about teaspoon here https://github.com/modeset/teaspoon


Growl is a really nice notifier application with lots of support and customisation. I really like that I can have my tests running in another window

and still get continuous feedback about how my tests are going. Its only available for Mac though. http://growl.info/

Chapter Two - Testing Setup

20

Initializing Teaspoon
1
2
3
4
5
6
7
8

rails generate teaspoon:install


create spec/teaspoon_env.rb
create spec/javascripts/support
create spec/javascripts/fixtures
create spec/javascripts/spec_helper.js
+============================================================================+
Congratulations! Teaspoon was successfully installed. Documentation and more
can be found at: https://github.com/modeset/teaspoon

By default Teaspoon initialises with Jasmine, so lets fix that up.


spec/teaspoon_env.rb
1
2
3
4
5
6
7
8
9
10

//......
Teaspoon.configure do |config|
config.suite do |suite|
suite.use_framework :qunit
suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee\
}"
suite.helper = "spec_helper"
suite.stylesheets = ["teaspoon"]
end
end

One issue that crops up when using Teaspoon with Rails 4.1 is that asset pipeline issues are set to
raise runtime errors in development mode. This causes issues with Teaspoon and can be resolved by
adjusting turning off the runtime error mode.
config/initializers/development.rb
1
2
3
4

Rails.application.configure do
//.....
config.assets.raise_runtime_errors = false
...

Now lets convert our application.js to application.js.erb to give us a nice place to turn on
and off Ember debugging flags.

Chapter Two - Testing Setup

21

app/assets/javascripts/application.js.erb
1
2
3
4
5
6

//=
//=
//=
//=
//=
//=

require jquery
require handlebars
require ember
require ember-data
require_self
require address_book

7
8

// for more details see: http://emberjs.com/guides/application/

9
10
11
12

<% if Rails.env.development? %>


Ember.LOG_TRANSITIONS = true;
<% end %>

13
14

AddressBook = Ember.Application.create();

15
16

//= require_tree .

We can now make changes in the development block that wont affect our site in production.
Next well add adjust the spec_helper.js to setup Ember for testing. Now theres a fair bit going
on here. First we require the main application.js.erb then we add two divs to allow a place for
our app to run.
Next we tell our app to run in the ember-testing div and use the setupForTesting() helper.
This stops the Ember app from going through the run loops, except when we use the wrapper
Ember.run(function() { some stuff to be done}) . We also injectTestHelpers() which gives
us access to a whole bunch of useful functions that we can use for testing.
New file - spec/javascripts/spec_helper.js
1
2

//= require application.js.erb


//= require_self

3
4
5

var d = document;
d.write('<div id="ember-testing-container"><div id="ember-testing"></div></div>');

6
7
8
9

AddressBook.rootElement = "#ember-testing";
AddressBook.setupForTesting();
AddressBook.injectTestHelpers();
More information on the run look and TDD here http://instructure.github.io/blog/2014/01/24/ember-run-loop-and-tdd/

Chapter Two - Testing Setup

22

In order to make our tests look a bit nicer when using web view well add a splash of CSS. The styling
comes from the Discourse project, one of the largest open source Ember apps in development.
New file - app/assets/stylesheets/teaspoon_custom.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14

#ember-testing-container {
position: absolute;
background: white;
width: 640px;
height: 384px;
overflow: auto;
z-index: 9999;
border: 1px
solid #ccc;
right: 50px;
top: 200px;
padding: 5px;
}
#ember-testing { zoom: 50%; }

Now lets tell Teaspoon to use this CSS.


spec/teaspoon_env.rb
1

suite.stylesheets = ["teaspoon", "teaspoon_custom"]

And now we will initalise the binstubs so that spring can help us out with running our tests quickly.
Generate binstubs
1

bundle exec spring binstub --all

Theres a fair bit of magic going on behind the scenes with this some of which is useful to know. The
first time you run rails after the binstubs are generated, spring fires up you app. Youll see a deloy
until the applicaiton is ready. Once spring is up, future commands will run more quickly. Spring
intelligently reloads any altered files in future, so youll find that any command run from here on
will get started much more quickly.

Guard
Before moving of to writing some actual tests (I know, its been a long time coming!), well install
Guard so we can get continuous feedback.
Because we added it to the bundle earlier, getting it running is really simple.
Discourse is pitched as a next generation discussion platform, aimed at making forums nice! Ive used it extensively and its just beautiful to use
and Ive learned massively by making small contributions to the development. Learn more here https://github.com/discourse/discourse/

Chapter Two - Testing Setup

23

Initalize Guard
1

guard init rspec teaspoon

Well update the Guardfile that gets generated to take advantage of what spring has to offer.
Guardfile
1
2
3

guard :rspec, cmd: 'spring rspec' do


//.......
guard :teaspoon, cmd: 'spring teaspoon' do

We should try Guard now and ensure all its working.


Trying Guard
1

guard

Assuming all is well you should see a Guard running RSpec and the three suites defined with
Teaspoon. There should be 0 examples and 0 failures at this stage. If not, its time to backtrack
and find out where the issue is.
If all was well, lets commit our changes now.
Commit changes
1
2

git add . -A
git commit -m 'Teaspoon and Guard'

Chapter Three - Routing Specs


One of the things that Ember gets so right is the assertion that the URL should be the source of truth.
Tom Dale gave a great talk about how front end development has a habit of breaking the web, in
particular around the back button.
Getting the concept of routes correct in your mind is part of the learning curve with Ember and the
way the map is somewhat different to Rails and can break your brain a bit when getting started!
Ember determines where to look for things based on the URL, as specified in the Router.
A Router was defined for us automatically when we bootstrapped Ember and it looks like this
app/assets/javascripts/router.js
1

AddressBook.Router.map(function() { });

At the moment this doesnt seem very impressive, but we will soon discover we actually got quite
a lot from that small declaration.
Lets go ahead and see what we can discover about the router by writing a spec.

QUnit Specs (or Are They Tests?)


The convention with QUnit tends to be calling the files something_test.js, Teaspoon, however,
expects something_spec.js, so well use that convention.
A spec is made up of a module declaration in which we can declare what is required for setup and
what to do after the specs have run. Lets create a routing spec.
New file - spec/javascripts/unit/routing/router_spec.js
1
2
3
4
5

module('Routing specs', {
setup: function () {
AddressBook.reset();
}
});
Stop breaking the web http://2013.jsconf.eu/speakers/tom-dale-stop-breaking-the-web.html

Chapter Three - Routing Specs

25

At the moment, this is mainly a placeholder, but the reset() call on our app in the setup is an
important step which will be called before each test is run - it ensures that our app is returned to
its initial state. I prefer to put this in the setup, rather than teardown as it makes for easier visual
debugging when running test in the browser as it leaves the output on screen. When you focus on
a single failing test, this presents the exact look of the failure for you.
Tests are then declared using a test declaration which taken a string as its name and an anonymous
function containing the actual test.
spec/javascripts/unit/routing/router_spec.js
1
2
3
4
5
6
7
8

test('root route', function () {


visit('/');
andThen(function () {
var current_route = AddressBook.__container__.lookup('controller:applicat\
ion').currentRouteName;
equal(current_route, '', 'Expected ****** got: ' + current_route);
});
});

If you run this spec now, youll see we have a failing test!
Spec output
1

Failures:

2
3
4

1) Routing specs root route (1, 0, 1)


Failure/Error: Expected ****** got: index

Lets commit those changes and then well dig into what weve just discovered.
Commit changes
1
2

git add .
git commit -m 'First failing spec'

26

Chapter Three - Routing Specs

Whats going on here?


Weve been introduced to some new methods here.
Name

Parameters

Explanation

visit(url)

URL as string

andThen(func)

Anonymous function

equal(thingUnderTest,
expectedResult, optional
error message)

thingUnderTest=Object, string,
anything really!, expectedResult=
speaks for itself, optional error
message can be a string, a
variable or a combination

Visits the specified URL and


return a promise
Ensures that any unfilled
promises succeed or fails then
executes the code in the
anonymous function
Tests for equality of
thingUnderTest and
expectedResult

So what weve done is visited the root route for our app and when the promise has been returned
weve started digging about in Embers innards!
We set a variable current_route for use in our test. Whilst setting a variable isnt strictly necessary
it gives us a nice construct for reporting on the outcome of the test.

Breaking it down
AddressBook = the name of our application
__container__ = an Ember namespace construct. NEVER use this in production, only use for

testing and exploring


lookup('controller:application') = find our ApplicationController, note that the wording is
reversed - controller, then application
currentRouteName = returns the name of the current route. This may be surprising to you if youre

familiar with rails routes, but more on that later.

.
Now in our failing test we see that currentRoute is index. This top level route is created for us
automatically by Ember. We now know how to make our test pass and can update it to provide a
more useful error message.

Martin Fowler explains promises very simply here http://martinfowler.com/bliki/JavascriptPromise.html. Chris Webb provides a much more
detailed explanation here http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt1-theory-and-semantics

Chapter Three - Routing Specs

27

spec/javascripts/unit/routing/router_spec.js
1
2
3
4
5
6
7
8

test('root route', function () {


visit('/');
andThen(function () {
var current_route = AddressBook.__container__.lookup('controller:applicat\
ion').currentRouteName;
equal(current_route, 'index', 'Expected index got: ' + current_route);
})
});

Now clearly this wasnt exactly a test first approach, but with this bit of exploration we know how
to lookup URLs and see how they are resolved.
Commiting changes
1
2

git add .
git commit -m 'Routing spec to green'

Creating a Contacts Route


Knowing what we do now, lets test drive creating a route for a collection of contacts.
spec/javascripts/unit/routing/router_spec.js
1
2
3
4
5
6
7
8

test('contacts route', function () {


visit('/contacts')
andThen(function () {
var current_route = AddressBook.__container__.lookup('controller:applicat\
ion').currentRouteName;
equal(current_route, 'contacts', 'Expected contacts got:' + current_route\
);
});

9
10

});

Well now have the following failure:

Chapter Three - Routing Specs

28

Routing failure
1

Failures:

2
3
4
5

1) Routing specs contacts route (1, 1, 2)


Failure/Error: Error: Assertion Failed: The URL '/contacts' did not match an\
y routes in your application

6
7
8

Finished in 0.10100 seconds


2 examples, 1 failure

Great, we now have failing spec and we now know that we need to define a contacts route.
app/assets/javascripts/router.js
1
2
3

AddressBook.Router.map(function() {
this.resource('contacts');
});

And with that we should now have a passing spec! That was very straightforward and is a testament
to the convention over configuration approach.
Commiting changes
1
2

git add .
git commit -m 'Create a contacts route in Ember'

And So a Problem Presents Itself


With the contacts route in place well create a template to render.
Handlebars templates should be placed in the app/assets/javascripts/templates folder and end
with either .hbs or .handlebars, i prefer the shorter version! The templates are pre-compiled into
javascript files and made available to your app.
Well create a test for what we expect to see in our template now. Lets create a new file for our
integration spec.

Chapter Three - Routing Specs

29

New file - spec/javascripts/integration/contacts_integrations_spec.js


1
2
3
4
5
6

module('Contacts integration', {
setup: function () {},
teardown: function () {
AddressBook.reset();
}
});

7
8
9
10
11
12
13
14
15

test('Contacts index page', function () {


visit('/contacts');
andThen(function () {
var header_text = find('.contacts_heading').text();
equal(header_text, "Now in the Contacts Index", 'Expected "Now in the Con\
tacts Index", got: ' + header_text );
});
});

Were introduced to a new helper here find(). The find helper takes a string which explains what
were looking for. Its usage is essentially the same as the jQuery find() API and allows us to
create complex lookups by nesting searches. For now though were simply going to look for a class
called contacts_heading.
app/assets/javascripts/templates/contacts/index.hbs
1

<h1 class="contacts_heading">Now in the Contacts Index</h1>

So we should have a passing test now, right? Sadly not. So, whats wrong? Well it turns out that when
we specified the route earlier, what we got was a route that looks for contacts.hbs. Now it wouldnt
be very sensible to keep all of our templates in one directory, so I created it in the contacts directory
and called it index.hbs. With the pre-compilation process it exists in Ember as contacts.index. We
dont have a route to contacts.index though, so how to create it?
Well I tricked you earlier with an incorrect use of the this.resource, I know, naughty hey! Bear
with me though and youll see why.
In order to fix this, we need to adjust the router.

Find the full list of valid jQuery selectors here http://api.jquery.com/category/selectors/

Chapter Three - Routing Specs

30

app/assets/javascripts/router.js
1
2
3
4

AddressBook.Router.map(function () {
this.resource('contacts', function () {
});
});

We pass in an anonymous function and our integration spec goes green. Uh Oh, now our router spec
is red!
Router spec error
1

Failures:

2
3
4

1) Routing specs contacts route (1, 0, 1)


Failure/Error: Expected contacts got: contacts.index

Perfect, that error tells us exactly what went wrong and shows the value of taking the time to specify
verbose errors. So we have now learned that this.resource('contacts') creates a simple route
called contacts, but when passed an anonymous function the route disappears and is replaced by
contacts.index. Essentially whats happened here is that Ember has assumed that when we passed
the anonymous function we plan to create a bunch of other nested routes. Clever Ember!
Lets update our spec to reflect reality now.
spec/javascripts/unit/routing/router_spec.js
1

equal(current_route, 'contacts.index', 'Expected contacts got:' + current_route);

OK great, weve now got a some routing specs and an integration spec, lets commit our changes.
Commiting changes
1
2

git add .
git commit -m 'First integration spec'

An Abstraction Trying to Get Out?


Observant programmers amongst you would have noticed that our routing spec gets a bit repetitive
and were going to use the pattern repeatedly, so lets do some light refactoring and create our own
test helper.

Chapter Three - Routing Specs

31

spec/javascripts/support/testing_helpers.js
1
2
3
4
5
6
7
8
9

var routesTo = function (url, route_name) {


visit(url);
andThen(function () {
var current_route = AddressBook.__container__.lookup('controller:applicat\
ion').currentRouteName;
equal(current_route, route_name, 'Expected ' + route_name + ', got: ' + c\
urrent_route);
});
};

This provides us with a really terse way of testing routes, tell it were we want to go and what route
we expect to see. Lets add it to the spec_helper.js
spec/javascripts/spec_helper.js
1

//= require support/testing_helpers

We can now refactor our router_spec


spec/javascripts/unit/routing/router_spec.js
1
2
3

test('root route', function () {


routesTo('/', 'index');
});

4
5
6
7

test('contacts route', function () {


routesTo('/contacts', 'contacts.index');
});

Lovely, doesnt that nice clean tidy code make you feel all warm inside??
Lets commit our changes and then well move on to putting some actual data on the page.
Commit changes
1
2

git add .
git commit -m ''

Chapter Four - Getting Started With


Data Rendering
Any exploration of Ember would be a total waste of time if we didnt use the data bindings! Data
bindings allow us to iterate over data, update it, and if we desire, persist it to a data store. With
bindings we only need to update data in one place, all other references to the data are then updated.
In order to do this well now explore Ember Models and routing to individual records.
Lets get started by writing a failing spec.
spec/javascripts/integration/contacts_integration_spec.js
1
2
3
4
5
6
7
8

test('Renders contacts', function () {


visit('/contacts');
andThen(function () {
var contacts_length = find('.contacts_list li').length;
equal(contacts_length, 2, "Expected contacts to contain 2 items, got: " +\
contacts_length);
});
});

Great, now weve got a failing spec, lets get on and get things working.
The first thing we will need in order to get this spec passing is a Route. Didnt we already create
a route I hear you ask? Well yes we did, but that was for routing not a route! Basically, when
you declare a route in the Router an Ember.Route is created for you automatically. The Route for
contacts.index is called ContactsIndexRoute, this is great if we dont want Route to do anything,
but if we want to create some functionality we will need to customise it.
New file - app/assets/javascripts/routes/contacts/contacts_index_route.js
1
2
3
4
5

AddressBook.ContactsIndexRoute = Ember.Route.extend({
model: function () {
return this.store.findAll('contact'); // Note that contact is singular
}
});

What weve done here is said that when we visit the contacts index, go to the application data store
and find all records for the contact data model and attach the result to a model property.

Chapter Four - Getting Started With Data Rendering

33

If we try to run our spec now well get the following error somewhere in the output. Like Rails,
when running specs the actual problem we need to fix can appear in the stack trace, rather than the
actual spec output.
Error - truncated
1
2
3
4
5

Error while loading route: Error: No model was found for 'contact'
at http://127.0.0.1:57169/assets/ember-data.js?body=1:3027
at http://127.0.0.1:57169/assets/ember-data.js?body=1:2629
at http://127.0.0.1:57169/assets/routes/contacts/contacts_index_route.js?body\
=1:3

Models
So we now know that there is no contact model available, so lets declare a model for storing our
contact information.
New file - app/assets/javascripts/models/contacts.js
1
2
3
4

AddressBook.Contact = DS.Model.extend({
first_name: DS.attr('string'),
last_name: DS.attr('string')
});

We can, if we choose simply declare DS.attr without specifying the type but, my preference is to
be explicit about what we are expecting.
Running our spec again we will see that we are getting Error while loading route: undefined.
That seems odd because we know that we have defined the route, so whats going on? When we
visit a route and ask for the model, Ember automatically makes a request to the server for the data.
In our case its going to hit http://localhost:3000/contacts. Anyone familiar with Rails will
quickly recognise that we havent created any of the Rails infrastructure necessary to support this
call so it responds with a 404 (not found) error. This failure results model call in the route to return
undefined and stalls the application.
Its not very useful to have the test suite hitting the server for data all the time anyway, so lets
ensure that we can get some data internally.

Chapter Four - Getting Started With Data Rendering

34

spec/javascripts/integration/contacts_integration_spec.js
1
2
3
4
5
6
7
8

module('Contacts integration', {
setup: function () {
AddressBook.ApplicationAdapter = DS.FixtureAdapter;
},
teardown: function () {
AddressBook.reset();
}
});

The Ember Fixture Adapter allows us to have pre specified data to use in our tests. This makes it
very easy for us to know what to test for.
Fixture error
1
2
3
4
5

Error while loading route: Error: Assertion Failed: Unable to find fixtures for m\
odel type AddressBook.Contact
at http://127.0.0.1:57169/assets/ember.js?body=1:81
at http://127.0.0.1:57169/assets/ember-data.js?body=1:8109
at _findAll (http://127.0.0.1:57169/assets/ember-data.js?body=1:3590)

Awesome, we are now told that there are no fixtures available for our Contact. Fixtures are specified
as a javascript Array of Objects.
spec/javascripts/integration/contacts_integration_spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

module('Contacts integration', {
setup: function () {
AddressBook.ApplicationAdapter = DS.FixtureAdapter;
AddressBook.Contact.FIXTURES = [
{
id: 1,
first_name: 'Dave',
last_name: 'Crack'
},
{
id: 2,
first_name: 'Dustin',
last_name: 'Hoffman'
}
]

Chapter Four - Getting Started With Data Rendering


16
17
18
19
20

35

},
teardown: function () {
AddressBook.reset();
}
});

Great, weve finally got a useful error, and if youre wondering about Dustin Hoffman and Dave
Crack, see the footnote!
Error message
1
2

1) Contacts integration Renders contacts (1, 0, 1)


Failure/Error: Expected contacts to contain 2 items, got: 0

We can now update our index template to render the data weve just setup.
app/assets/javascripts/templates/contacts/index.hbs
1
2
3
4
5
6

<h1> .... </h1>


<ul class="contacts_list">
{{#each}}
<li>First Name: {{first_name}}</li>
{{/each}}
</ul>

Here we create an unordered list and using the {{#each}}{{/each}} helper, asking Ember to create
a <li> element for each item in the array. Inside the iterator we ask for the first_name property to
be rendered. The first name is received from the model
With that we should now have a passing spec.
Commit time again!
Commit changes
1
2

git add .
git commit -m 'Contacts integration spec to green'

Unit Testing Our Model


When Im working with Rails I really like to use the RSpec construct for testing models:
Dave Crack is a London cab driver who was given mention by Dustin Hoffman as part of a BAFTA acceptance speech. I love the story because
its a great example of humility. http://www.zipadeeday.com/story/60/uk-dinner-lady-flabbergasted-by-hoffman-call/

Chapter Four - Getting Started With Data Rendering

36

RSpec example
1
2
3

subject { Contact.new }
it {should respond_to :some_property_name }
it {should respond_to :some_other_property }

This pattern gives me confidence that my model has the attributes I need to handle data. We can do
something similar with Ember.
New file - spec/javascripts/unit/models/contacts_spec.js
1
2
3
4
5
6

module('Contacts Model', {
setup: function () {},
teardown: function () {
AddressBook.reset();
}
});

7
8
9
10
11
12

test('Has a first_name property', function () {


var first_name = AddressBook.Contact.metaForProperty('first_name');
equal(first_name.type, 'string');
ok(first_name.isAttribute);
});

When we create a Model in Ember it is attached to the top level namespace, hence AddressBook.Contact.
With the metaForProperty('first_name') call we receive an object with metadata for first_name:
DS.attr('string') The subsequent calls check that it is an attribute and that the type is string.
I like to use this pattern frequently enough to create a helper which requires a spot of light
metaprogramming! Well also introduce the ok() test helper. ok() takes an argument that should
evaluate as a boolean and an optional second argument that is the error message.
spec/javascripts/support/testing_helpers.js
1
2
3
4
5
6

var respondsTo = function (model, attribute, type) {


var test_subject = AddressBook[model].metaForProperty(attribute);
equal(test_subject.type, type, 'Expected ' + type + " got: " + test_subject.t\
ype);
ok(test_subject.isAttribute);
};

We can then refactor our original to use the new helper and check the other property.

Chapter Four - Getting Started With Data Rendering

37

spec/javascripts/unit/models/contacts_spec.js
1
2
3
4

test('attributes', function () {
respondsTo('Contact', 'first_name', 'string');
respondsTo('Contact', 'last_name', 'string');
});

Now there are many argument for and against this pattern in testing. Many would argue that its
brittle and unnecessary. I can definitely see this perspective, particularly for a seasoned developer,
but I believe that whilst learning the workings of the framework declaring what were looking
for before we implement it gives us great insight into whats going on internally. Later when we
encounter the errors in a less controlled manner we can have confidence that we know what to do.
As your confidence grows you can rely less on this kind of pattern.
Obviously we didnt test the Model first in this chapter, but we will in future.
Well commit our changes now and in the next chapter well look at nested routes.
Committing changes
1
2

git add .
git commit -m 'Model testing and helper'

An Unexpected Problem
In defining the ContactsIndexRoute weve unwittingly introduced a problem into the test suite.
The problem is that if we run the routing spec on its own we will now get a failure reporting
Failure/Error: Expected contacts.index, got: index. The reason for this is that now we
have declared the ContactsIndexRoute the first thing we ask it to do is to retrieve all contacts
from the server. We dont have anything setup in Rails at this stage so the server returns a
404 - Not Found error, which means we cant transition to contacts.index. This happens if we
try to move from / to /contacts. If we try to hit /contacts without first hitting / we get
a different error Failure/Error: TypeError: 'undefined' is not an object (evaluating
'AddressBook.__container__.lookup('controller:application').currentRouteName').
Both of these problems can be resolved by moving the FixtureAdapter to our initial test helper so
that the real server is never hit during testing.
Lets do that now.

Chapter Four - Getting Started With Data Rendering

spec/javascripts/spec_helper.js
1
2
3
4
5
6
7
8
9
10
11
12
13

AddressBook.ApplicationAdapter = DS.FixtureAdapter;
AddressBook.Contact.FIXTURES = [
{
id: 1,
first_name: 'Dave',
last_name: 'Crack'
},
{
id: 2,
first_name: 'Dustin',
last_name: 'Hoffman'
}
];

We can now remove this from the contacts_integration_spec.js.


Commit changes
1
2

git add .
git commit -m 'Refactor FixtureAdapter'

38

Chapter Five - Nested Routes


So far weve worked with only index level routes, but what if we want to look at an individual item?
Well there are a number of ways we can do this and as yet, there hasnt been a golden path agreed
by the Ember team., so this section may change in the future.
As we saw earlier in our router we can use the this.resource construct, which allows us to nest
routes into logical groupings. Lets consider what we need for a moment.
Name

What we want

contacts
contacts/:contact_id
contacts/new

A listing of all our contacts


A view of a single contact
A form to create a new contact

With this in mind we can now define our nested routes and I will use the Rails style of index, show,
new etc.
Lets start with a failing spec.
spec/javascripts/unit/routing/router_spec.js
1
2
3
4

//....
test('individual contact', function () {
routesTo('/contacts/1', 'contacts.show');
});

This should give us an error similar to this:


Error message
1
2
3

1) Routing specs individual contact (2, 0, 2)


Failure/Error: Error: Assertion Failed: The URL '/contacts/1' did not match \
any routes in your application

4
5
6
7

2) Routing specs individual contact (2, 0, 2)


Failure/Error: TypeError: 'undefined' is not an object (evaluating 'AddressB\
ook.__container__.lookup('controller:application').currentRouteName')

Great, no route matches, so lets define one by updating our router.


A fuller discussion on the topic is available here http://emberjs.com/guides/routing/defining-your-routes/, with some supplementary opinions
here http://hashrocket.com/blog/posts/ember-routing-the-when-and-why-of-nesting

40

Chapter Five - Nested Routes

app/assets/javascripts/router.js
1
2
3
4
5

AddressBook.Router.map(function () {
this.resource('contacts', function () {
this.route('show', {path: '/:contact_id'});
});
});

Our spec should now be green. Lets consider what weve just done in a bit more detail.

Defining Routes Within Resources


Construct

Use case

Notes

this.resource('name',
function () {})

Creates a namespaced route in


which other routes can be nested

this.route('names', {path:
'/:segment_id'})

Define a visitable route and can


be created top level or nested
within a resource

Ember guide recommends using


resource for URLs that represent
a noun or a class of thing e.g.
Contacts, Posts
The path option allows us to take
in a parameter by using :

In our example we get a route called contacts.show which takes the final URL segment as a
parameter which gets bound to contact_id which we can use in our controller for looking up the
record.
We will now create a route for an individual record and an associated template.
New file - /app/assets/javascripts/routes/contacts/contacts_show_route.js
1
2
3
4
5

AddressBook.ContactsShowRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('contact', params.contact_id);
}
});

The main difference here between our ContactsShowRoute and the the ContactsIndexRoute is that
the model only asks for one contact and does so via the params.contact_id. contact_id got defined
in our router via {path: '/:contact_id'}.

Chapter Five - Nested Routes

41

Rendering the Individual Contact


If we want to render the individual contact we will need a template, so we will write a failing
integration test.
spec/javascripts/integration/contacts_integration_spec.js
1
2
3
4
5
6
7
8
9
10

//....
test('Renders only one contact', function () {
visit('/contacts/1');
andThen(function () {
var contact = find('#contact h1').text();
var expected_result = 'Details for Contact 1';
equal(contact, expected_result, 'Expected: ' + expected_result + ' got: '\
+ contact);
});
});

With this test we will visit the newly defined route and the use the find helper to find an html
element with the id of contact and collect the text. We expect that this text is Details for Contact
1.
We can now create the template.
New file - app/assets/javascripts/templates/contacts/show.hbs
1
2
3
4
5
6
7

<div id="contact">
<h1>Details for Contact {{id}}</h1>
<ul>
<li>First Name: {{first_name}}</li>
<li>Last Name: {{last_name}}</li>
</ul>
</div>

Here we are using the id , first_name and last_name attributes in our template.
And with that our tests suite should be green.

Refactor Tests
Lets remove the call to AddressBook.reset() from all of our tests (router_spec.js , contacts_spec.js , contacts_integration_spec.js) and keep it in the spec_helper.js now. This removes
duplication and ensures that we dont forget to put it in anywhere.

42

Chapter Five - Nested Routes

spec/javascripts/spec_helper.js
1
2

AddressBook.injectTestHelpers();
# ....

3
4
5
6

QUnit.testStart = function () {
AddressBook.reset();
};

The change of reseting the app before each test rather than afterwards gives us an opportunity to
have a look at the application state in the browser. Lets see how that is done.

An Alternate Way of Viewing Our Specs


So far weve only relied on the command line interface for viewing the outcome of our specs, but
what does our app actually look like?? Teaspoon comes with a very pretty web interface for running
the suite. We can view it view the standard rails server by visiting http://localhost:3000/teaspoon.
Have a look now and you should see something like this.

Teaspoon web interface

This is kinda neat! The CSS we set up earlier is a trick that allows us to render the results of the tests
in the bottom right hand corner of the screen. These are not screenshots, but our actual application.
You can go ahead and click around in there are navigate your app in the test environment.

Chapter Five - Nested Routes

43

This is particularly useful because we will see the results of a failing spec here and can really visualise
whats going on. I prefer to rely mostly on the command line approach and move to the web interface
if I just cant figure out why things are going wrong.

Navigating Around the App


Knowing that we can directly visit contact number 1 is nice, but wouldnt it be better if our users
could click on a name in their index and move to the details page?
Lets create a spec for that!
spec/javascripts/integration/contacts_integration_spec.js
1
2
3
4
5
6
7
8
9
10

//..
test('Visiting a contact via the index screen', function () {
visit('/contacts').click('ul li:last a');
andThen(function () {
var contact = find('#contact h1').text();
var expected_result = 'Details for Contact 2';
equal(contact, expected_result, 'Expected: ' + expected_result + ' got: '\
+ contact);
});
});

Here we learn some other new tricks! The visit() helper can be chained, so we visit the index route,
the we chain the click() helper. By nesting our search we can ensure that we click the last link in
the list and not the first.
find ul -> then find last li element -> then find the a tag -> then click it!

Once weve clicked the link we would expect to be on the details page for contact 2 this time.
I like to change up the details I check (i.e. contact 2 this time, not 1) for in this manner as it helps to
detect any issues I may have not picked up otherwise.
With that, we should have a failing spec similar to this

Chapter Five - Nested Routes

44

Failure
1
2

1) Contacts integration Visiting a contact via the index screen (1, 0, 1)


Failure/Error: Error: Element ul li:last a not found.

We can use the link-to helper now to get everything working.


/app/assets/javascripts/templates/contacts/index.hbs
1
2
3
4
5

<ul class="contacts_list">
{{#each}}
<li>{{#link-to 'contacts.show' this}}{{first_name}}{{/link-to}}</li>
{{/each}}
</ul>

With this helper we are asking Ember to go to the contact.show route which is passed as a string
and to use this for the parameter. Each time the helper is iterated over this represents the record /
object being rendered. Ember is able to use the record id to build the correct URL.
And with that, were back to green.
Time for a commit before we switch things over to Rails to get an idea about the infrastructure
needed to allow persistence in our app.
Commiting changes
1
2

git add .
git commit -m 'Individual contacts and specs'

Chapter Six - Persisting Data With


Rails
Theres been a lot of controversy with Ember Data. Yehuda himself discussed the problems with
their initial implementation of Ember Data. Most of the issues there have been ironed out now as
a result of a major rewrite. Its looking very good these days and provides a lot of functionality. We
will use this later, but for now we will focus on getting the backend set up.

Setting Up the Backend


Weve managed to get to Chapter 6 before writing any real Rails code! We are going to go through
this section pretty quickly because the focus of the book is on Ember, not Rails.
I personally avoid using generators wherever possible as Ive alluded to earlier, but for the purpose
we have here they will serve us well.
Generating Contact Model
1

rails g model Contact first_name:string last_name:string

We should get a few files generated


Files generated
1
2
3
4
5
6
7

invoke
create
create
invoke
create
invoke
create

active_record
db/migrate/20140204004246_create_contacts.rb
app/models/contact.rb
rspec
spec/models/contact_spec.rb
factory_girl
spec/factories/contacts.rb

Well update the spec now.

Yehuda discussed in detail the problems in developing Ember Data on the Ember Hot Seat podcast (From about 41 minutes onwards). http:
//emberhotseat.com/2013/07/19/ember-hot-seat-episode-007.html

Chapter Six - Persisting Data With Rails

46

spec/models/contact_spec.rb
1

require 'spec_helper'

2
3
4

describe Contact do
let(:contact) { FactoryGirl.build_stubbed(:contact) }

5
6

subject { contact }

7
8
9
10

it { should respond_to :first_name }


it { should respond_to :last_name }
end

In this test we use the #build_stubbed method. I particularly like this pattern because it generates
an instance of the model, but without any persistence. This is great because it keeps our tests really
fast because the database doesnt get hit. We can already feel confident that the persistence layer of
Rails is very well tested, so theres no need to double up.
Were testing here that our model has first_name and last_name properties. If we run the spec now
well get a failure because we havent migrated the database.
Lets fix that.
Migrate
1

rake db:migrate && rake db:test:prepare

Well need a controller and a route to pass our data back and forth.
We will use a namespaced controller which will allow us to create our API in a partitioned way.
This, in my opinion, is good for our ability to swap things out in the future.
New file - spec/controllers/api/v1/contacts_controller_spec.rb
1

require 'spec_helper'

2
3

describe Api::V1::ContactsController do

4
5

end

And with that we should have a failing spec that looks similar to this.

Chapter Six - Persisting Data With Rails

47

Contacts failing spec


1
2

spec/controllers/api/v1/contacts_controller_spec.rb:3:in `<top (required)>': unin\


itialized constant Api::V1::ContactsController (NameError)

This is easily resolved.


New file - app/controllers/api/v1/contacts_controller.rb
1

class Api::V1::ContactsController < ApplicationController

2
3

end

Great, so now we want to return JSON representing all of our contacts.


spec/controllers/api/v1/contacts_controller_spec.rb
1

# ...

2
3
4
5
6
7
8
9
10

describe Api::V1::ContactsController do
describe 'GET methods' do
it 'index' do
@contacts = FactoryGirl.create_list(:contact, 2)
get :index
end
end
end

We ask FactoryGirl to create (and persist) two contacts so we have some data to work with and to
try to GET the index method on our controller. Well get a failing spec similar to this
Failing spec
1
2
3
4

1) Api::V1::ContactsController GET methods index


Failure/Error: get :index
ActionController::UrlGenerationError:
No route matches {:action=>"index", :controller=>"api/v1/contacts"}

We can define the route now.

Chapter Six - Persisting Data With Rails

48

config/routes.rb
1

# ......

2
3
4
5
6
7
8

get 'assets/index'
namespace :api do
namespace :v1 do
resources :contacts
end
end

And create an index method in our controller.


app/controllers/api/v1/contacts_controller.rb
1
2
3
4

class Api::V1::ContactsController < ApplicationController


def index
end
end

Our error will be similar to this.


Failing spec
1
2
3
4
5
6
7
8

1) Api::V1::ContactsController GET methods index


Failure/Error: get :index
ActionView::MissingTemplate:
Missing template api/v1/contacts/index, application/index with {:locale=>[\
:en], :formats=>[:html], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder]}. Se\
arched in:
* "#<RSpec::Rails::ViewRendering::EmptyTemplatePathSetDecorator:0x007fe1\
fa991ce8>"

This is one of my personal frustrations with controller testing in Rails - the errors you get when a
view isnt defined. There are two ways we can fix this, we can create views/api/v1/index.html.erb
as an empty file or we can update our controller to render nothing. I prefer the second approach
because we are not actually going to be creating standard views.

Chapter Six - Persisting Data With Rails

49

/app/controllers/api/v1/contacts_controller.rb
1
2
3

def index
render json: nil
end

Our specs should now be passing. Simply rendering nothing isnt particularly useful though, so next
lets expect some data.
spec/controllers/api/v1/contacts_controller_spec.rb
1
2

get :index
assigns(:contacts).length.should == 2

Here we are expecting an instance variable called contacts with a length of 2.


The simplest step to get our spec to green is this.
/app/controllers/api/v1/contacts_controller.rb
1
2
3
4

def index
@contacts = [1,2]
render json: @contacts
end

Great, our spec is green again. Next we should get it passing some real data.
spec/controllers/api/v1/contacts_controller_spec.rb
1
2
3

get :index
assigns(:contacts).length.should == 2
assigns(:contacts)[0].class.should == Contact

Now we want to make sure that the first element in our @contacts is an instance of the Contact
class.

Chapter Six - Persisting Data With Rails

50

/app/controllers/api/v1/contacts_controller.rb
1
2
3
4

def index
@contacts = Contact.all
render json: @contacts
end

Beautiful, specs are green again.


Time to commit again.
Commiting changes
1
2

git add .
git commit -m 'Build Rails API for Contacts'

Serializing the Data


In order to pass data to Ember, it is expected in a particular format. Luckily ActiveModelSerializer
is included with Ember-Rails and makes this pretty trivial.
New file - /app/serializers/contact_serializer.rb
1
2
3

class ContactSerializer < ActiveModel::Serializer


attributes :id, :first_name, :last_name
end

We define a serialiser which intercepts our @contacts from the controller and adjusts the output
format before passing it back to the render json: call.
The serialiser we just created trims the output to only include the id, first_name and last_name.
Timestamps are excluded.
Below are before and after examples of the differences in JSON formatting just so you can get your
mind around it.

Chapter Six - Persisting Data With Rails

51

Before ActiveModelSerializer is created


1
2
3
4

{"contacts":[{"contacts":{"id":1,"first_name":"Dave","last_name":"Crack","created\
_at":"2014-02-04T02:16:38.491Z","updated_at":"2014-02-04T02:16:38.491Z"}},{"conta\
cts":{"id":2,"first_name":"Dustin","last_name":"Hoffman","created_at":"2014-02-04\
T02:16:54.229Z","updated_at":"2014-02-04T02:16:54.229Z"}}]}

After creation
1
2

{"contacts":[{"id":1,"first_name":"Dave","last_name":"Crack"},{"id":2,"first_name\
":"Dustin","last_name":"Hoffman"}]}

Apart from the obvious removal of the timestamps, you can also see that the root contacts is gone.
All of the contacts are now part of one array and has a clean and legible structure.
We can also create a big of sample data to show in our app.
Rails console
1

rails console

This will give us an interactive console. Enter the following.


Entering data in the console
1
2
3

Contact.create(first_name: 'Dave', last_name: 'Crack')


Contact.create(first_name: 'Dustin', last_name: 'Hoffman')
exit

Great, were now ready to connect.

Connecting the Backend to the Frontend!


Weve done a lot of infrastructure building, but nothing much in the way of actually looking at our
app! This has been very deliberate because I want to emphasise the test driving approach. We are
now in a position to link our fronted and backend in just a couple of lines of code.

Chapter Six - Persisting Data With Rails

52

/app/assets/javascripts/store.js
1
2
3
4
5
6

AddressBook.Store = DS.Store.extend({
adapter: '-active-model'
});
DS.RESTAdapter.reopen(
{namespace: "api/v1"}
);

Here were telling Ember to use the DS.ActiveModelSerializer for our data. We also tell the
DS.RESTAdapter that all of our JSON requests should be prefixed with api/v1. In this way should
we ever wish to change our API we simply updated to a new version.
You will now be able to move around your app and navigate between contacts.
Time to commit again
Committing changes
1
2

git add .
git commit -m 'Connecting the frontend to the backend'

More Routes Necessary


All is working well in our fronted for now, but if we visit localhost:3000/#/contacts/1 without
first hitting the index, were going to have a problem. The reason for this is that we dont have a
Rails controller action to return a single Contact only a collection.
Failing spec time again!
spec/controllers/api/v1/contacts_controller_spec.rb
1
2
3
4
5
6
7
8

describe 'GET methods' do


#......
it 'show' do
@contact = FactoryGirl.create(:contact)
get :show, id: @contact.id
assigns(:contact).first_name.should == 'MyString'
end
end

We can now add our show method to the controller

Chapter Six - Persisting Data With Rails

53

/app/controllers/api/v1/contacts_controller.rb
1
2
3
4
5

#..
def show
@contact = Contact.find(params[:id])
render json: @contact
end

Specs to green, but lets refactor our controller and take advantage of StrongParameters.
/app/controllers/api/v1/contacts_controller.rb
1
2
3
4
5

class Api::V1::ContactsController < ApplicationController


before_action :set_contact, only: [:show]
def index
render json: @contacts = Contact.all
end

6
7
8
9

def show
render json: @contact
end

10
11

private

12
13
14
15

def set_contact
@contact = Contact.find(params[:id])
end

16
17

end

Awesome, thats nice and neat and in keeping with the patterns promoted by StrongParameters.
Commit changes
1
2

git add .
git commit -m 'Show single contact'
For more information on StrongParameters see https://github.com/rails/strong_parameters and http://guides.rubyonrails.org/action_controller_
overview.html#strong-parameters

Chapter Six - Persisting Data With Rails

54

Adding Contacts
So far weve only dealt with data weve pushed directly into the store, we will now look at how to
add a contact of our own.
A reasonable pattern for this to have a button for adding which changes the view to present us with
an input field, a save button and a cancel button.
Lets start with an integration spec for showing an input field.
spec/javascripts/integration/contacts_integration_spec.js
1
2
3
4
5
6
7
8

//..
test('Show input for new contact', function () {
visit('/contacts').click('#add_new_contact');
andThen(function () {
var input_field = find('#new_first_name').length;
ok(input_field == 1, 'Input field not found');
});
});

So we expect that when we visit /contacts and click the button with id add_new_contact then we
will see an input field with an id of new_first_name.
/app/assets/javascripts/templates/contacts/index.hbs
1
2
3

<h1 class="contacts_heading">Now in the Contacts Index</h1>


<button id="add_new_contact"></button>
<ul class="contacts_list">

And with that we have the following failure.


Failure message
1

Failures:

2
3
4

1) Contacts integration Show input for new contact (1, 0, 1)


Failure/Error: Input field not found

So how do we go about fixing this up? Firstly we will need to create a controller for our contacts
route which will give us a place to hang actions from.

Chapter Six - Persisting Data With Rails

55

New file - /app/assets/javascripts/controllers/contacts_index_controller.js


1
2
3

AddressBook.ContactsIndexController = Ember.ArrayController.extend({
actions: {
addNewContact: function () {

6
7

});

ContactsIndexController means that this controller is associated with the ContactsIndexRoute


and will be available when we are visiting the /contact URL. We can defined custom attributes and
functions in this object to add functionality. Defining an actions object allows us to bind events
such as click events to a specific action.

We will add the action to the template now and then define the action required to get the spec
passing.
/app/assets/javascripts/templates/contacts/index.hbs:1
1
2
3

<h1 class="contacts_heading">Now in the Contacts Index</h1>


<button id="add_new_contact" {{action 'addNewContact'}}></button>
<ul class="contacts_list">

Our spec at this point will still be failing because although we have a button, clicking it doesnt do
anything just yet.
We should define an action to occur on click now.
/app/assets/javascripts/controllers/contacts_index_controller.js
1
2
3
4

actions: {
addNewContact: function () {
this.toggleProperty('addingNewContact');
}

We will now use a conditional in our template to show the input field based on whether
addingNewContact is true or not.

Chapter Six - Persisting Data With Rails

56

/app/assets/javascripts/templates/contacts/index.hbs
1
2
3
4
5
6
7

{{#if addingNewContact}}
<label for="new_first_name">First name</label>
{{input type='text' value=controller.new_first_name id='new_first_name'}}
{{else}}
<button id="add_new_contact" {{action 'addNewContact'}} >Add new contact</but\
ton>
{{/if}}

With this update we show the button if we are not editing and if we are, then we hide the button and
show a the new_first_name input field. We use the ember input helper to create a text input field with
the correct id. We also use value to bind the input to a controller property called new_first_name.
This does not need to be declared ahead of time, Ember just takes care of it for us.
And with that our spec should be green.
Well add a bit more now, because most people have a first and a last name! With that in mind, lets
update the spec we wrote.
spec/javascripts/integration/contacts_integration_spec.js
1
2
3
4
5
6
7

//.....
andThen(function () {
var first_name_field = find('#new_first_name').length;
var last_name_field = find('#new_last_name').length;
ok(first_name_field == 1, 'First name field not found');
ok(last_name_field == 1, 'Last name field not found');
});

Now you know how to get this spec passing!


/app/assets/javascripts/templates/contacts/index.hbs
1
2
3
4
5

{{#if addingNewContact}}
<label for="new_first_name">First name</label>
{{input type='text' value=controller.new_first_name id='new_first_name'}}
<label for="new_last_name">Last name</label>
{{input type='text' value=controller.new_last_name id='new_last_name'}}

We should now handle the case for saving our contact.

Chapter Six - Persisting Data With Rails

spec/javascripts/integration/contacts_integration_spec.js
1
2
3
4
5
6
7
8
9
10
11

//.....
test('Adding a new contact', function () {
visit('/contacts').click('#add_new_contact');
fillIn('#new_first_name', 'Buzz');
fillIn('#new_last_name', 'Lightyear');
click('#save_new_contact');
andThen(function () {
var first_name = find('.contacts_list:contains("Buzz")').length;
ok(first_name == 1, "First name was not saved");
});
});

Lets get started with updating the template by adding the save button.
/app/assets/javascripts/templates/contacts/index.hbs
1
2
3
4
5

{{#if addingNewContact}}
//....
{{input type='text' value=controller.new_last_name id='new_last_name'}}
<button id="save_new_contact" {{action 'saveNewContact'}} >Save new contact</butt\
on>

Our failing spec is now tell us what to do.


Failing spec
1

Failures:

2
3
4
5
6

1) Contacts integration Adding a new contact (1, 0, 1)


Failure/Error: Error: Nothing handled the action 'saveNewContact'. If you di\
d handle the action, this error can be caused by returning true from an action ha\
ndler in a controller, causing the action to bubble.

We need to define the saveNewContact function.

57

Chapter Six - Persisting Data With Rails

58

/app/assets/javascripts/controllers/contacts_index_controller.js
1
2
3
4
5
6

actions: {
//...
},
saveNewContact: function () {
var new_first_name = this.get('new_first_name');
var new_last_name = this.get('new_last_name');

var new_contact = this.store.createRecord('contact',{


first_name: new_first_name,
last_name: new_last_name
});

8
9
10
11
12

new_contact.save();

13

14

Again, our spec is green, but it would be great if the text fields were cleared and we were returned
to the initial view with an add button.
spec/javascripts/integration/contacts_integration_spec.js
1

//....
ok(first_name == 1, "First name was not saved");
var add_new_contact_button = find('#add_new_contact').length;
ok(add_new_contact_button == 1, "Have not transitioned back to original s\

2
3
4
5

tate");

This new expectation test to ensure that we can see the add new contact button again.
When we call the save() function Ember returns a promise. We can respond to that with .then()
which takes two optional arguments, the first to handle success and the second to handle failure.
Lets use that to go back to the original view if we succeed and to show an alert if it fails.

Chapter Six - Persisting Data With Rails

59

/app/assets/javascripts/controllers/contacts_index_controller.js
1
2
3
4
5
6
7
8
9
10
11

saveNewContact: function () {
//..
var self = this;
new_contact.save().then(
function ( {
self.set('new_first_name', '');
self.set('new_last_name', '');
self.toggleProperty('addingNewContact');
},
function () {alert('Unable to save record'); });
}

With the promise pattern its important to know that when the promise returns, this may
no longer be this, it may be another this! In order to prevent any issues we explicitly
set a reference to the current context by var self = this;. In the success portion of our
promise, we call the .toggleProperty on self.

The two this.set calls, these arent strictly necessary, but I think its nice if when a user later clicks
add that they start with a blank slate :)
Spec is now green, which is great, but if we want to actually save the data in our backend, well
need to create a create method on our controller in Rails.
As always, we will starting with a failing spec!
spec/controllers/api/v1/contacts_controller_spec.rb
1
2
3
4
5
6
7
8

describe Api::V1::ContactsController do
#......
describe 'POST methods' do
it 'creates a new contact' do
@contact = FactoryGirl.attributes_for(:contact)
expect{post :create, contact: @contact}.to change(Contact, :count).by(1)
end
end

Here we use the FactoryGirl method #attributes_for to get a hash of attributes. When we call post
:create we are sending a HTTP POST request and with contact: @contact we are sending the
attributes as JSON. We wrap the call in an expect block and tell it we that Contact.count should
change by 1.

Chapter Six - Persisting Data With Rails

60

/app/controllers/api/v1/contacts_controller.rb
1
2
3
4
5
6
7
8
9

#.....
def create
@contact = Contact.new(get_contact_params)
if @contact.save
render json: @contact
else
render json: @contact.errors, status: :unprocessable_entity
end
end

10
11
12
13
14
15

private
#....
def get_contact_params
params.require(:contact).permit([:first_name, :last_name])
end

For those not familiar with the StrongParameters pattern, in get_contact_params we state that we
require the POST request to have a contact, and that on that contact we will permit a first_name
and a last_name. Anything else will be discarded. This pattern helps us to keep malicious code out
of our system. Perhaps, for example a user model has an admin property, if we dont protect this a
malicious user could send a request to set the admin flag to true, giving them unauthorized privileges
on the site. With StrongParams, an unauthorized admin flag would be discarded.
And with this our specs should again be green.
Lets commit
Commit changes
1
2

git add.
git commit -m 'Enable saving of contact'

There are a couple more things that we will do before we wrap up this chapter, allowing users to
cancel creation of a contact and deleting an existing contact.
Well start with cancelling.

Chapter Six - Persisting Data With Rails

61

spec/javascripts/integration/contacts_integration_spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13

test('Cancelling creation of new contact', function () {


visit('/contacts').click('#add_new_contact');
andThen(function () {
var first_name_field = find('#new_first_name').length;
ok(first_name_field == 1, 'First name field not found');
click('#cancel_new_contact');
andThen(function () {
var add_new_contact_button = find('#add_new_contact').length;
ok(add_new_contact_button == 1, "Have not transitioned back to origin\
al state");
});
});
});

Here we transition to the add_new_contact view, check that we are there. We then click the cancel
button and check we have return to the original view. As can be see here, we can nest the andThen
calls.
We should update the template now.
/app/assets/javascripts/templates/contacts/index.hbs
1
2
3
4
5

<!-- =-->
<button id="save_new_contact" {{action 'saveNewContact'}} >Save new contact</butt\
on>
<button id="cancel_new_contact"{{action 'cancelNewContact'}}>Cancel new contact</\
button>

This pattern should look pretty familiar now. Our spec will now complain that Nothing handled
the action 'cancelNewContact', and im sure you know now how to fix that up.
/app/assets/javascripts/controllers/contacts_index_controller.js
1
2
3
4
5
6
7

//.....
},
cancelNewContact: function () {
this.set('new_first_name', '');
this.set('new_last_name', '');
this.toggleProperty('addingNewContact');
}

Chapter Six - Persisting Data With Rails

62

Deleting a Contact
The final thing well do in this chapter is add the ability to delete a contact from our list.
Failing spec time.
spec/javascripts/integration/contacts_integration_spec.js
1
2
3
4
5
6
7
8

//....
test('Deleting a contact', function () {
visit('/contacts').click('.contacts_list li:first .delete_button');
andThen(function () {
var contacts = find('ul li').length;
ok(contacts == 1, "Exepcted 1 contact got: " + contacts);
});
});

So we visit contacts, click on the first delete button and then expect our list of contacts to shrink to
1.
/app/assets/javascripts/templates/contacts/index.hbs
1
2
3
4
5
6
7

{{#each}}
<li>
{{#link-to 'contacts.show' this}}{{first_name}}{{/link-to}}
<button {{action 'deleteContact' this}} class="delete_button">Delete \
contact</button>
</li>
{{/each}}

The only real difference with this call is that when we specify the deleteContact action we also
pass in this, which makes the current object available in the controller.
We can use the Ember Datas destroyRecord function to remove the record from the store and send
a delete request to the server.

Chapter Six - Persisting Data With Rails

63

/app/assets/javascripts/controllers/contacts_index_controller.js
1
2
3
4
5

//....
},
deleteContact: function (contact) {
contact.destroyRecord();
}

contact is received from the template and we simply make the correct function call on it.

At this point youll notice that our spec is still failing and the reason is our FIXTURES dont get
reset between tests. When we added our Buzz Light year, we upped the number to 3, and with the
delete its down to 2. Grr!
In order to resolve this issue, it would be good to know that we start each integration spec with a
clean data set. Well do a spot of refactoring now
spec/javascripts/spec_helper.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14

var resetFixtures = function () {


AddressBook.Contact.FIXTURES = [
{
id: 1,
first_name: 'Dave',
last_name: 'Crack'
},
{
id: 2,
first_name: 'Dustin',
last_name: 'Hoffman'
}
];
};

15
16

resetFixtures();

We are now wrapping the setup of the FIXTURES in a function and immediately calling the function.
This means our other tests are not disrupted, but we can now reliable return our data to baseline in
the integration specs. Lets do that now.

Chapter Six - Persisting Data With Rails

64

spec/javascripts/integration/contacts_integration_spec.js
1
2
3

teardown: function () {
resetFixtures();
}

Great, all green again.


We will of course now need to define a #delete method in rails.
spec/controllers/api/v1/contacts_controller_spec.rb
1
2
3
4
5
6
7
8

describe Api::V1::ContactsController do
#..
describe 'DELETE method' do
it 'deletes a contact' do
@contact = FactoryGirl.create(:contact)
expect{delete :destroy, id: @contact.id}.to change(Contact, :count).by(-1)
end
end

In order to get this passing well need to update our controller.


/app/controllers/api/v1/contacts_controller.rb
1
2

class Api::V1::ContactsController < ApplicationController


before_action :set_contact, only: [:show, :destroy]

3
4

#....

5
6
7
8
9
10
11
12

def destroy
if @contact.destroy
render json: nil
else
render json: @contact.errors, status: :unprocessable_entity
end
end

All is well on our specs, but well still have a problem if we try to delete a contact in our real app
and this is due to Cross Site Request Forgery protections that Rails adds by default. We will solve
this the Ember Appkit Rails way.
Ember Appkit Rails is a great project aimed at helping you get up and running with Ember and Rails by setting sensible defaults and providing
you with access to generators. https://github.com/dockyard/ember-appkit-rails/

Chapter Six - Persisting Data With Rails

65

/app/assets/javascripts/application.js.erb
1
2

//= require address_book


//= require csrf

/app/assets/javascripts/csrf.js
1
2
3
4
5
6
7
8
9

$.ajaxPrefilter(function(options, originalOptions, jqXHR) {


var token;
if (!options.crossDomain) {
token = $('meta[name="csrf-token"]').attr('content');
if (token) {
return jqXHR.setRequestHeader('X-CSRF-Token', token);
}
}
});

This takes the CSRF token from the page header and adds it to all of our ajax requests. And with
that, we should be able to add and delete contacts.
Time for a commit
Commit changes
1
2

git add .
git commit -m 'Add and remove contacts'

Chapter 7 - Relationships in Ember


Relationships are hard, but not in Ember! Relationships allow us to describe how our various models
relate to each. This is a very common pattern in Rails with the has_many and belongs_to helpers
being two of the types available.
There are fewer types available in Ember, but they are no less powerful when we put them to use.
Weve got our basic contact set up with a first and last name, but it would be good to add email
addresses and telephone numbers. Lets start with email addresses.
There area number of things well need to do in this section.

Ember model for email addresses


Rails model for email addresses
Contact -> email relationship in Ember
Contact -> email relationship in Rails
A way of rendering the email addresses in the contact show template

A good place would be to start with a high level failing integration spec.
Whilst this could quite easily be added to the contacts_integration_spec.js, well create a new
file to keep our tests tidy.

New file - spec/javascripts/integration/email_integration_spec.js


module('Email address integration');

2
3
4
5
6
7
8
9
10
11

test('Showing associated email addresses', function () {


visit('/contacts/1');
andThen(function () {
var emails = find('.email_address');
ok(emails.length == 2, "Expected two emails got: " + emails.length);
equal(emails[0].innerText, '[email protected]', 'Expected [email protected] got\
: ' + emails[0].innerText);
});
});

With this spec were looking for elements with the email_address class and checking to see how
many exist. Were also checking to specify that the first element contains specific text. The reason
for the two types of test will become apparent later.
Running our spec well get a failure
For more information on relationships in Rails visit http://guides.rubyonrails.org/association_basics.html#the-types-of-associations

Chapter 7 - Relationships in Ember

67

Failure message
1

Failures:

2
3
4

1) Email address integration Showing associated email addresses (1, 0, 1)


Failure/Error: Expected two emails got: 0

At this stage well live with this failing spec for a while as we move to implement the infrastructure
required to get it to green.

A Place to Store Emails


We will want an email model which we can associate with a contact, so lets create a model spec.
New file - spec/javascripts/unit/models/email_spec.js
1

module('Email model');

2
3
4
5

test('email address attribute', function () {


respondsTo('Email', 'address', 'string');
});

Well call the model email for simplicity, so we look for an email model, with an address attribute
of type: string. We should have a failure similar to this:
Failure message
1
2
3
4
5

1) Email address model email address attribute (1, 0, 1)


Failure/Error: Died on test #1
at http://127.0.0.1:49285/assets/teaspoon\
-qunit.js?body=1:427
at http://127.0.0.1:49285/assets/models/email_spec.js?body=1:5: 'undefined' i\
s not an object (evaluating 'AddressBook.Email.metaForProperty')

Lets now define our model.

Chapter 7 - Relationships in Ember

68

New file - /app/assets/javascripts/unit/models/email.js


1
2
3
4

AddressBook.Email = DS.Model.extend({
address: DS.attr('string'),
contact_id: DS.attr('number')
});

And with that, our model spec should be passing.

Testing Relationships
Now we will need to have a relationship between our contact and our email addresses. Each contact
may have zero or many email addresses, so lets write a spec for that.
spec/javascripts/models/contacts_spec.js
1
2
3
4
5
6
7
8

//....
});
test('relationships', function () {
var emails = AddressBook.Contact.metaForProperty('emails');
ok(emails.isRelationship, 'Expecting isRelationship to be true, got false');
equal(emails.kind, 'hasMany', 'Expected a hasMany relationship got: ' + email\
s.kind);
});

With this test were are using some new concepts. metaForProperty('emails') looks for an emails
property on our model. We then test to see if .isRelationship is true. .kind returns a string
describing the relationship.
We will expect to see a failure similar to this:
Failure message
1

Failures:

2
3
4
5
6
7

1) Contacts Model relationships (1, 0, 1)


Failure/Error: Died on test #1
at http://127.0.0.1:50516/assets/teaspoon\
-qunit.js?body=1:427
at http://127.0.0.1:50516/assets/models/contacts_spec.js?body=1:15: Assertion\
Failed: metaForProperty() could not find a computed property with key 'emails'.

We can implement the relationship now by adding a new property to our Contact model.

Chapter 7 - Relationships in Ember

69

/app/assets/javascripts/models/contacts.js
1
2

last_name: DS.attr('string'),
emails: DS.hasMany('email')

And with that we should have a green spec for our relationship.

Rendering Relationships
Well now need a place to display our data so we need to update the show template.
/app/assets/javascripts/templates/contacts/show.hbs
1
2
3
4
5
6
7
8

</ul>
<ul>
{{#each emails}}
<li class="email_address">
{{address}}
</li>
{{/each}}
</ul>

Now in order to get our integration spec passing we will need some sample data to render. We will
need to update our fixtures in order to do this and in doing so well discover a new gotcha.
spec/javascripts/spec_helper.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

var resetFixtures = function () {


AddressBook.Contact.FIXTURES = [
{
id: 1,
first_name: 'Dave',
last_name: 'Crack',
emails: [1,2]
},
//.....
];
AddressBook.Email.FIXTURES = [
{
id: 1,
address: '[email protected]',
contact_id: 1

Chapter 7 - Relationships in Ember

},
{

16
17

id: 2,
address: '[email protected]',
contact_id: 1

18
19
20

21

];

22
23

70

};

With this update to the fixtures we add an emails property to our first contact which specifies the
ids of the associated emails. This will mean our first contactct should have two associated emails,
whilst the second one still has zero.
We should now get a spectacular explosion with multiple specs failing with a similar message!
Failure message
1
2
3
4

Failure/Error: Error: Assertion Failed: You looked up the 'emails' relations\


hip on '<AddressBook.Contact:ember538:1>' but some of the associated records were\
not loaded. Either make sure they are all loaded together with the parent record\
, or specify that the relationship is async (`DS.hasMany({ async: true })`)

Now this seems to be telling us exactly how to fix the issue, so we can try that:
/app/assets/javascripts/models/contacts.js
1
2

//..
emails: DS.hasMany('email', {async: true})

Most of the failures will now be fixed, but our integration spec is still complaining.
Failure message
1

Failures:

2
3
4

1) Email address integration Showing associated email addresses (1, 1, 2)


Failure/Error: Expected [email protected] got:

So we now have two elements on the page for email addresses, but we dont have the correct text.
The fix for this turns out to be kinda obscure. Im not entirely sure that this approach would be
considered best practice and it feels like a bit of a hack.
With that in mind, lets remove the change to contacts.js

Chapter 7 - Relationships in Ember

71

/app/assets/javascripts/models/contacts.js
1
2

//...
emails: DS.hasMany('email')

Well next adjust the fixture reset function.


spec/javascripts/spec_helper.js

address: '[email protected]'

];
AddressBook.Contact.reopen({
emails: DS.hasMany('email', {async: true})
});
//...

3
4
5
6
7
8

};

What were doing here is overwriting the hasMany property after the fixtures are created
which for some reason fixes the issue! I feel like this is the right place to make the hack as
it will be easy to remove if this issue gets resolved in the future.

And with that, we should be green for everything again.


Time for a commit
Commit changes
1
2

git add .
git commit -m 'Add Email model and relationship'

Wiring Up the Relationships to Rails


So now were gonna need a place to store our emails in Rails, this will involve defining relationships
and ways in which the should be serialised.
First well add another testing gem. The shoulda matchers allow us to specify the existence of
relationships on models, amongst other things.

Chapter 7 - Relationships in Ember

Gemfile
1
2
3

gem 'rspec-rails'
gem 'shoulda-matchers'
end

And install it.


Install gems
1

bundle install

An Email Model
Well need an email model, so lets start with a failing spec.
New file - spec/models/email_spec.rb
1

require 'spec_helper'

2
3
4

describe Email do
let(:email) { FactoryGirl.build_stubbed(:email)}

5
6

subject { email }

7
8
9

it { should respond_to :address }


it { should belong_to :contact}

10
11

end

We should see an error similar to this


Failure message
1
2

address-book/spec/models/email_spec.rb:3:in `<top (required)>': uninitialized con\


stant Email (NameError)

Lets fix that by creating our Email class.

72

Chapter 7 - Relationships in Ember

73

New file - /app/models/email.rb


1
2

class Email < ActiveRecord::Base


end

Good, now we should have a failure similar to this.


Failure message
1

Failures:

2
3
4
5
6
7
8
9

1) Email
Failure/Error: let(:email) { FactoryGirl.build_stubbed(:email)}
ActiveRecord::StatementInvalid:
Could not find table 'emails'
# ./spec/models/email_spec.rb:4:in `block (2 levels) in <top (required)>'
# ./spec/models/email_spec.rb:6:in `block (2 levels) in <top (required)>'
# ./spec/models/email_spec.rb:9:in `block (2 levels) in <top (required)>'

In order to get this passing well need a table to store the emails, and well create a migration for
that.
New file - /db/migrate/create_emails.rb
1
2
3
4
5
6
7
8

class CreateEmails < ActiveRecord::Migration


def change
create_table :emails do |t|
t.string :address
t.references :contact
end
end
end

If youre fairly new to rails, the t.references may be new to you. This statement tells Rails that
there is going to be a relationship with contacts and when the migration is run, it will create a
column called contact_id. This is just a more expressive way of saying t.integer :contact_id.
There are no database level foreign key constraints created.
We can now run the migration.

Chapter 7 - Relationships in Ember

Migrating
1
2

bundle exec rake db:migrate


bundle exec rake db:test:prepare

Our failure message should now be complaining about the missing factory.
Failure message
1
2
3
4

1) Email
Failure/Error: let(:email) { FactoryGirl.build_stubbed(:email)}
ArgumentError:
Factory not registered: email

Lets create our factory.


New file - spec/factories/email.rb
1
2
3
4
5

FactoryGirl.define do
factory :email do
address "MyString"
end
end

You will need to execute the reload command in the guard session now to make the new
factory available.

Contact Relationship
Our failure should now tell us what we need to fix up next.

74

Chapter 7 - Relationships in Ember

75

Failure message
1

Failures:

2
3
4
5
6
7

1) Email should belong to contact


Failure/Error: it { should belong_to :contact}
Expected Email to have a belongs_to association called contact (no associa\
tion called contact)
# ./spec/models/email_spec.rb:9:in `block (2 levels) in <top (required)>'

Great, lets get that fixed.


/app/models/email.rb
1
2
3

class Email < ActiveRecord::Base


belongs_to :contact
end

Email Relationship
Well need to specify the relationship in the opposite direction now, so well add a new test to our
contact spec.
spec/models/contact_spec.rb
1
2

it { should respond_to :last_name }


it { should have_many :emails }

Our failure message will now tell us what to do.


Failure message
1

Failures:

2
3
4
5
6
7

1) Contact should have many emails


Failure/Error: it { should have_many :emails }
Expected Contact to have a has_many association called emails (no associat\
ion called emails)
# ./spec/models/contact_spec.rb:10:in `block (2 levels) in <top (required)>'

Lets get on an define the relationship

Chapter 7 - Relationships in Ember

76

/app/models/contact.rb
1
2

class Contact < ActiveRecord::Base


has_many :emails, dependent: :destroy

Awesome, were back to green.

Email Controller and Routes


Now were going to need a way to interact with our Email class so we will need a controller and
route.
Lets get started with a failing spec.
New file - spec/controllers/api/v1/emails_controller_spec.rb
1

require 'spec_helper'

2
3
4

describe Api::V1::EmailsController do
end

We should see a failure similar to this.


Failure message
1
2

address-book/spec/controllers/api/v1/emails_controller_spec.rb:3:in `<top (requir\


ed)>': uninitialized constant Api::V1::EmailsController (NameError)

And to fix it.


New file - app/controllers/api/v1/emails_controller.rb
1
2

class Api::V1::EmailsController < ApplicationController


end

Great, green again. Now to add a show test.

Chapter 7 - Relationships in Ember

spec/controllers/api/v1/emails_controller_spec.rb
1
2
3
4
5
6
7
8

describe Api::V1::EmailsController do
describe 'GET methods' do
it 'returns an email address' do
@email = FactoryGirl.create(:email)
get :show, id: @email.id
assigns(:email).class.should == Email
end
end

Well get a failure similar to this.


Failure message
1

No route matches {:id=>"1", :controller=>"api/v1/emails", :action=>"show"}

We can add the necessary route now.


/config/routes.rb
1
2
3

namespace :v1 do
resources :contacts
resources :emails

And now a new failure message


Failure message
1

Failures:

2
3
4
5
6
7
8

1) Api::V1::EmailsController GET methods returns an email address


Failure/Error: get :show, id: @email.id
AbstractController::ActionNotFound:
The action 'show' could not be found for Api::V1::EmailsController
# ./spec/controllers/api/v1/emails_controller_spec.rb:7:in `block (3 levels)\
in <top (required)>'

Lets go ahead and fix that up.

77

Chapter 7 - Relationships in Ember

/app/controllers/api/v1/emails_controller.rb
1
2
3
4
5

class Api::V1::EmailsController < ApplicationController


before_action :set_email, only: [:show, :destroy]
def show
render json: @email
end

6
7

private

8
9
10
11
12

def set_email
@email = Email.find(params[:id])
end
end

Well also need a way to add an email, so lets get that sorted
spec/controllers/api/v1/emails_controller_spec.rb
1
2
3
4
5
6
7
8
9

describe 'GET methods' do


#...
end
describe 'POST methods' do
it 'creates an email address' do
@email = FactoryGirl.attributes_for(:email)
expect{post :create, email: @email}.to change(Email, :count).by(1)
end
end

Failure message
1

Failures:

2
3
4
5
6
7

1) Api::V1::EmailsController POST methods creates an email address


Failure/Error: expect{post :create, email: @email}.to change(Email, :count).\
by(1)
AbstractController::ActionNotFound:
The action 'create' could not be found for Api::V1::EmailsController

OK, lets fix that up.

78

Chapter 7 - Relationships in Ember

/app/controllers/api/v1/emails_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# ...
def create
@email = Email.new(get_email_params)
if @email.save
render json: nil
else
render json @email.errors, status: :unprocessable_entity
end
end
private
#...
def get_email_params
params.require(:email).permit([:address, :contact_id])
end

And were back to green.


Next it would be good to be able to delete emails too.
spec/controllers/api/v1/emails_controller_spec.rb
1
2
3
4
5
6

describe 'DELETE methods' do


it 'deletes an email address' do
@email = FactoryGirl.create(:email)
expect{delete :destroy, id: @email.id}.to change(Email, :count).by(-1)
end
end

Failure message
1
2
3
4
5

1) Api::V1::EmailsController DELETE methods deletes an email address


Failure/Error: expect{delete :destroy, id: @email.id}.to change(Email, :coun\
t).by(-1)
AbstractController::ActionNotFound:
The action 'destroy' could not be found for Api::V1::EmailsController

OK, lets fix that too.

79

Chapter 7 - Relationships in Ember

80

/app/controllers/api/v1/emails_controller.rb

def destroy
if @email.destroy
render json: nil
else
render json @email.errors, status: :unprocessable_entity
end
end

1
2
3
4
5
6
7

Serializing and Relationships


In order to pass our data to Ember well need to update our serialisers and introduce the concept of
embedding relationships.
As our application grows we may want to embed many things, so well make some adjustments that
will reduce repetition in the future.
New file - /app/serializers/application_serializer.rb
1
2
3

class ApplicationSerializer < ActiveModel::Serializer


embed :ids, include: true
end

With this new class defined, well get our other serializers to inherit from it. This tells Rails to use
a particular convention for including associations. Essentially the object were looking for specified
at the root level and its association ids are specified as an array. Also at the root level we get the
associated records as an array of objects. Ember data does the work to reassemble the data on receipt.
Embedding Format
1
2
3
4
5
6
7
8
9
10
11
12

{
"contacts": {
"id": 1,
"first_name": "Dave",
"last_name": "Crack",
"emails": ["1", "2"]
},
"emails": [{
"id": "1",
"address": "[email protected]"
}, {
"id": "2",

Chapter 7 - Relationships in Ember

"address": "[email protected]"

13

}]

14
15

/app/serializers/contact_serializer.rb
1
2
3
4

class ContactSerializer < ApplicationSerializer


attributes :id, :first_name, :last_name
has_many :emails
end

New file - /app/serializers/email_serializer.rb


1
2
3

class EmailSerializer < ApplicationSerializer


attributes :id, :address, :contact_id
end

With that weve got everything we need to display our associated emails on the frontend.
Phew, time for a commit!
Commit
1
2

git add .
git commit -m 'Emails and serialization'

Up next, well work on testing computed properties.

81

Chapter 8 - Unit Testing Computed


Properties
So in our contact index just having the contacts first name isnt really useful for navigation. Luckily
Ember gives us access to computed properties which can be kept in sync with the database and
updated whenever any of its base components change.
Lets see how we can unit test that.
Computed properties are relatively straightforward as a concept, specify the computed property
name and a function which returns the output you want and chain the properties that it depends on
so that Ember can keep it up to date if any of those properties change.
In order to test them, well need a record to work with and then test the output for our computed
property.
Im big on abstracting wherever possible, so lets create a testing helper.
spec/javascripts/support/testing_helpers.js
1
2
3
4
5
6
7
8
9
10
11

//.....
var computedPropertyTest = function (model, record, computed_property, expected_o\
utput) {
var store = AddressBook.__container__.lookup('store:main');
Ember.run(function () {
var new_record = store.createRecord(model, record);
var computed = new_record.get(computed_property);
equal(computed, expected_output, 'Expected ' + expected_output +' got: ' \
+ computed);
});
};

Below is an explanation of its usage


parameter

expected input

example

model

The name of the model under test as a


string
A record to be created from an object

'full_name'

record
computed_property
expected_output

The name of the computed property as


a string
what we expect to see as a string

{first_name: 'Dave', last_name:


'Crack'}
'full_name'
'Crack, Dave'

Chapter 8 - Unit Testing Computed Properties

83

And with that, were ready to write a failing spec.


spec/javascripts/unit/models/contacts_spec.js
1
2
3
4
5
6

// ....
});
test('full_name computed property', function () {
computedPropertyTest('contact', {first_name: 'Mabel', last_name: 'Smith'}, 'f\
ull_name', 'Smith, Mabel');
});

Which should tell us that result was undefined.


Failure message
1

Failures:

2
3
4

1) Contacts Model full_name computed property (1, 0, 1)


Failure/Error: Expected Smith, Mabel got: undefined

We can fix this up by adding our computed property.


/app/assets/javascripts/models/contacts.js
1
2
3
4

emails: DS.hasMany('email'),
full_name: function () {
return this.get('last_name') +', '+ this.get('first_name');
}.property()

Beautiful, weve got the spec passing. It would be great, however if the output got updated when
one of its dependencies changes.
Lets test that.

Chapter 8 - Unit Testing Computed Properties

spec/javascripts/unit/models/contacts_spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

});
test('full name updates when properties change', function () {
var store = AddressBook.__container__.lookup('store:main');
Ember.run(function () {
var contact = store.createRecord('contact', {first_name: 'Buzz', last_nam\
e: 'Lightyear'});
var full_name = contact.get('full_name');
equal(full_name, 'Lightyear, Buzz', 'Expected "Lightyear, Buzz", got: ' +\
full_name);
contact.set('first_name', 'Slinky');
full_name = contact.get('full_name');
equal(full_name, 'Lightyear, Slinky', 'Expected "Lightyear, Slinky", got:\
'+ full_name);
contact.set('last_name', 'Dog');
full_name = contact.get('full_name');
equal(full_name, 'Dog, Slinky', 'Expected "Dog, Slinky", got: '+ full_nam\
e);
});
});

This should give us the following failure


Failure message
1

Failures:

2
3
4

1) Contacts Model full name updates when properties change (2, 1, 3)


Failure/Error: Expected "Lightyear, Slinky", got: Lightyear, Buzz

5
6
7

2) Contacts Model full name updates when properties change (2, 1, 3)


Failure/Error: Expected "Dog, Slinky", got: Lightyear, Buzz

This is pretty straightforward to resolve.

84

Chapter 8 - Unit Testing Computed Properties

85

/app/assets/javascripts/models/contacts.js
1
2
3

full_name: function () {
return this.get('last_name') +', '+ this.get('first_name');
}.property('first_name', 'last_name')

Lovely, green specs again. Now it would be nice to use the full_name property in our contacts index.
spec/javascripts/integration/contacts_integration_spec.js
1
2
3
4
5
6
7
8
9

test('Full name used for link on index page', function () {


visit('/contacts');
andThen(function () {
var first_contact = find('.contacts_list li:first a').text();
equal(first_contact, 'Crack, Dave', 'Expected "Crack, Dave", got: ' + fir\
st_contact);
});
});
//..

So we can now update our index template to use the new computed property instead for first_name.
/app/assets/javascripts/templates/contacts/index.hbs
1
2

{{#link-to 'contacts.show' this}}{{first_name}}{{/link-to}}


{{#link-to 'contacts.show' this}}{{full_name}}{{/link-to}}

And with that weve got another green spec!


Time to commit.
Commit changes
1
2

git add .
git commit -m 'Computed properties'

Chapter 9 - Creating and Updating


Emails on the Frontend
In this chapter were going to take a dive into mocking our server responses. Theres going to be a
whole stack of new things coming and a reasonable amount of refactoring.
It would be possible to do most of what we want to develop in our application without mocking
server responses, but this is a great opportunity to gain a deeper understanding of our API and how
data is passed back and forth in Ember.

Faking HTTP Requests


In order to provide a fake server, were going to use the work of Trek Glowacki .
There are a number of ways to do this, the simplest is to use wget, though you can visit the three
repos and download and copy and paste into new files.
All of the files will be created in /spec/javascripts/support
Fake server dependencies
1
2
3
4
5
6

cd /spec/javascripts/support
wget https://github.com/trek/FakeXMLHttpRequest/raw/master/fake_xml_http_request.\
js
wget https://raw.github.com/trek/fakehr/master/fakehr.js
wget https://raw.github.com/trek/ember-testing-httpRespond/master/httpRespond-1.1\
.js

With these files downloaded, lets add them to the dependencies for teaspoon.

The three repos we are using are https://github.com/trek/ember-testing-httpRespond, https://github.com/trek/fakehr, https://github.com/trek/


FakeXMLHttpRequest

Chapter 9 - Creating and Updating Emails on the Frontend

87

spec/javascripts/spec_helper.js
1
2
3
4
5
6

//=
//=
//=
//=
//=
//=

require application.js.erb
require support/fake_xml_http_request
require support/httpRespond-1.1
require support/fakehr
require support/testing_helpers
require_self

By including these three files we make a new construct available in our testing, .httpRespond("request
type", "url", response as object or array of objects) . The helper can be chained from
.click() and other testing contracts to mock a server hit and response.
Lets see how this works by creating a new spec for frontend email testing.
New file - spec/javascripts/integration/frontend_email_spec.js
1
2
3
4
5
6
7
8

module('Frontend Email', {
setup: function () {
fakehr.start();
},
teardown: function () {
fakehr.stop();
}
});

This first step should look fairly familiar, with the addition of the fakehr start and stop calls. As the
name suggests, it starts and stop the HTTP faker!
We should now write our failing spec.
spec/javascripts/integration/frontend_email_spec.js
1
2
3
4
5
6
7
8
9
10

});
//..
test("Add an email", function () {
var fakeContact = {contact: {id: 1, first_name: 'Dave', last_name: 'Crack', e\
mails: [1, 2]}, emails: [
{id: 1, address: '[email protected]'},
{id: 2, address: '[email protected]'}
]};
visit('/contacts/1').httpRespond('get', '/api/v1/contacts/1', fakeContact);
});

Chapter 9 - Creating and Updating Emails on the Frontend

88

So theres a lot going on here! First we create an object that will represent our contact using the
format that Rails will send it in:
Top level - a contact with first name and last name strings, and an emails array which contains
two ids
Top level - an array containing email objects
From there we visit the correct route /contacts/1 and add the new httpRespond() function. We tell
it to expect a get request to /api/v1/contacts/1 and to return the fakeContact. Now we havent
actually issued any expectations at this point, but lets run the test anyway.
Failure message
1

Failures:

2
3
4
5

1) Frontend Email Add an email (1, 0, 1)


Failure/Error: Error: No request intercepted for GET /api/v1/contacts/1. Int\
ercepted requests were:

When we use the httpRespond() function is sets an expectation that it will be called. Now we know
that when we visit /contacts/1 Ember should be requesting data from the server, so whats gone
wrong? Well the problem is that we earlier setup a FixtureAdapter which means that no server
requests are happening!
In order to resolve this were going to ensure we have functions to turn on the RESTAdapter or the
FixtureAdapter as necessary. Well start by moving the resetFixtures function our of our spec_helper
(note that the line above it goes too!).
spec//javascripts/spec_helper.js
1

AddressBook.ApplicationAdapter = DS.FixtureAdapter;

2
3
4
5
6
7
8
9
10
11
12
13
14
15

var resetFixtures = function () {


AddressBook.Contact.FIXTURES = [
{ id: 1, first_name: 'Dave', last_name: 'Crack', emails: [1,2] },
{ id: 2, first_name: 'Dustin', last_name: 'Hoffman' }
];
AddressBook.Email.FIXTURES = [
{ id: 1, address: '[email protected]', contact_id: 1 },
{ id: 2, address: '[email protected]', contact_id: 1 }
];
AddressBook.Contact.reopen({
emails: DS.hasMany('email', {async: true})
});
};

Chapter 9 - Creating and Updating Emails on the Frontend

89

We will now add this to our testing_helpers


spec/javascripts/support/testing_helpers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

//..
var resetFixtures = function () {
AddressBook.ApplicationAdapter = DS.FixtureAdapter;
AddressBook.Contact.FIXTURES = [
{ id: 1, first_name: 'Dave', last_name: 'Crack', emails: [1, 2] },
{ id: 2, first_name: 'Dustin', last_name: 'Hoffman' }
];
AddressBook.Email.FIXTURES = [
{ id: 1, address: '[email protected]', contact_id: 1 },
{ id: 2, address: '[email protected]', contact_id: 1 }
];
AddressBook.Contact.reopen({
emails: DS.hasMany('email', {async: true})
});
};

Next well ensure that we turn have the fixture adapter turned back on after our frontend_email_spec.
spec/javascripts/integration/frontend_email_spec.js
1
2
3

teardown: function () {
fakehr.stop();
resetFixtures();

Reimplementing RESTAdapter in Tests


Now lets create a function to turn our RESTApdapter back on.

Chapter 9 - Creating and Updating Emails on the Frontend

90

spec/javascripts/support/testing_helpers.js
1
2
3

//..
var turnOnRESTAdapter = function () {
AddressBook.ApplicationAdapter = DS.RESTAdapter;

AddressBook.Store = DS.Store.extend({
adapter: '-active-model'
});

5
6
7
8

DS.RESTAdapter.reopen(
{namespace: "api/v1"}
);

9
10
11
12

};

What we are doing here is specifying that the ApplicationAdapter uses the DS.RESTAdapter, telling
the Store to use the active-model structure for serialising the data and telling the RESTAdapter to
prepend the AJAX calls with api/v1.
We can now use this function in the frontend_email_spec.
spec/javascripts/integration/frontend_email_spec.js
1
2
3

setup: function () {
turnOnRESTAdapter();
//..

Great, now we should see QUnit complaining about no assertions being specified.
Failure message
1

Failures:

2
3
4
5

1) Frontend Email Add an email (1, 0, 1)


Failure/Error: Expected at least one assertion, but none were run - call exp\
ect(0) to accept zero assertions.

Now Ive specified in our mock JSON data that we should have an email [email protected] which
is different to our Fixture data, lets test for that to make sure the data is getting rendered.

Chapter 9 - Creating and Updating Emails on the Frontend

91

spec/javascripts/integration/frontend_email_spec.js
1
2
3
4
5

visit('/contacts/1').httpRespond('get', '/api/v1/contacts/1', fakeContact);


andThen(function () {
var email = /[email protected]/.test($('li').text());
ok(email, 'Expected to find [email protected]');
)};

So here we introduce a new way of checking that text can be found in the page by using a
RegularExpression test. This takes in a jQuery object on which we call text() and returns true if the
text is found. The same effect can be achieved with the find helper, but its great to have choices!
With that, our tests should be green.
Now lets start adding the behaviour we want to add a new email.
Weve seen earlier that we can use a boolean to set a state and use this in the view to determine
what is rendered, so lets create the behaviour we want attached to a button which will then render
an input field where the user can enter a new email address.
Well start with the button.
spec/javascripts/integration/frontend_email_spec.js
1
2
3
4

ok(email, 'Expected to find [email protected]');


click('#create_email');
var input = find('#new_email').length;
ok(input, 'Expected to find new email input');

We are asking for or spec to look for a element with an id of create_email, click the button and
then find an element with an id of new_email.
With that we should be getting a similar error to this
Failure message
1

Failures:

2
3
4

1) Frontend Email Add an email (1, 1, 2)


Failure/Error: Error: Element #create_email not found.

In our template we can now add the button in an if/else block.

Chapter 9 - Creating and Updating Emails on the Frontend

92

/app/assets/javascripts/templates/contacts/show.hbs
1
2
3
4
5
6

{{#if addingEmail}}
{{else}}
<button id="create_email" {{action 'createEmail'}} >Add new email</button>
{{/if}}
<ul>
{{#each emails}}

So here we are stating that we should check truthiness of addingEmail and if its false, display a
button. We assign the action createEmail to the button.
Our tests should now be guiding us where to go.
Failure message
1

Failures:

2
3
4
5
6

1) Frontend Email Add an email (1, 1, 2)


Failure/Error: Error: Nothing handled the action 'createEmail'. If you did h\
andle the action, this error can be caused by returning true from an action handl\
er in a controller, causing the action to bubble.

Now the correct place to handle this behaviour would be in a ContactShowController, so lets get
that created.
New file - /app/assets/javascripts/controllers/contacts_show_controller.js
1
2
3
4
5
6
7

AddressBook.ContactsShowController = Ember.ObjectController.extend({
actions: {
createEmail: function () {
this.toggleProperty('addingEmail');
}
}
});

We use the Ember.ObjectController.extend constructor for this controller because we are dealing
with a single item, rather than a collection.
Our tests should now be telling us that the input field cannot be found.

Chapter 9 - Creating and Updating Emails on the Frontend

93

Failure message
1
2

1) Frontend Email Add an email (1, 1, 2)


Failure/Error: Expected to find new email input

Now we can add the input field.


/app/assets/javascripts/templates/contacts/show.hbs
1
2
3

{{#if addingEmail}}
<label for="new_email">Email address</label>
{{input type='text' value=controller.new_email id='new_email'}}

Here we create a label element and a text input field. We give the input field an id of new_email
and bind its value to the controller under a new_email property.
Now notice that we didnt need to specify a new_email object on the controller or to set a boolean
or addingEmail, Ember deals with this for us when the actions are called.
Our tests should again be green.
We will now specify the behaviour we would like to see when a user tries to save the new email.
spec/javascripts/integration/frontend_email_spec.js
1
2
3
4

ok(input, 'Expected to find new email input');


fillIn('#new_email', '[email protected]');
keyEvent('#new_email', 'keyup', 13).httpRespond('post', '/api/v1/emails', {}, 200\
);

Weve used the fillIn behaviour before so thats straightforward.


The keyEvent helper is a new one, which allows us to simulate a key event. The function is called
like this - keyEvent(selector, type, keyCode). Lets explain that a bit further.
Parameter

Expected input

selector
type
keyCode

A string to find the element on which to simulate a key event


A string describing the javascript key event (such as keyup, keydown, keypress)
A number which represents the key pressed, such as 13 which represents the enter key.

We now know that we are expecting the enter button to be pressed to save the email. This seems
like a nice, user focussed thing to do - rather than adding another button for them to click and also
A list of many of the available key codes is here http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes

Chapter 9 - Creating and Updating Emails on the Frontend

94

gives us the opportunity to look at some of the other things we can test!
Our httpRespond this time is expecting to receive a POST request to api/v1/emails and will return
with an empty response with a 200 (success) status code.
And with that, we have a new failure indicating that no request has been made.
Failure message
1

Failures:

2
3
4
5

1) Frontend Email Add an email (1, 2, 3)


Failure/Error: Error: No request intercepted for POST /api/v1/emails. Interc\
epted requests were: GET /api/v1/contacts/1

Theres a few things we need to get this resolved. First we need to bind the enter key to an action.
/app/assets/javascripts/templates/contacts/show.hbs
1
2
3
4

{{#if addingEmail}}
<label for="new_email">Email address</label>
{{input type='text' value=controller.new_email id='new_email' insert-newl\
ine='saveNewEmail'}}

Here we update the input to bind insert-newline to a saveNewEmail action.


This wont get our test passing, well still need to add the behaviour to our controller.

Saving Associations
This bit is fairly involved, so Ill explain it in more detail afterwards.
/app/assets/javascripts/controllers/contacts_show_controller.js

},
saveNewEmail: function () {

1
2
3

//1
var self, emailStore, newEmail, contactID, record;

4
5

//2
self = this;

6
7

//3
emailStore = this.store;

8
9
10

//4
newEmail = this.get('new_email');

Chapter 9 - Creating and Updating Emails on the Frontend


11

//5
contactID = this.get('id');

12
13

//6

14

record = emailStore.createRecord('email', {address: newEmail, contact\


_id: contactID});
//7
var onFulfillment = function (data) {
self.toggleProperty('addingEmail');
self.set('new_email', '');
};
//8
record.save().then(onFulfillment);
}
}
});

15
16
17
18
19
20
21
22
23
24
25

95

1.
2.
3.
4.
5.

We declare a bunch of variable we are going to use


self is to store a reference to this which well use later when our promise is fulfilled
emailStore gets a reference to our data store
newEmail gets the value of new_email
contactID gets the ID of our contact (we need this to associate the email with the correct
contact)
6. We create a new email record passing in an object with the data we want to store
7. We create a function that will be called if the record saves successfully. It sets addingEmail to
false and new_email to an empty string.
8. We call save() on our record and specify that onFulfillment should be called if the save
succeeds in .then()
Phew! That was a lot of work. The reason that things are a bit more involved here is because were
not actually working directly with the emails, but rather accessing them through their parent object.
Because of this we need to be much more explicit than when we were creating a new contact earlier.
Now our tests are green again. We should check that our data is now displayed on the screen.

Chapter 9 - Creating and Updating Emails on the Frontend

96

spec/javascripts/integration/frontend_email_spec.js
1
2
3
4
5
6

keyEvent('#new_email', 'keyup', 13).httpRespond('post', '/api/v1/emails',\


{email: {id: 3, contact_id: 1, address: '[email protected]'}}, 200);
andThen(function () {
ok(/[email protected]/.test($('.email_address').text()), 'Expecte\
d to find [email protected]');
});

Which should fail with the following


Failure message
1

Failures:

2
3
4

1) Frontend Email Add an email (1, 2, 3)


Failure/Error: Expected to find [email protected]

We can fix that by pushing the data we receive from the server into our store.
/app/assets/javascripts/controllers/contacts_show_controller.js
1
2
3

self.set('new_email', '');
var localEmails = self.get('model.emails');
localEmails.pushObject(data);

Our tests should once again be green.

Dealing With Failed Saves


The next obvious thing to deal with would be the scenario where the request to save the item fails.
Well move our fakeContact variable outside the test so its available to all tests and write the spec
for failure.

Chapter 9 - Creating and Updating Emails on the Frontend

97

spec/javascripts/integration/frontend_email_spec.js
1
2
3
4
5

var fakeContact = {contact: {id: 1, first_name: 'Dave', last_name: 'Crack', email\


s: [1, 2]}, emails: [
{id: 1, address: '[email protected]'},
{id: 2, address: '[email protected]'}
]};

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

test("Add an email", function () {


var fakeContact = {contact: {id: 1, first_name: 'Dave', last_name: 'Crack', e\
mails: [1, 2]}, emails: [
{id: 1, address: '[email protected]'},
{id: 2, address: '[email protected]'}
]};
visit('/contacts/1').httpRespond('get', '/api/v1/contacts/1', fakeContact);
//....
});
test("Tell user when failed to save email", function () {
visit('/contacts/1').httpRespond('get', '/api/v1/contacts/1', fakeContact);
andThen(function () {
click('#create_email');
fillIn('#new_email', '[email protected]');
keyEvent('#new_email', 'keyup', 13).httpRespond('post', '/api/v1/emails',\
{}, 400);
andThen(function () {
ok(/Failed to save/.test($('.error').text()), 'Expected to find "Fail\
ed to save"');
});
});
});

Here we specify that when we hit the enter button we expect that a post request is made and that
it will return a black response with a 400 failure code. This should trigger the display of a message
telling the user that the save failed.
To implement this well need a place in our template to render the message.

Chapter 9 - Creating and Updating Emails on the Frontend

98

/app/assets/javascripts/templates/contacts/show.hbs
1
2
3
4
5
6

{{#if failedToSave}}
<div>
<p class="error">{{failedToSaveMessage}}</p>
</div>
{{/if}}
{{#if addingEmail}}

We can now update the controller to handle the error.


/app/assets/javascripts/controllers/contacts_show_controller.js
1
2
3
4
5
6
7

localEmails.pushObject(data);
};
var onRejection = function () {
self.toggleProperty('failedToSave');
self.set('failedToSaveMessage','Failed to save email, please try later');
};
record.save().then(onFulfillment, onRejection);

With this we creating a failedToSave boolean and setting it to true and creating a failedToSaveMessage
string. Green specs again, but what happens if the user then proceeds to try and save again and this
time it succeeds?
Lets update our spec to allow for a success following failure.
spec/javascripts/integration/frontend_email_spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14

test("Tell user when failed to save email", function () {


visit('/contacts/1').httpRespond('get', '/api/v1/contacts/1', fakeContact);
var textToFind = /Failed to save/;
andThen(function () {
click('#create_email');
fillIn('#new_email', '[email protected]');
keyEvent('#new_email', 'keyup', 13).httpRespond('post', '/api/v1/emails',\
{}, 400);
andThen(function () {
ok(textToFind.test($('.error').text()), 'Expected to find "Failed to \
save"');
keyEvent('#new_email', 'keyup', 13).httpRespond('post', '/api/v1/emai\
ls', {email: {id: 3, contact_id: 1, address: '[email protected]'}}, 200);
andThen(function () {

Chapter 9 - Creating and Updating Emails on the Frontend


15
16
17
18
19
20

99

ok(!textToFind.test($('.error').text()), 'Did not expect to find \


"Failed to save"');
});
});
});
});

Now that were testing the text twice we pull it out into a variable, then we add another keyEvent
with a successful response and assert that we should not see the error message any more.
/app/assets/javascripts/controllers/contacts_show_controller.js
1
2
3
4
5
6
7
8
9
10
11

var resetErrorMessages = function () {


if (self.get('failedToSave')) {
self.toggleProperty('failedToSave');
self.set('failedToSaveMessage', '');
}
}
var onFulfillment = function (data) {
//...
localEmails.pushObject(data);
resetErrorMessages();
};

And with that weve got a useful message for our users if the save fails and way of dealing with
success after failure.
Now would be a good time to commit our changes before we move on to connecting this up to the
backend.
Commit changes
1
2

git add .
git commit -m 'Frontend email specs and implementation'

Testing JSON Responses in Rails


Now the observant ones among you would have noticed that in the specs weve just worked through
we mocked a response that returned the newly created email, but when we implemented it earlier
in the Rails controller, we were returning a blank, but successful, response. Well create a spec for
that now.

Chapter 9 - Creating and Updating Emails on the Frontend

100

spec/controllers/api/v1/emails_controller_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12

describe 'POST methods' do


it 'creates an email address' do
//..
end
it 'returns the email as JSON' do
email = FactoryGirl.attributes_for(:email)
post :create, email: email
body = JSON.parse(response.body)['email']
%w(address contact_id id).each do |attribute|
expect(body).to have_key(attribute)
end
end

Here we are using a tiny bit of meta-programming to make the code a little bit shorter. This also
means we can easily update the test in the future if we want to make any changes.
Failure message
1

Failures:

2
3
4
5
6
7
8

1) Api::V1::EmailsController POST methods returns the email as JSON


Failure/Error: body = JSON.parse(response.body)['email']
JSON::ParserError:
757: unexpected token at 'null'
# ./spec/controllers/api/v1/emails_controller_spec.rb:21:in `block (3 levels\
) in <top (required)>'

This is because we are not actually returning any json currently, only a success code. Lets get that
sorted, and a I apologise for how much work this is going to take.
/app/controllers/api/v1/emails_controller.rb
1
2
3
4

def create
@email = Email.new(get_email_params)
if @email.save
render json: @email

Well commit our changes now.

Chapter 9 - Creating and Updating Emails on the Frontend

Commit changes
1
2

git add .
git commit -m 'Creating emails specs and implementation'

In the next chapter well have a look at updating our contacts and their email addresses.

101

You might also like