Monthly Archives: August 2009

Restful action caching

Caching actions without layout can be complicated when multiple request formats are used. In particular, an HTML response may use a dynamic layout, in which case you want to use the :layout => false option. However, other formats (such as XML) don’t use a layout, but the :layout => false option to _caches_action_ does not properly cache the body in this case. To solve the problem, create two caches action statements:

caches_action :show,
              :if => lambda { |c| c.request.format == :html },
              :cache_path => lambda { |c| c.cache_key },
              :layout => false   caches_action :show,
              :unless => lambda{ |c| c.request.format == :html },
              :cache_path => lambda { |c| c.cache_key },
              :layout => true

Also, relying on the accept header may not cause the action caching module to detect the appropriate format. Try this in your application_controller:

before_filter :set_explicit_request_format
def set_explicit_request_format
  # Set format explicitly from accept header, unless it's already set
  request.format = :html if request.format == :any
  params[:format] ||= request.format.to_sym.to_s
end

Detecting action caching within controller

Rails offers three forms of caching within your controller: page, action and fragment. Page caching results in the fastest access times, as the results of the first call to an action are saved in a file so that subsequent accesses never even hit rails. However, for most applications, this isn’t useful, as there may be dynamic content on a page, and this does not allow for authentication. Fragment caching is the most detailed, and allows different parts of a page to be cached and allows you to check for the presence of a cached fragment within your controller (or view), but it requires the most maintenance of cache keys. Action caching is a nice compromise between the two. It allows the controller to get into the action but takes care of cache key creation. It also allows for a dynamic layout using the :layout option. However, in some actions, the amount of work done by the controller may be non-trivial, so it would be nice to check for the presence of the cache within the body of the controller action. This can be solved by borrowing some code from within ActionController::Caching::Actions.

def index
  cache_path = ActionCachePath.new(self, cache_key)
  return if self.read_fragment(cache_path.path)
  # body of action
end

Controlling your own cache keys is also useful, particularly for actions that may take a number of parameters:

def cache_key
  key = "#{params[:controller]}/#{params[:action]}"
  params.each_pair {|k, v| key += ":#{k}=#{v.gsub(/\s/, "_")}
  key
end