The Blog

The what's what of the Flowdock atmosphere.

Rails 3.1 Asset Pipeline in the Real World

Ville Lautanala June 14th, 2011

The new asset pipeline is the single most important new feature in Rails 3.1. DHH used most of his keynote at RailsConf 2011 to explain its new features. In this blog post, I’ll go through its implications to an existing application and explain how we integrated it to our workflow at Flowdock.

The Rails 3.1 asset pipeline integrates well with CDNs like Amazon’s CloudFront: just point your distribution to your web server. Most likely, you won’t need your old hacks for assets.

What’s new

The new asset pipeline in Rails 3.1 makes assets first class citizens in Rails. Assets are moved from public directory under apps/assets and they receive much more attention from Rails. Asset files can inline other asset files using requires and gem dependencies can provide asset files for the application to use.

This means that you don’t have to include jquery.js to your repository. Generators don’t have to worry about creating proper static assets under your public folder. Assets from other sources work just like those which are under your app/assets folder.

The static asset URL scheme has changed. Compiled assets are served under /assets. In production mode, generated files include an MD5 hash as part of the file name, which is a better cache buster than the previous timestamp scheme. Asset pipeline also generates URLs inside stylesheets so that they can link to generated files.

A stylesheet stored at app/assets/application.css will be served as /assets/application.css. Most likely, this layout will contain dependency declarations such as inclusion of CSS reset. Thus, your new application.css would become

/*
 *= require blueprint/reset
 *= require blueprint/typography
 */

body {
  background: url(<%= asset_path "sunshine-rainbows-and-lollipops.png" %>);
  /* You have to use ERB to link to asset pipelined resource */
}
/* Rest of stylesheet */

At the heart of the pipeline is Sprockets 2.0. In addition to dependency management, Sprockets will allow any number of filters to be used with asset files. By adding extensions to the files names one can control which filters are used. For example, it is possible to run a file through SASS and ERB by naming it stylesheet.css.scss.erb. JS files can also be filtered using both CoffeeScript and ERB.

Among the more controversial changes, CoffeeScript and SASS are now included in the default template. It is possible to use both CoffeeScript and SASS with old style assets. Not much has changed in this respect, DHH just is more vocal about what you should use.

Deal with it

Additionally, support for JS minification comes built-in with Rails. The default is to use UglifyJS via the Uglifier gem. Existing apps can use this by adding it in their Gemfile and setting the minificator in the (production) environment configuration.

Gemfile:

gem "uglifier"

config/enfironments/production.rb:

config.assets.js_compressor  = :uglifier

Upgrading existing application

Running rake rails:update will show how your configuration files should be updated for them to work with new asset pipeline. At a bare minimum, you’ll only need to add

config.assets.enabled = true

to your application.rb. After that stylesheets, javascripts and images can be moved under app/assets.

There are a few things to change to make your assets work. All absolute paths to images in stylesheets and asset helpers are broken and should be replaced with call to relative helpers, e.g. image_path /images/subdir/rails.png should be asset_path "subdir/rails.png". This way Sprockets will be able to figure out where the asset is served. It is especially neat in production where file names will have MD5 hash suffix for more efficient caching.

You’ll also want to put this to your .gitignore file:

.sass-cache/
public/assets/

Serving assets in production

Serving assets in production environment is much more pleasant with the new pipeline. First of all, instead of a timestamp, an MD5 hash is used to bust caches. Second, this is not part of the query string but used as suffix in file name. These MD5 suffixed files really exist and you can have multiple versions coexist at the same time.

Because the URIs are unique not only by query string but also file name, CloudFront can be easily used to serve your assets. Create a new custom origin distribution with your host as origin DNS and use the CloudFront domain as your asset hosts. That’s really all you need barring some web server configuration: check CloudFront custom origin best practices.

One thing to notice is that the assets should be precompiled before your site goes live to reduce load. There is a rake task for this, @rake assets:precompile@. We put this in our deploy.rb:

before :"deploy:symlink", :"deploy:assets";

desc "Compile asets"
task :assets do
  run "cd #{release_path}; RAILS_ENV=#{rails_env} bundle exec rake assets:precompile"
