Far Future Expires Headers for CSS Images in Rails

Posted by paul
on 28 September 2009

Setting far future expires headers for your static assets noticeably improves the speed at which pages load and is easy to setup in Rails thanks to asset timestamps. All you need to do is:

  1. Use image_tag, stylesheet_link_tag, javascript_include_tag and friends in your views.
  2. Update your web server config to set the correct headers.

One problem that's not immediately obvious here is that while your web server is happily adding expires headers to the images you're using in CSS, those requests don't have the timestamp in the query string so the browser will continue to use the cached version after you have updated the file.

One solution is to instruct the web server to only add the expires header to requests that do include the asset timestamp. While this might be ok if you only have a few images in your CSS, if most of your images are specified this way then you're still not making the most of the browser's cache which was the whole point of the exercise in the first place.

An alternative solution, and the one we took on Nurphy, is to use ERB templates to generate your stylesheets, and use the Rails' helpers to get asset timestamps in your CSS. It's dead easy, here's how:

Create a Stylesheets Controller and a Matching Route

class StylesheetsController < ApplicationController
  caches_page :application, :iphone
end
map.connect "/stylesheets/:action.css", :controller => "stylesheets", :format => "css"

This is just about as simple as you can imagine. We're using page caching to cache the stylesheet to disk, once rendered it will be served like any other static asset.

Move Stylesheets to the Views Directory

mv public/stylesheets/application.css app/views/stylesheets/application.css.erb
mv public/stylesheets/iphone.css app/views/stylesheets/iphone.css.erb

Use the image_path Helper

Wherever you reference an image in your stylesheet, use the image_path helper like so:

body { background: url(<%= image_path('fade.png') %>); }

When the template renders this will output something like:

body { background: url(/images/fade.png?1253089219); }

And that's all there is to it. After each deploy the cached stylesheet will disappear and the next time it's requested it will be regenerated with new timestamps, which in turn will cause the browser to fetch fresh copies of your images.

A Note on Asset Hosts

We're also using an asset host, for a couple of reasons:

  1. A separate host for static assets can itself improve page load speed.
  2. We can specify far-future headers on anything served from the asset host, safe in the knowledge that the URL will have been generated using the Rails helpers and will therefore include the timestamp in the query string.

This causes one additional complication with the approach described earlier however. Our asset host doesn't run Rails since it only serves static assets, but the first request for the stylesheet needs to go through Rails so the template can be rendered and written to disk.

Assuming you're using Capistrano, a simple solution is to add a task that fetches the stylesheets from the Rails host after each deploy. Adding this to deploy.rb will do the trick:

task :prime_cache, :roles => :app do
  run <<-CMD
    wget --spider http://#{domain}/stylesheets/application.css;
    wget --spider http://#{domain}/stylesheets/iphone.css
  CMD
end

after "deploy", :prime_cache

Another Take on jQuery, Rails and respond_to

Posted by paul
on 08 May 2009

I been using jQuery to do AJAX with Rails a lot recently, and I love it. When I was starting out however, I hit the same problem that seems to trip up everyone: AJAX requests made from jQuery don't trigger format.js (or wants.js if you're cool) within the respond_to block of Rails controller actions.

The most common solution I've seen to this problem involves adding a one-liner to your Javascript to instruct jQuery to add a text/javascript Accept header to AJAX requests. Which works great in FireFox, but not in Safari.

