This is brief overview on fragment caching in Rails and clearing those cached fragments on ActiveRecord callbacks on CRUD operations — in particular, the various caveats I stumbled across in setting up a cache sweeper.
You will find the following commented out in production.rb
# Use a different cache store in production
# config.cache_store = :mem_cache_store
However, for this example let's place our configuration in application.rb
so that we use redis-store
as our default Cache Store
Let's also update development.rb
to enable caching
We'll also need to add gem "redis-store", "~> 1.0.0"
to our Gemfile with the version specification to ensure compatibility with Rails 3. If you're testing out in development, I suggest installing redis via Homebrew by doing brew install redis
— this will give you instructions on how to start up redis. If you forget, you can always get those instructions again by simply running brew info redis
.
Our cache sweeper inherits from ActionController::Caching::Sweeper
which are request dependent observers and are covered in the Rails API as well as the guide on caching. The Rails guide covers sweepers and is worth reading over. However, there are a couple gotchas to watch out for.
First, let's take a look at my FragmentSweeper
We also need to enable the sweeper in the respective controller
While implementing this, I found that my sweeper object could not call expire_fragment
— it simply returned nil
. Digging deeper I found that Sweeper
implements method_missing
def method_missing(method, *arguments, &block)
return unless @controller
@controller.__send__(method, *arguments, &block)
end
Notice how it returns nil
if the instance variable @controller
is not set? Hence, including @controller ||= ActionController::Base.new
. This allowed expire_fragment
to be called.
I also found out — the hard way — that it isn't a good idea to pass a Hash
to cache
, which is what caches_action
does.
This fantastic post on Advanced Caching in Rails details how the cache helper arguments maps to the cache keys
cache 'explicit-key' # views/explicit-key
cache @post # views/posts/2-1283479827349
cache [@post, 'sidebar'] # views/posts/2-2348719328478/sidebar
cache [@post, @comment] # views/posts/2-2384193284878/comments/1-2384971487
cache :hash => :of_things # views/localhost:3000/posts/2?hash_of_things
This is a caveat of implementing cache sweepers as we do not have access to request.host_with_port
in the sweeper. Therefore, the solution was to go with a format of keys that we could work with, and for this example my #show
view was cached like so
Obtaining the updated_at
timestamp is quite simple, as shown in FragmentSweeper
. I urge you to read Adam's post as he covers a lot more aspects of caching and all that it entails. He even includes a means of caching by moving away from the HTTP request.
There is also a Pro RailsCast on Fragment Caching that should not be missed.
Quoting Adam
I will not go into much depth on sweepers because they are the only thing covered in the rails caching guide. The work, but I feel they are clumsy for complex applications.
How have you gone about tackling caching and what do you think about cache sweepers?