Michael de Silva's Blog

Software Engineer. Rubyist and Roboticist.

Michael de Silva's Blog

Software Engineer. Rubyist and Roboticist.

Caching in Rails 3.2.8 including Cache Sweeping with Redis

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?

comments powered by Disqus