The problem seems to be that in Safari, text/javascript is appended to the existing Accept header, so Rails sees something like text/html, */*, text/javascript which it still interprets as a request for HTML.

An alternate solution that I've been using is to add the following to environment.rb.

config.action_controller.use_accept_header = false

This instructs Rails to use the X-Requested-With rather than the Accept header to determine the request format. Since jQuery sets this header in both browsers, Rails controllers will behave as expected.

One thing to keep in mind is that if your app has an XML or JSON API, you might need to think carefully about whether this is the best way to fix this problem. For a while this became the default in Rails, although that didn't last very long.

Installing Passenger 2.1.1 Beta

Posted by paul
on 07 March 2009

Update: Passenger 2.1.2 (final) has now been released.

The current stable release of Passenger (2.0.6) isn't compatible with Rails 2.3 because of recent changes in Rack. In the app I'm working on, this manifested itself as cookies not been set correctly, making it impossible to log out of the site. The good news is that the latest beta of Passenger includes support for Rails 2.3, you can install it (on OS X) as follows:

curl -O http://phusion-passenger.googlecode.com/files/passenger-2.1.1.gem
sudo gem install passenger-2.1.1
sudo passenger-install-apache2-module

Follow the on-screen instructions on updating your config, restart Apache and you're done.

Rails I18n Translation Lookup in TextMate

Posted by paul
on 17 December 2008

I've been working on internationalizing a Rails app, and along the way I created a simple command to lookup Rails i18n translation keys from within TextMate. You use it like this:

  1. Highlight a translation key. e.g. 'activerecord.errors.messages.inclusion'
  2. Hit Shift-Command-i.
  3. The relevant translation is shown on a tool tip, in this case "is not included in the list".

As well as simple keys, the command also works with any additional parameters you'd pass to I18n.t when using interpolation or the alternate translation key scoping syntax. For example highlighting :greater_than, :scope => [:activerecord, :errors, :messages], :count => 100 before invoking the command would yield "must be greater than 100" on the tool tip. There's also support for replacing local and instance variables used for interpolation with placeholder strings.

Since Sven Fuchs has already started an experimental Rails i18n TextMate bundle I've forked his rails-i18n repository and added this to it. Here's how to set it up:

The command is included in Sven Fuchs' experimental Rails i18n TextMate bundle, here's how to set it up:

git clone git://github.com/svenfuchs/rails-i18n.git
cp -r rails-i18n/tools/ ~/Library/Application\ Support/TextMate/Bundles/
# You also need the i18n gem
sudo gem install mattetti-i18n --source http://gems.github.com

The command has the default locale set to :en and is only loading translations from config/locales/en.yml. If you need to do something different take a look at the command in TextMate's Bundle Editor and you'll see what you need to change to get things working for you.

Scaffolding Nested Resources in Rails

Posted by paul
on 08 October 2008

Creating nested resources in Rails can be a little bit tedious, particularly if you're using Rspec and its scaffolding. Updating the controller isn't really a problem, but making the specs pass involves a bit too much busy work. And then there are the views to take care of, and their specs, and don't forget the routing specs while you're at it.

Now you might suggest that make_resourceful and resource_controller offer a neat solution to this problem. Well, yes, sometimes. Often though, I find these super controllers tend to be a little too DRY for my tastes, at least on some projects. I'm usually happier starting with some generated code and hacking from there.

So what I'd really like to be able to do is scaffold out the nested resource and its specs. A quick look around Github turned up rspec_on_rails_nested_scaffold, a plugin which does just that. It is very nearly perfect for my needs, although I have forked it and updated the templates used for views and specs with those from the latest versions of Rails and Rspec respectively.

You can install the plugin like so:

script/plugin install git://github.com/phorsfall/rspec_on_rails_nested_scaffold.git

If you already have a Post resource you can create nested Comments like this:

script/generate rspec_nested_scaffold Comment --owner=Post post_id:integer body:text

This will generate a CommentsController which includes a before_filter to load the correct Post and has Active Record calls scoped accordingly. You also get views which include the correct links and form actions, along with updated controller, view and routing specs. Before you are finished however, there are a few small things you'll need to do by hand:

  1. Update routes.rb. The generator will add an un-nested map.resources for our nested resource. In our example, we'd remove this and add the following:
    map.resources :posts, :has_many => :comments
  2. Create the Active Record associations in your models.
    class Post < ActiveRecord::Base
      has_many :comments
    end
    
    class Comments < ActiveRecord::Base
      belongs_to :post
    end
  3. If you didn't include a foreign key when you ran the generator, add one to the migration.

With that done, migrate the database and run the specs which should all pass. You now have an app you can go to work on and hopefully you've saved a bit of time along the way.

Thanks to Jeremy Friesen for putting the original plugin together.

Active Resource Callbacks

Posted by paul
on 27 August 2008

It looks like work is still on-going to extract functionality common to Active Record and Active Resource into a new Active Model library. Excellent. But I wanted to add a couple of callbacks to Active Resource and I wanted to do it quickly. Thankfully, someone much smarter than me has already extracted callbacks from Active Record, so getting something working was pretty straight forward.

module ActiveResource
  module Callbacks
    CALLBACKS = %w( after_save after_destroy )
    
    def self.included(base)
      [:save, :destroy].each do |method|
        base.send :alias_method_chain, method, :callbacks
      end
      
      base.send :include, ActiveSupport::Callbacks
      base.define_callbacks *CALLBACKS
    end
    
    def save_with_callbacks
      returning save_without_callbacks do
        run_callbacks(:after_save) unless new?
      end
    end
    
    def destroy_with_callbacks
      returning destroy_without_callbacks do
        run_callbacks(:after_destroy)
      end
    end
  end
end

ActiveResource::Base.send :include, ActiveResource::Callbacks

resourceful_pagination

Posted by paul
on 03 August 2008

I've been doing quite a bit of work recently on an Active Resource project and needed to add pagination to one of our resources. I had a quick look around to see what other people were doing, and while it looks like work is underway to add support for Active Resource (and ORMs other than Active Record) to will_paginate I don't think it's quite ready just yet. So while we wait, may I present resourceful_pagination.

If you want to dive straight in, take a look at the readme then install the gem with:

sudo gem install phorsfall-resourceful_pagination --source http://gems.github.com

With the gem installed you get a shiny new ActiveResource::Base#paginate method. Use it like this:

MyResource.paginate(:page => params[:page])

This returns a WillPaginate::Collection which you can use with will_paginate's view helpers as usual.

Easy, right?, Well, sort of. The gem makes a few assumptions about your Active Resource class and your RESTful resource. If you want to use this you'll probably need to make a few changes. First you need to add two class methods to your Active Resources.

  1. A count method which returns the total number of items. This could call a custom count action on the app server.
  2. A per_page method indicating how many records you require per page.

Something like this would do the trick:

class MyResource < ActiveResource::Base
  @@per_page = 10
  cattr_reader :per_page

  def self.count
    get(:count)['count']
  end
end

You also need the index action of the controller on the app server to handle the :starting_at and :per_page parameters. These map to :offset and :limit in SQL and can be passed straight through to Active Record.

MyResourcesController < ApplicationController
  def index
    MyResource.find :all, :limit => params[:per_page], :offset => params[:starting_at]
    # Render XML etc.
  end

  def count
    count = MyResource.count
    render :xml => { :count => count }
  end
end

And that's about it. There's a little bit more to do if you have a nested resource, but there's a bit more on that in the readme.

Enjoy.

An include_any Matcher for RSpec

Posted by paul
on 29 June 2008

Writing custom RSpec expectation matchers is a great way to enhance the readability of your specifications. As an example, here's a simple matcher to check whether a collection contains any of the given items. An example will make things clearer:

# Passing example
odds  = [1,3,5,7,9]
evens = [2,4,6,8]
odds.should_not include_any(*evens)

One thing to note is that include_any expects multiple arguments rather than a collection, hence the splat. Here's the code:

module Spec
  module Matchers
    def include_any(*args)
      IncludeAny.new(*args)
    end
  
    class IncludeAny
      def initialize(*args)
        @args = args
      end
    
      def matches?(target)
        @target = target
        @target.include_any?(*@args)
      end
    
      def failure_message
        "expected #{@target.inspect} to include one or more elements from #{@args.inspect}"
      end
    
      def negative_failure_message
        "expected #{@target.inspect} not to include any elements from #{@args.inspect}"
      end
    end
  end
end

This is about as trivial as a custom matcher gets. All of the work is done by the include_any? method, which doesn't actually exist as part of the core Ruby API. In fact it's a simple extension to Enumerable that I think I originally borrowed from Merb. Put the following in config/initializers/core_extensions.rb or similar to use it in a Rails app.

module Enumerable
  def include_any?(*args)
    args.any? { |arg| self.include?(arg) }
  end
end

Which custom matchers are you using?

How to Bypass attr_accessible and attr_protected in Rails

Posted by paul
on 25 June 2008

Mass assignment in Rails is super handy and saves heaps of code, especially in your controllers. As everyone knows however, hackers love mass assignment so you really need to use attr_accessible or attr_protected to protect your models.

But what happens if you want to mass assign model attributes which are otherwise protected? It would be simple enough to create your own method to do this, but if you take a look at the source you'll see that Rails already has it covered. Here's the method signature for ActiveRecord::Base#attributes=:

def attributes=(new_attributes, guard_protected_attributes = true)

That optional second parameter lets you bypass attr_accessible and attr_protected exactly as we'd like. But how do we call this? Well, you use send.

@model.send :attributes=, attributes, false

Contributing to Rails

Posted by paul
on 20 April 2008

Encouraged by a couple of kind comments I submitted a patch for the change I wrote about in my previous post, and as of yesterday you can now do something like this in edge Rails.

caches_page :index, :if => Proc.new { |c| !c.request.format.json? }

I'd not used Lighthouse before and I'm still getting my head around everything Git. If you're in a similar position, you might find these links useful too:

Conditional Page Caching in Rails

Posted by paul
on 13 April 2008

Update 20/04/2008: I submitted a patch and this is now in edge Rails

When I created the JSON feed for my deadtre.es blog badge, one thing I didn't take care of was caching. As the contents of the feed will always be the same for a given URL no matter which user makes the request, it's a prime candidate for page caching. All I need to do is add caches_page :index to my controller, and Rails takes care of caching the index action for me. However, there's a problem here as the HTML format index action will also be cached, and unlike the JSON feed this does vary based on the logged on user (if you're looking at your own bookmarks you get edit and delete links for example) and so it isn't as suitable for page caching. What I'd really like to do here is to specify a condition that will be evaluated before the page is cached, much as the session method does.

caches_page :index, :if => Proc.new { |c| c.request.format.json? }

Unfortunately, a quick look at the docs for caches_page tells me that it doesn't support this. But looking at the source code I can see that caches_page is really just a nice shortcut for setting up an after_filter to perform the caching. So, instead of calling caches_page I could do this directly myself, and check the format of the page requested so that only the JSON feed is cached:

after_filter(:only => :index) { |c| c.cache_page if c.request.format.json? }

Now, although this works, it doesn't check that caching is enable (as caches_page does) and it's also not that pretty. Instead, I decided to override caches_page by dropping the following in my ApplicationController.

def self.caches_page(*actions)
  return unless perform_caching
  options = actions.extract_options!
  after_filter(:only => actions) { |c| c.cache_page if options[:if].nil? or options[:if].call(c) }
end

This is pretty similar to the original caches_page except that it evaluates any :if proc passed in before caching the page. Now, I can now call caches_page with the syntax I originally wanted:

caches_page :index, :if => Proc.new { |c| c.request.format.json? }

Job done.

Really Simple Blog Badges in Rails 2.0

Posted by paul
on 27 March 2008

When I wanted a quick (and arguably dirty) way to show my current reading list from deadtre.es on this blog, I figured that it was time to play with the JSON support which was baked in Rails 2.0. Wanting to keep things super simple, all I wanted to do was:

  • Create a simple JSON feed I can include via a <script> tag. (I'll write a simple Javascript callback which will insert my reading list into each page. If I decide to make this easier for users I'll put together the usual configuration page where people can play with the look and feel of their badge and get some code to copy and paste.)
  • Keep things RESTful. Each user already has a URL for their bookmarks so I really wanted to avoid to avoid creating new controllers or actions.

Cutting to the chase, this pretty much turns out to be a one-liner in Rails. Here's an example:


class BookmarksController < ApplicationController
  session :off, :only => :index, :if => Proc.new { |request| request.format.json? }
  
  def index
    # fetch bookmarks ...
    respond_to do |format|
      format.json do     
        excluded_book_attrs = [:aws_domain, :id, :created_at, :updated_at]        
        excluded_bookmark_attrs = [:book_id, :id, :updated_at, :user_id]
        render :json => @bookmarks.to_json(:except => excluded_bookmark_attrs, :include => { :book => { :except => excluded_book_attrs }}), :callback => 'show_deadtrees_books'
      end
    end
  end
end

It doesn't get much simpler than that! There are a couple of things worth pointing out:

  • render :json takes care of setting the content type to application/json in the reponse header. Additionally, it wraps the JSON in a call to the method specified by the :callback option. (I should allow users to override this via the querystring to avoid name conflicts in their Javascript or to remove the callback altogether to support use outside of a <script> tag.)
  • You can include Active Record associations when calling to_json using :include
  • You can also control which attributes are serialized (including those on associations) using the :only and :except options.
  • I've disabled sessions for JSON requests to the index action since they're not required and carry a reasonable overhead.

With that in place, all you need to do is implement your Javascript callback (borrow mine if you like) and your blog badge is done!

Introducing deadtre.es

Posted by paul
on 16 March 2008

I suppose the laziest way to describe deadtre.es is as 'del.icio.us for books'. I've been tinkering with it on and off over the last couple of months or so, and thought it time I polished off the roughest edges and gave it a mini-launch here. As far as functionality goes it's missing some fairly obvious stuff at the minute, but it's usable and includes a bookmarklet for adding books from the pages of Amazon and a simple JSON feed should you want to display your reading list on your blog. If you want something a little more comprehensive you might want to check out Library Thing, BookJetty or maybe Amazon Wish Lists, but if you want a super simple way to keep and share a reading list you're more than welcome to give deadtre.es a go.

will_paginate and acts_as_taggable_on_steroids

Posted by paul
on 11 March 2008

Getting the acts_as_taggable_on_steroids and will_paginate plugins playing nicely together has been written about plenty of times before, but I still ended up doing my own thing when I tackled this on a project of mine. This topic came up in conversation again recently so I guess it's time I wrote up my solution.

Using the typical blog application as an example you might have a Post model which acts_as_taggable. Together, the plugins give you a Post.paginate_tagged_with method which looks as though it will do what you want, but unfortunately doesn't work as expected. You may well get the correct results, but the SQL that is generated to count the total number of results is incorrect so you'll see some strange behavior in you paging controls.

The solution I settled on, was to create a custom finder (paginater?) on the Post model. It looks like this:


class Post < ActiveRecord::Base
  acts_as_taggable

  def self.paged_find_tagged_with(tags, args = {})
    if tags.blank?
      paginate args
    else
      options = find_options_for_find_tagged_with(tags, :match_all => true)
      options.merge!(args)
      # The default count query generated by paginate includes COUNT(DISTINCT Posts.*) which errors, at least on mysql
      # Below we override the default select statement used to perform the count so that it becomes COUNT(DISTINCT Posts.id)
      paginate(options.merge(:count => { :select => options[:select].gsub('*', 'id') }))
    end
  end
end

You use it as you would the regular paginate_tagged_with:

@posts = Post.paged_find_tagged_with('rails', :page => 1)

The way this is works is that we first call find_options_for_find_tagged_with to get a hash of options required to perform a find by tag. If we hand this straight to paginate we'll get an error trying to SELECT COUNT(DISTINCT Posts.*). Thankfully however, will_paginate lets you override the options it uses to generate its count SQL. If we take options[:select] and replace the '*' with 'id' and use that as the :select for our :count, the SQL generated is SELECT COUNT(DISTINCT Posts.id) and everything works as expected.

That's it, really. It's worth pointing out that when no tags are passed in I'm calling paginate (without any additional options) to return all posts. This makes sense in my app and moves some logic out of the controller but you might want to do something different.