Monday, May 30, 2011

Managing coffeescript/js dependencies with the Rails 3.1 asset pipeline

We've been using and loving coffeescript and backbone on my current project.  In the last couple days we've upgraded our project to rails 3.1rc1 and been able to take advantage of the new asset pipeline therein.  This feature elegantly solves some super annoying problems we've had with managing dependencies in our app, but is not yet well documented.  I thought I'd take a few minutes and share what I've learned.

Where the assets live

Before rails 3.1, we'd lump all our javascript and stylesheets in the "junk drawer" that is public.  No more.  Now they can go in 3 different dirs depending on their purpose:

  • app/assets for things that our your application code
  • lib/assets for shared-ish things (not sure I grok what I'd put here yet)
  • vendor/assets to put things that your app uses but is provided by others
These directories are magically put on the asset pipeline for you.  In your views, you can reference things in any of these directories under the prefix /assets, eg. if foo.js was in app/assets (or lib or vendor) you would refer to it as /assets/foo.js.  The rails helpers such as javascript_include_tag will do this for you.

So this gives places to put stuff, which is nice, but only the beginning.  The killer features (for me) are the ability to package assets and manage dependencies.  To see how this works, take a look the app/assets/application.js file you get when generate a new rails 3.1 app.

Nothing but comments, weird huh. That's because this is what we're calling a "manifest file" which is basically just a file that requires in other files. When you include /assets/application.js at runtime, rails will package all the files you required and concatenate (and optionally minimize) them. Let's look at those last 3 lines, as there's magic in them thar comments. The first 2 require in jquery and jquery_ujs. It seems like this must mean that there are jquery.js and jquery_ujs.js files somewhere in one of the assets directories, but this isn't so. That's because gems can contribute to the asset pipeline as well. More on this later. The last line says to include all files in this directory or subdirectories as well.  In our apps so far we find it nice to have 2 such manifest files, vendor/assets/javascripts/vendor.js and app/assets/javascripts/application.js.  Not sure I can call this "best practice" or not yet, just that it seems nice to us so far.

It's also worth pointing out that files that need to processed (eg coffeescript and sass) will be handled automatically as well. Simply drop a file named into the /app/assets directory and it will be compiled and included into application.js

Where this all gets more interesting to me is where we have files in the application that depend on each other. Files that are required can have requires of their own. The example I have in my app is coffeescript class inheritance. Imagine a couple classes like so:

When you're building apps with a lot of front end code, it's to be able to organize code with each class in it's own file. But this means you have to make sure to have the script that brings in fruit before apple. With the ability to require in rails 3.1, this problem is nicely solved for us. If we add a require statement to like so, the asset pipeline will take care of making sure things are in the correct order:

This is a huge win for me, as before this I was naming files with numbers such as to get around this problem. Yuck.

I mentioned earlier that I'd talk about assets living in gems.  This is a feature that allows you to take front end code and easily share it between multiple projects.  In my next post I'll talk about my experience building a rails 3.1 asset containing gem.

Sunday, May 15, 2011

2 easy things you can screw up in backbone

First off let's be clear.  Backbone is awesome.  It's totally changed the way I develop web applications for the better.  Before backbone, working on javascript code for a rich client web app was a miserable experience.  With backbone (and coffeescript) I'm now enjoyably writing code I can be proud of.  But because we are now developing our apps in a new way, we've found new ways to mess things up ;)  Here are a couple ways we found that could save you some hours of frustration if you avoid doing them.

Don't have multiple views using the same element

This one I did early on in my backbone days the first time I had one view that created another view.  Imagine some code like the following:

Seems ok at first. When we click a button in FooView it creates a BarView giving it an element and telling it to render. The problem happens the second time the button gets clicked. At that point you have 2 instances of BarView which each use the same element. And if those BarViews are listening to events on (or within) said element, mayhem ensues. I've found it to work out better to write the code like so:

In this version the BarView is created during the render method, right after his element has been created presumably. Then the BarView instance is displayed when needed. I've found this to be a good rule of thumb: backbone view objects should have the same life cycle as their elements. They should be created when their elements are created and destroyed when they are removed.

Beware model defaults that initialize complex attributes

The second gotcha I've run into cost us a good few hours today actually.  Backbone models have a nice feature for allowing default attributes.  It's a handy feature, but by using it to initialize array or object properties we found it caused a subtle (to us) but pretty terrible bug.  In our model we had code like so:

The problem here is what happens when you create a second Foo. Turns out backbone does a shallow clone of defaults, which means the same array object in memory is used for both. This spec describes the problem nicely:

Turns out there is a pretty easy fix for this too. Defaults don't have to be an object literal, they can also be a function and backbone will be smart enough to invoke it to build the model's attributes. In coffeescript, this fix is exactly 2 characters:

Hopefully these two tips will save you some frustration as you dig deeper into backbone. In a future post I'll share some of the code we've refactored out of our app that we feel like is generally useful for other backbone + coffeescript + rails apps.