Dynamic setup of caches_page in Rails

Hot off the press! :) This blog entry will show you how to dynamically setup page caching in Rails, when you have a constantly growing list of actions you want to cache, but don't want to update caches_page's actions listing every time you add a new action which requires caching.. This is especially handy if you use FlexImage plugin for Rails. Read on if you're interested.

I use excellent FlexImage plugin for working with images in Rails. The plugin allows you to upload images to database, and process them before sending back to user. To be able to do so, you install the plugin, and then create a controller responsible for image processing, where you define different flex_image actions, calling which takes an originally uploaded image from database, processes it (resizing, cropping, etc..), and returns back to user. I have something like that:

class ImageController < ApplicationController
flex_image :action => 'show', :class => Image, :size => "650×500"
flex_image :action => 'big', :class => Image, :size => '400×400'
flex_image :action => 'medium', :class => Image, :size => '275×275'

……

end

In order to save time on image processing, it is always a good idea to turn page caching for the flex_image actions:

class ImageController < ApplicationController
flex_image :action => 'show', :class => Image, :size => "650×500"
flex_image :action => 'big', :class => Image, :size => '400×400'
flex_image :action => 'medium', :class => Image, :size => '275×275'

caches_page :show, :big, :medium, :small

end

The problem arises when the system you are building has dozens of flex_image actions defined:

overflex.gif

You definitely don't want to add that many actions to caches_page by hand. So, here's my poor man's solution for the problem (explanation is to follow):

# caches_page :show, :big, :medium, :small

def self.setup_page_cache
dont_cache_actions = ["post_upload", "post_video"]

actions = (ImageController.instance_methods - ApplicationController.instance_methods - dont_cache_actions)
actions.each do |action|
return unless perform_caching
class_eval "after_filter { |c| c.cache_page if c.action_name == '#{action}' }"
end
end
setup_page_cache

Here's what I do here.

  • First, I turn off the old cached_page directive as we will be replacing it with a new function.
  • Next, in the function setup_page_cache, I define a list of actions which are present in the controller by which I don't want to be cached.
  • Then, I just extract the list of actions I want to be cached - which is difference between controller parent's instance methods (ApplicationController in my case) and instance methods of the controller itself (the controller instantiates all the methods of its parent, and you probably don't want to cache all the methods of all the parent controllers/classes). Finally, I subtract methods which I don't want to be cached.
  • After that, I just set up page caching filter for all the methods in the to-be-cached methods list
  • Once I'm done with the setup_page_cache function, I just call it in order to setup page caching for real. There's one catch though. You must call the setup_page_cache after it is defined, and not before (you'll get error of function not beign defined)

That's all! You just place this function inside your controller and you're done. When you add a new flex_image method to the controller, it will automatically be added to the list of cached pages.

PS: I'm sure there must be a better way to do the dynamic actions caching setup, but I tried and searched the web and actually found nothing. Any ideas or different approaches to the problem are greatly appreciated.

2 Responses to “Dynamic setup of caches_page in Rails”

  1. Michael Mahemoff Says:

    after_filter lambda { |c| c.cache_page }, :except => [:post_upload, post_video]

  2. mike Says:

    Michael,

    thanks. That's perfect :)

Leave a Reply