Header_image
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!

Rails

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

Jasmine

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'
end

group :development, :test do
  gem "jasminerice"
end

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()
        expect(app.sanity()).toEqual(true)

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.

Backbone.js

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'
  #...
end

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:

:javascript
    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
    
    defaults:
        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'
  #...
end

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() )
        $(@el).html(template);
        this

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}
.discoball
.stage
    .band
        %h3 The Band
        .instrument Drums
        .instrument Guitar
        .instrument Guitar
.floor
    %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
        
    routes:
        "": "index"
        
    index: ->
        party = new PartyTimeApp.Party()
        view = new PartyTimeApp.PartyView(model:party)
        $('body').append(view.render().el)

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()
        Backbone.history.start()

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

Comments

Bab77f8b3dc06dbd8c941258d542e716

Luis Hurtado

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

955103d80e386882143a78875fb727bf

Jeremy Walker

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

F7badb59154ee74f13e1586e94c73d8d

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).

8fc2d845aba2fb41d1d4910e2eca79ab

Tor Ivry

Great stuff

57c2dff3cf681f22f9519a18bafc7bce

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.

Dfa87bfee80d8c219fd8c5747eddfc8c

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!

77b4c59d3fe25662098f6b1e18dafd19

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!

Bb3447bf58a68fe4b2633fc79143aef5

Tom

Jeremy,
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?

955103d80e386882143a78875fb727bf

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.

393b2a86666b668aaf855a46ae75045e

Mihai

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

393b2a86666b668aaf855a46ae75045e

Mihai Tarnovan

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

Efe90c21899f95a64cb78e01b5cb7733

Ed

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!

69e8297645d592bc60a1bd52daea003c

Sean

Thanks for this walkthrough. Very helpful.

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

69e8297645d592bc60a1bd52daea003c

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.

D1afaef7c05ff4f300c3d8092d4fdfc7

tankwanghow

Great post. Thank you.

B30130824fb515fb4c69c6101aca32fb

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.

B436b84edaeb7f75a55912a3807581cd

David Julia

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

6ec7697ec27284826a651f7a603d1825

Norman

Fantastic, thanks for the thorough post!

Post a Comment

I'd love to hear your thoughts :)