Monday
Jan182010
Notes on Using Jammit with Rails
Monday, January 18, 2010 at 6:42AM
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>
tagged
jammit,
rails in
Rails Articles
jammit,
rails in
Rails Articles 
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.
Help yourself. Happy to be a resource.
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.
So, this seems straightforward enough, but does it actually work for you folks?
Apache's Content Negotiation/MultiViews docs note that:
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!
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.
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.
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.