Conditional Page Caching in Rails

Posted on 13 Apr 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.