end

If you have multiple CSS and JS files, you might have to specify manually which of them are precompiled by adding them to the config.assets.precompile array.

Compiling your assets require that you have a JS runtime available if you use either CoffeeScript or Uglifier. We use therubyracer, but your mileage may vary. Installing node.js is also a good alternative.

Retrospective

Is this really better than the old setup? Did we learn anything?

First, the development and production setups are now more similar. Previously multiple JS files containing our application code were loaded in development, but now they’re concatenated to one big file. We are more likely to run into weird bugs before code is deployed to QA, but on the other hand line numbers aren’t meaningful for debugging purposes. When using CoffeeScript, this point is moot.

Second, we are happy that we could remove our old hacks to get Flowdock deployed. Instead of the combination of patched cloudfront_asset_host gem and some other hacks, we can compile our assets to be served via CloudFront using the built-in rake task.

Third, our deployment times were reduced by over a minute as we don’t need to sync our asset files with the S3 bucket.

2D Fisheye with JavaScript

Mikael Roos December 21st, 2009



Well, it’s almost Christmas! With the holidays getting closer, I went through some dusty files and stumbled upon an oldie but goodie: a 2-dimensional fish eye element (technically it’s also quasi-3d, but whatever). It uses Prototype and Scriptaculous and is available on Github here.

It’s something I actually needed to write for work over a year and a half ago – can you imagine?

Merry Christmas to all from everyone at Nodeta!

Here’s the thing with chrismassy icons!

The free Christmas icons can be found from here.

iPhone browser more popular than Internet Explorer

Otto Hilska June 17th, 2008

We’ve been feeling sorry because we didn’t pay much attention to rails-doc IE compatibility. Today I had a look at the server statistics, and it seems that even the iPhone browser is more popular than Internet Explorer. :) Good to see that Rails developers have such a good taste.

Anyways, today is the Firefox 3.0 download day:

Download Day

Go and get it! The super-fast JavaScript interpreter is going to enable lots of new possibilities in web UIs. I’m really looking forward to see stuff like SproutCore used in different kinds of applications.

PS. The upcoming Rails-doc.org JavaScript search is already going to benefit from the upgrade. ;)

Undefined success with Prototype

Mikael Roos April 15th, 2008

I noticed something really strange in the Prototype API today after a fellow developer came smirking to ask me why an export action seemed to quickly success after he had dropped the whole server. I tried to reproduce the situation and surely enough, the onSuccess callback ran when there effectively was no response. I immediately perused the Prototype API documentation and to my amazement read this description for the onSuccess callback:

Invoked when a request completes and its status code is undefined or belongs in the 2xy family. This is skipped if a code-specific callback is defined, and happens before

onComplete

.

Did you notice that? “Invoked when a request completes and its status code is undefined…” What on earth!? After some googling, I found out that this is no new discovery and that there’s more to this problem than you’d think.

Different browsers handle the situation differently, and so, what eventually ends up in the status field of the response object, varies greatly depending on the browser and the protocol. Since there is no standard XMLHTTPRequest, there is no standard behavior for a browser regarding what should be passed to the JavaScript response object. In the olden golden days of standard HTTP requests there of course is no problem because there just is no response.

Apparently, when a network problem occurs and no actual response is received, Firefox and Opera (most versions) pass an undefined status with status code 0, IE passes its own proprietary status codes in the 1000-range and Webkit/Safari, in addition to the occasional status code of -1004 (!), also passes a status code of zero. Other problems are brought on by the use of file protocol.

There has been a few suggested patches (1) (2) (3) (4), and the status quo will hopefully change soon.

In the meantime, you can solve the problem in at least these ways:

  1. Use the on0 callback (that’s “on”-zero) to separately handle the cases when the browser passes a status code of zero to the AJAX Response object. The fired on0 will block the onSuccess from firing.
  2. Forget all the different completion-related onXXXX callbacks and simply only use the onComplete and manually check if the status code pleases you – something along the lines of this:
    new Ajax.Request("/path", {
      onComplete: function(response) {
        if (response.status==200)
          alert('Very successful!');
        else
          alert('Far less successful');
      }
    });