I recently deployed a Rails application that, for various reasons, required caching dynamic content. All dynamic content is represented by a core “Content” model that contains a url attribute. In order for my app to show these pages I have the following as my last route:
map.dynamic ':p1',
:controller => "contents",
:action => "show",
:requirements => {:p1 => /[\w|-]*/}
As you can see, the contents/show is my catchall action. If I can find a content url matching :p1, I show the content.
Another feature of the application is that you can restrict access to this content to one or many user groups via my Lockdown gem. At a basic level, Lockdown works by testing for access in a before_filter it adds to all controllers. To ensure I don’t expose protected content, I needed to use action caching to have my before filters execute before showing the page.
Action caching typically creates a cache file that corresponds to the action. However, in this case, the show action represents multiple urls. Here’s how you get around that:
class ContentsController < ApplicationController
caches_action :show,
:cache_path => proc{|c| c.send(:cached_page, c.params[:p1], c.params[:page]) }
def show
#logic to render multiple content types
end
private
# This method will return my cache name
def cached_page(url, page)
page ||= "1"
"#{url}_#{page}"
end
end
If the url for the contents was “/about-us”, the cache would be “about-us”. If the content url was “/reunion-pictures?page=2″, the cache would be “reunion-pictures_2″. So forth and so on.
I was going setup a route that would turn “/reunion-pictures?page=2″ to “/reunion-pictures/2″, but for some reason, adding the route to do this broke the display of the page links in will_paginate. I’m not sure why and the url wasn’t that important to me. The value provided by will_paginate was more important to me than pretty routes in this instance. I may revisit this issue later…but I need to get back on point.
So…I’m caching now, cool. Now I just have to account for changing content, enter the sweepers:
class ImageSweeper < ActionController::Caching::Sweeper
observe Image
def after_create(post)
expire_cache_for(post)
end
def after_update(post)
expire_cache_for(post)
end
def after_destroy(post)
expire_cache_for(post)
end
private
def expire_cache_for(record)
record.galleries.each do |gal|
for i in 1..((gal.artworks.length / 10) + 1)
expire_action("#{gal.url}_#{i}")
end
end
end
One type of dynamic content is a gallery that can contain multiple images. In order to make sure my galleries are kept up-to-date, I needed this sweeper to expire the caches of the galleries that contained the images I was creating/updating/deleting.
In order for this ImageSweeper to clean house, I needed to add the following to the ImagesController:
cache_sweeper :image_sweeper,
:only => [:create, :update, :destroy]
Now my galleries will stay current and I won’t be locked into showing cached galleries that represent old/incorrect information.
The cached pages are ridiculously faster…and yes, that’s a technical term similar to “ludicrous speed”.
