A Fresh Cup is Mike Gunderloy's software development weblog, covering Ruby on Rails and whatever else I find interesting in the universe of software. I'm a full-time Rails developer and contributor, available for long- or short-term consulting, with solid experience in working as part of a distributed team. If you'd like to hire me, drop me a line. I'm also the author of Rails Rescue Handbook and Rails Freelancing Handbook.

Navigation
« Double Shot #629 | Main | Double Shot #628 »
Monday
Jan182010

Notes on Using Jammit with Rails

I spent some time this weekend wiring up a new Rails application to use Jammit for asset caching. So far I'm quite happy with the results; it offers a sophisticated set of asset caching strategies (indeed, we're not even making use of them all yet) and does its compression on the client side during the deployment process. For those who aren't complete experts in all things sysadmin, a few notes on making this work: 1) If you're using the rails_xss plugin (or Rails 3), you'll need to make sure it doesn't try to escape the HTML generated by the Jammit helpers. You do this by wrapping all calls to those helpers in the raw helper:

<%= raw include_stylesheets(:common, :media => 'all') %>
<%= raw include_javascripts(:common) %>  
2) For deployment, I'm using this chunk of code in my Capistrano recipe (hat-tip to kpumuk whose gist I started from):

namespace :deploy do
  desc 'Bundle and minify the JS and CSS files'
  task :precache_assets, :roles => :app do
    root_path = File.expand_path(File.dirname(__FILE__) + '/..')
    assets_path = "#{root_path}/public/assets"
    gem_path = ENV['GEM_PATH']
    run_locally "#{gem_path}/bin/jammit"
    top.upload assets_path, "#{current_release}/public", :via => :scp, :recursive => true
  end
end
after 'deploy:symlink', 'deploy:precache_assets'
This will build the assets locally and then upload them. You'll want to add public/assets to your .gitignore file. 3) You should have mod_expires loaded in Apache to handle far-future expiration dates for asset access. If this is missing, you'll get an error when you try to load a configuration file that includes the ExpiresDefault directive. On Debian, this is as simple as moving expires.load from /etc/apache2/mods-available to /etc/apache2/mods-enabled (which is what a2enmod does). If you're using some other flavor of OS, try this article for instructions. OS X includes mod_expires by default. 4) The Passenger documentation says flat out that MultiViews is incompatible with Passenger. So far, I haven't found any problem enabling MultiViews just on the assets directory, though, probably because it doesn't go through Passenger. 5) Here's a vhosts entry that sets things up for a Jammit-enabled application:

<VirtualHost *:80>
   ServerName myapp.com
   RailsEnv production
   DocumentRoot /u/apps/myapp/public
   <Directory "/u/apps/myapp/public">
      Options FollowSymLinks
      AllowOverride None
      Order allow,deny
      Allow from all
   </Directory>
   <Directory /u/apps/myapp/public/assets>
      Options MultiViews
      ExpiresDefault "access plus 1 year"
   </Directory>
</VirtualHost>    

Reader Comments (7)

Thanks for the handy tips, Mike. I hope you don't mind if I link to this from the Jammit documentation -- there's been a couple of questions about integration with Capistrano, and this is a handy overview.

January 18, 2010 | Unregistered CommenterJeremy Ashkenas

Help yourself. Happy to be a resource.

January 18, 2010 | Unregistered CommenterMike Gunderloy

Thanks. This solves an issue I was having with Java not being available properly on my hosts. Now I can ensure they get packaged properly.

January 21, 2010 | Unregistered CommenterJGeiger

So, this seems straightforward enough, but does it actually work for you folks?

Apache's Content Negotiation/MultiViews docs note that:

if the server receives a request for /some/dir/foo, if /some/dir has MultiViews enabled, and /some/dir/foo does not exist, then the server reads the directory looking for files named foo.* ...

Jammit of course generates plain .css, etc. files in public/assets to serve to clients that don't accept gzip, so it would seem that this all doesn't really work out of the box with MultiViews.

Pseudo-test:


$ curl -I -H "Accept-Encoding: gzip" http://www.example.com/assets/example.css
HTTP/1.1 200 OK
Date: Tue, 02 Feb 2010 16:45:34 GMT
Server: Apache/2.2.9 (Ubuntu) Phusion_Passenger/2.2.9
Last-Modified: Tue, 02 Feb 2010 01:54:56 GMT
ETag: "6e799-1cf8a-47e9463795c00"
Accept-Ranges: bytes
Content-Length: 118666
Cache-Control: max-age=31536000
Expires: Wed, 02 Feb 2011 16:45:34 GMT
Content-Type: text/css

No gzip love there. It does seem to work if the assets are named example.css.en and example.css.en.gz, if jammit can be coaxed to generate a structure like that without messing up datauri paths, etc... Otherwise it becomes a hackish exercise in rewrite rules to serve gzipped files but not when the client doesn't support it.

Unless I'm missing something!

February 2, 2010 | Unregistered CommenterChes Martin

I've been really enjoying Jammit and this Capistrano script, but something strikes me as dangerous about it. Let's say you're in a development branch or an experimental branch when you get the go-ahead to deploy master to production, this script will grab the assets from your current branch and upload those instead of using the assets from the deploy/master branch. So be careful.

February 15, 2010 | Unregistered CommenterJon Crawford

Good point, Jon. I always make a point of checking out the branch that I'm going to deploy, so not an issue for me, but definitely something to be aware of.

February 15, 2010 | Unregistered CommenterMike Gunderloy

I have the same impression as Ches -- the gzipped files are simply ignored with this setup. If you have apache doing the deflating, then it might appear to be working but it isn't using the Jammit files.

April 1, 2010 | Unregistered CommenterMark

PostPost a New Comment

Enter your information below to add a new comment.
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>