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
# 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
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
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
cache, which is what
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
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.
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?