Jeremy Walker

Software Developer & Social Entrepreneur

Backbone.js, CoffeeScript, Jasmine, HAML and Rails working together

This week I started a new project that requires a reasonably in depth Javascript application. I used CoffeeScript for to make Javascript fun and Backbone to give it structure. I wrote templates in HAML, tested using Jasmine, and bundled the whole thing in Rails. I thought it might be useful to show how it all sits together!


First we create a rails app without Test Unit (as we will use rspec as per Jasmine). Let's call it party_time:

rails new party_time -T
cd party_time
rm public/index.html


Let's start with Jasmine. I played around with a couple of methods of integrating it but settled on using Jasminerice, which has worked superbly. This tutorial uses version 0.0.8, but I imagine it will continue to work for later versions. We'll update the Gemfile and can tidy it a little while we're here. Notice the new development/test group.

source 'https://rubygems.org'

gem 'rails', '3.2.3'
gem 'sqlite3'

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sass-rails',   '~> 3.2.3'
  gem 'coffee-rails', '~> 3.2.1'
  gem 'therubyracer', :platform => :ruby

  gem 'uglifier', '>= 1.0.3'

group :development, :test do
  gem "jasminerice"

gem 'unicorn'

gem 'jquery-rails'

Don't forget to run bundle install.

We now create a spec file that manages which tests get run. The file lives at spec/javascripts/spec.js.coffee. It's very simple and you should never need to change it once made.

#= require ../../app/assets/javascripts/application
#= require_tree ./ 

Let's set up a really basic test and check everything's working as it should. We'll start by creating a test file in spec/javascripts/party_time_app_spec.coffee:

describe "PartyTimeApp", ->
    it "passes a sanity test", ->
        app = new PartyTimeApp()

Run rails s in a new tab then open your favourite browser and point it to http://localhost:3000/jasmine. You should see a failing test. Let's make it pass! Create a new file at app/assets/javascripts/party_time_app.coffee:

class window.PartyTimeApp
    sanity: -> true

Refresh the browser and you should be seeing green. If you're not, see if you can figure out why not (Jasmine error messages can be a little confusing but generally lead to the right solution). If you still can't get it, drop me a comment or an email and we'll work it out.


Now that we can test CoffeeScript, let's add in Backbone. Download backbone.js and underscore.js and add them to vendor/assets/javascripts/. I prefer to use the development versions so I can debug anything as I go along. Backbone has a set directory structure, so let's create some folders:

  • app/assets/javascripts/collections
  • app/assets/javascripts/models
  • app/assets/javascripts/routers
  • app/assets/javascripts/templates
  • app/assets/javascripts/views

We're going to need to load things in a specific order for Backbone so we need to rewrite our app/assets/javascripts/application.js.

//= require jquery
//= require jquery_ujs
//= require underscore
//= require backbone
//= require party_time_app
//= require_tree ./models
//= require_tree ./collections
//= require_tree ./views
//= require_tree ./routers
//= require_tree ./templates
//= require_tree .

Check your tests still pass. Backbone is now "installed".

Let's add our first model - the Party class - and create a sanity test for it. Create the test at spec/javascripts/models/party_spec.coffee.

describe "Party", ->
    it "can be initialized", ->
        expect(-> new PartyTimeApp.Party()).not.toThrow(new TypeError("undefined is not a function"));

Run the tests again. This one should fail. Let's add the model to make it pass at app/assets/javascripts/models/party.coffee:

class window.PartyTimeApp.Party extends Backbone.Model

Voila - your test passes and your model is linked in.

Client-side HAML

We're going to use HAML for our templates. Before we get there, let's just fast forward a little and add a bit of server-side stuff to render the page. Update the Gemfile to add rspec and haml:

gem 'haml'
group :development, :test do
  gem 'rspec-rails'

Run bundle install and rails generate rspec:install to install rspec.

Now let's generate a server-side Party model:

rails g resource party
rake db:migrate

Create a view to initialize our javascript application at app/views/parties/index.html.haml:

    new PartyTimeApp()

We're also going to add a couple of attributes to the javascript Party model which we'll use later. Update app/assets/javascripts/models/party.coffee:

class window.PartyTimeApp.Party extends Backbone.Model
        location: "Partyville"
        dancers: [
            {gender:'male', name:'Fred'}
            {gender:'female', name: 'Jane'}
            {gender:'male', name: 'Bart'}

Now we have all that setup we can start adding in the client-side HAML. the haml_coffee_assets gem works well, so add it to the Gemfile and run bundle install:

group :assets, do
  gem 'haml_coffee_assets'

Add a reference to the hamlcoffee javascript in your app/assets/javascripts/application.js just before backbone:

//= require hamlcoffee
//= require underscore
//= require backbone

If you have a rails server running still, stop it and restart it. You should always do this if you update your Gemfile, and it will definitely break if you don't here.

Let's create a simple party view at app/assets/javascripts/views/party_view.coffee:

class window.PartyTimeApp.PartyView extends Backbone.View
    id: "party"
    render: ->
        template = JST['party_view']( @model.toJSON() )

We now add our HAML template at app/assets/javascripts/templates/party_view.hamlc. This is just normal haml being rendered using the haml_coffee_assets gem

%h1 Party at #{@location}
        %h3 The Band
        .instrument Drums
        .instrument Guitar
        .instrument Guitar
    %h3 The Crowd
    -for dancer in @dancers
        .dancer{class:dancer.gender}= dancer.name

We need to create a router that renders our view. Here's the code for app/assets/javascripts/routers/router.coffee:

class PartyTimeApp.Router extends Backbone.Router
        "": "index"
    index: ->
        party = new PartyTimeApp.Party()
        view = new PartyTimeApp.PartyView(model:party)

We need to update the code in app/assets/javascripts/party_time_app.coffee to initialize the router.

class window.PartyTimeApp
    sanity: -> true
    constructor: ->
        new PartyTimeApp.Router()

Hit http://localhost:3000/parties and we have a party!!

A footnote

Obviously, the examples here are quite contrived, but this process was pretty much exactly what I used to get things working in my proper application. It should get you on the right steps. To move forward, read the documentation of the various projects - it's all very thorough. If you're still stuck, read the source code! There are some great tutorials out there - I learnt backbone from Peepcode. If you have any questions, please leave a comment or email me!

Share This Post



Luis Hurtado

Just want to thank you for this article Jeremy! Keep them going.


Jeremy Walker

Thanks for taking the time to comment, Luis. Your encouragement is very appreciated.


Michael Kessler

Thanks for the nice article, this will help people to get started with Backbone, Haml-Coffee and Jasmine. Since you're using Jasminerice, I suggest you to have a look at guard-jasmine, which plays nicely with Jasminerice and you'll have automated headless Jasmine specs in your console with PhantomJS (and on your CI server as well).


Tor Ivry

Great stuff


Mehul Kar

Been upgrading a Rails 2 app to Ember.js and went through something similar. Can't wait to try this out after work today or this weekend. Thanks for writing.
PS, I don't know much about backbone, but I know Ember is moving really fast so examples like these are outdated in a week or so. If the same is true for backbone, it might be helpful to mention version number, etc.


Nell Shamrell

Very cool. I really like the overall look at various front end engineering tools and how you made them work together. This will definitely help me in my apps!


Bernd Ustorf

Have been wanting to try this stack for a while, but I've been way to lazy to figure it out myself - now I have no more excuse. Cheers!



Thanks for this useful post. Unfortunately, I am unable to get jasmine to find the the first test.. Seems like jasminerice/jasmine is not picking up the spec in spec/javascripts/party_time_app_spec.coffee

The localhost:3000/jasmine page reports "0 specs, 0 failures"

How would you troubleshoot this?


Jeremy Walker

Thank you all for your encouragement. I appreciate you taking the time to leave comments.

Mehul - I have updated the post to mention the version. Good idea!
Tom - I'll contact you privately to help debug.



Thanks for the post. I have the same problem as Tom, so if you found a solution please let me know.


Mihai Tarnovan

Forget my last post, I was just stupid and placed the tests in test/spec instead of just spec/. It works now.



Really good article. Quite chuffed too to find out you live in good old Brum. I graduated from Uni there many moons ago and am pretty curious about the state of the tech scene there.
Keep up the good work!



Thanks for this walkthrough. Very helpful.

Jeremy, how did you fix Tom's issue? I'm having the same result.


Sean Hussey

I figured out my issue and I wanted to post in case someone else had the same problem.

It turns out that PEBKAC. The way my browser rendered the page, this code snippet was blank on the page:

#=require ../../app/assets/javascripts/application
#=require_tree ./

So I checked the source. Without thinking, I assumed the "#=" was what was making the code snippet invisible, like a comment.

I eventually figured it out and everything is running fine now.



Great post. Thank you.


Chris Salzberg

Great article! I setup the same stack using exactly the same gems etc. before I read this email, probably around the time this article was posted. I remember having some of the same issues as others here, but finally solved them and now I'm really happy with the setup. Glad to see that others are doing the same thing.


David Julia

Thanks for this post! Was just what I needed to get me started!



Fantastic, thanks for the thorough post!

Post a Comment

I'd love to hear your thoughts :)