We just wrapped up a large client project using EmberJS and we learned a few things that are interesting to share.
Ember made this project easier. There are times that a JavaScript framework is unnecessary and there are times that it makes the code much cleaner. This was the latter.
Split Development
We built our API and our JavaScript application as two completely separate applications. We had one repo that held a very basic Rails application with Ember on top and another repo that held the API built in Rails.
Rails instead of Yeoman, Grunt, Brunch, etc
There are a lot of front end development tools that will allow you to build an EmberJS application using CoffeeScript, Sass and the other tools that we like to use on projects. After evaluating them we settled on using a basic Rails application instead; primarily for simplicity. The project had a short timeline and we didn’t want to have to worry about another tool that we were not familiar with. In the future I would love to try building an Ember UI using a front end tool such as Tapas with Ember but we didn’t have any complaints with using Rails in this case and it made our stack a bit simpler to use.
For Ember in our Rails app we used the ember-rails gem. It provides a
basic folder structure for your Ember application inside the
app/assets/javascripts
directory. The directory structure is similar to a
Rails application as you can see below.
controllers/
helpers/
components/
models/
routes/
templates/
templates/components
views/
The one thing that is strange when using the gem for a UI only application is
that your app/
directory in Rails is basically unused except for the
app/assets/javascripts/
where all the actual work will happen. Another
project, EmberAppkitRails, solves this issue by putting the app/
directory
into asset pipeline. This is an interesting idea. The gem is pre-1.0 so the
API could change.
Ember-rails also provides configuration variables for using the development or production version of Ember depending on your current enviroment. This is nice so that your Ember debug information is automatically removed in production.
Fixtures in Development
To allow rapid development, we built the UI in Ember using only fixtures in Ember Data. This allowed us to very quickly build out complex interactions without having to worry about the API being in place. This was a huge help in moving fast and later we backfilled the API. Being able to change property names without having to worry about migrations or outside API changes was very efficient. An Ember Data fixture is a simple JSON object and you can quickly modify it to your needs. It also handles has many and belongs to references using the IDs of other elements.
App.User.FIXTURES = [
{
id: 1
email: 'user@example.com'
posts: [1, 3]
}
{
id: 2
email: 'secondUser@example.com'
posts: [2,4]
}
]
App.Post.Fixtures = [
{
id: 1
title: 'The Art of Vim'
user: 1
}
{
id: 2
title: '15 Minute Blog in EmberJS'
user: 2
}
]
There are downsides to this approach. The first one is the backfill process. We waited too long in the project to connect our API to Ember and ran into issues.
The other problem is that by having the applications in two seperate repos, you can’t easily have a full integration test. In order to do this you would need to run both applications on the same machine simultaneously. You would also need some way to make sure that both of the applications were on the same revision for the test.
We decided to test the apps seperately and trust that the API is what we’ve said it was. This can be frustrating but in our case, it didn’t turn out to be a real problem. Once you have wired your API to the UI, you should never change your UI without also changing the API. This was enforced in code review only.
CoffeeScript
I love CoffeeScript and as a company we have embraced it for our all our projects. Ember is no exception to that. CoffeeScript made our Ember application more readable and easier to work with objects. The only thing that is odd is the syntax for a computed property, but that is a minor issue and we quickly adjusted to seeing it as normal.
fullName: (->
"#{@get('firstName')} #{@get('lastName')}"
).property('firstName', 'lastName')
Fast Tests
By removing the API from
the UI application, we were able to write feature specs entirely in
CoffeeScript. This was a huge benefit to the overall success of the project. We
could test every interaction in our app precisely and not have to worry about
the normal overhead associated with those types of feature specs. The specs only
had to deal with JavaScript so it was really fast. A full rake
for our UI
application was 32.770 seconds including starting the Rails environment. The
suite had a total of 71 examples, most of which were feature specs.
Testing in General
We found Ember to be very easy to test in general. Most things break down to
Ember.Object
and it was easy to grab a controller in a test and verify that a
property works as expected. Because we wanted to use Mocha with Chai BDD
matchers instead of QUnit, the initial test setup was a bit complex but
after using Konacha with a Mocha adapter, it was smooth sailing. The
extra setup time for Mocha over QUnit was definitely worth it. The syntax has a
much more readable format.
describe 'AggregateStatsController', ->
describe 'summed properties', ->
beforeEach ->
stats = []
stats.push Ember.Object.create
clicks: 2
cost: 1.99
stats.push Ember.Object.create
clicks: 4
cost: 2.00
model = Ember.Object.create(dailyStats: stats)
controller = App.__container__.lookup('controller:aggregate_stat')
controller.set('model', model)
it 'will sum the number of clicks in the model', ->
expect(controller.get('clicks')).to.equal(6)
it 'will sum the cost in the model', ->
expect(controller.get('cost')).to.equal(3.99)
Feature specs were also very easy to handle. Ember has built in integration test helpers that worked for most of our needs and we used jQuery to augment them in our expectations. The specs were fast enough that we could test small details in the interface that we might otherwise want to omit. Being able to test all the UI interactions gave us a lot of faith in our codebase.
describe 'Navigating SEM Campaigns', ->
before ->
App.DailyStat.FIXTURES = [
{
id: 1
clicks: 11
}
{
id: 2
clicks: 10
}
]
App.SemCampaign.FIXTURES = [
{
id: 1
name: 'Opening Campaign'
status: 'active'
dailystats: [1]
}
{
id: 2
name: 'Final Sale'
status: 'active'
dailyStats: [2]
}
]
it 'shows the daily stats information for campaign', ->
visit('/').then ->
clickLink('SEM Campaigns').then ->
expect(pageTitleText()).to.equal('SEM Campaigns')
expect(pageHasCampaignWithTitle('Opening Campaign')).to.be.true
expect(statusFor('Opening Campaign')).to.equal('icon-active')
expect(clicksFor('Opening Campaign')).to.equal('11')
expect(pageHasCampaignWithTitle('Final Sale')).to.be.true
Naming your tests
Konacha and Teaspoon both have the downside of not showing a filename when a
spec fails. This caused us a lot of pain when we first started so we decided on
the convention of using the first describe
docstring as the name of the file.
In the case above our file would be named
navigating_sem_campaigns_spec.js.coffee
. This worked out great and made it
much easier to find a failing spec.
Overall
Ember is far stabler than I would have imagined given that 1.0 was just released 6 months ago. If you have a project that is highly interactive and requires a lot of data binding, I recommend trying it out. The Ember community has been incredibly helpful on Stack Overflow, their forums and their IRC channel.