Automatic expiration of cached actions
last.fm offers various Webservices to retrieve data like the most recently played tracks or your personal top ten. Nice I thought, finally there is a way to provide the three people interested in my music listening habits with the information they want :-)
To make it even easier to let people know you’re Britney Spears’ biggest fan, Typo even comes with a sidebar plugin that displays your recent tracks list from last.fm.
Unfortunately, with page caching turned on this is somewhat pointless as the list will be statically rendered into each page until the next blog post, when the page cache is flushed.
So I was looking for another solution and came up with a separate controller containing an action that only renders the recent tracks list you see at the end of the sidebar. This action is embedded into the page with a render_component
call in my theme’s default.rhtml
.
Right after that call there is some Javascript that updates the playlist whenever somebody views a page.
Not very exciting so far. But I didn’t want to query the Audioscrobbler API on every page hit. The average audio track lasts around 3 or 4 minutes, and that’s how long I wanted to stay the cached playlist around, too.
The usual Rails solution to problems like this seems to be a cron job which regularly deletes cache files, triggering regeneration of the content. This is acceptable for wiping out cached pages once a day or every hour, but when it comes to different life times for different cached actions cron doesn’t fit. Imho.
What I wanted in my controller was something like
Here’s how I reached this goal:
First, implement the caches_action_for
method. To use it, a controller must include the ActionCacheFragmentExpiration
module.
expire_old_cache_entry
is called as a before_filter
and removes any already cached output of the current action when it is older than the maximum age given to caches_action_for
.
Next, extend the fragment cache (which is used for action caching) to allow for conditional deletion of fragments based on their age:
Third, work around a Rails bug concerning action caching not working when using render_component. More concrete, the caching works, but the cache file is named .cache
and gets overwritten whenever another cached action is rendered as a component.
That’s what the :use_component_workaround
option of caches_action_for
is for. Setting this to true enables an alternative ActionCache implementation that deals with this issue:
The trick is to hand the params hash to url_for when computing the fragment file name. That way, controller and action name get included into the fragment file name properly.
To use this style of action caching, put that stuff somewhere in your loadpath (Typo has lib/rails_patch/
for things like this) , require the file in environment.rb
, and declare your cached actions with caches_action_for
like described above.