Sunday 1 April 2012

Adding backbone to Rails 3.1 part one

RAILS Y U NO GROW A SPINE

Since the first time I used (okay, was forced to understand and use) Backbone.js I was completely mesmerized by it. MVC architecture driven by one of the most powerful (author's opinion) languages out there? Yes. Please.

However the project I was working on used Backbone as an addition, supplement... a non-stand-alone add-on (if you prefer game terminology) to existing Rails 3-ish architecture. Because Backbone has become very popular - in what is to be perceived as a very short period of time (relative to other technologies) - there are many great tutorials - including official ones - on how to start building a complete Backbone-based MVC application.

...but I could't find anything concrete on how to add full Backbone support to a Rails app, while still keeping it a Rails app. How to replace all the awful unobtrusive JavaScripts with beautiful Coffeescripts governed by a smart framework.

A friend of mine (@michaltaberski) helped me to understand the concepts, but after that it was good-ol' copy-paste from the projects I worked on... and even better-older trial-and-error.



So let's start where all good things start.
No, not in a bar.
At the beginning.


Adding Backbone to an existing Rails 3.1 app

At the beginning there was an ordinary Rails 3.1 application. It had an asset pipeline and all that good jazz you'd expect from 3.1. Among the most important gems, I guess, there would be:

gem 'rails', '3.1.3'
gem 'haml-rails'
gem 'jquery-rails'
gem 'json'
gem 'rails-backbone'
gem 'therubyracer'

Of course I ran all the required rails g foobar:install generators if there were any (and there definitely was one for Backbone). It added STUFF to my app/assets/javascripts/application.js and also created a directory tree under app/assets/javascripts/backbone/. There's also a very important file app/assets/javascripts/backbone/doomhub.js.coffee.
Now I wasn't really sure if I was too happy with it, but I really don't want this blog entry to evolve into "where-your-files-should-go" discussion, so I let them be.

At this point I could run the rails server, view the app and tell right away (by calling Backbone from the JavaScript console in a browser), that the framework is included.
Now I needed a way to make it fire up proper backbone-actions in proper backbone-routers, in response to rails server requesting rails-actions.
How to connect two different MVCs; how to force one of them to - basically - be a slave of the other one? There are probably so many ways to do this, and probably even more really ugly ways to do this. I'm quite happy with the solution @michaltaberski suggested.



Making Backbone run automatically

Have your application.html.haml body tag include some additional data:


%body{:data => { :controller => controller_name, :action => action_name  }}


This will serve as an "anchor" for Backbone runner class (created manually in... just a moment), which will read the controller/action parameters, and push them to a corresponding router/action combo.

Next I created the Initializer (runner) class file in app/assets/javascripts/backbone/initializer.coffee:

class @Doomhub.Initializer
  constructor: ->
    alert 'oh my god yes'


Now I had to find a way to run it on every Rails request. application.js seemed like a good place, but when I added new Doomhub.initializer() or anything along those lines in there, it was ignored, tho' the same thing called in JavaScript console created an Initializer properly.
Then I felt that even if I did manage to find how to run it from application.js, that wasn't really a good place for this code to be. I knew a better location.


I took  app/assets/javascripts/backbone/doomhub.js.coffee and modified it like so:

#= require_self
#= require_tree ./templates
#= require_tree ./models
#= require_tree ./views
#= require_tree ./routers

@Doomhub ?= {}
@Doomhub.Routers ?= {}
@Doomhub.Views ?= {}

$ ->
  init = new Doomhub.Initializer()


This was more like it. The code felt in place and - most importantly - it was working.


...and I also removed the js portion of the file name, because I hate it.
Then I renamed all my files to pure coffee and it felt great.
application.js required translation to Coffeescript format to make it work, so I changed all "//" into "#/"... and that was it.

Earlier in this "chapter" we made controller_name and action_name accessible through data attribute in the application's main layout's body tag. Now why would we do that, if we didn't plan on using it later?
Let's modify the Initializer class:


class @Doomhub.Initializer
  constructor: ->
    @controller = $('body').data('controller')
    @action = $('body').data('action')
    console.log(@controller)
    console.log(@action)


In the next episode we'll modify the Initializer to automatically use a Router-based structure, with one main general Router ...routing requests to other Routers below it, to finally call a desired action.
We will also create a handy Helper class.

Ta-ta.

[Read part two here: Adding backbone to Rails 3.1 part two]

2 comments:

  1. Thank you for this, just what i was looking for!

    ReplyDelete
    Replies
    1. Hey, if you need more help with running BB.js along with Rails drop me a line. I've learned more elegant ways to do this since I wrote this post.

      Delete