/ rails_3

Fragment caching and sweeping strategy

Rails provides three types of caching techniques:

  • page caching
  • action caching
  • fragment caching

The rails_guides_on_caching is the best place to know how it works.

This article is to demonstrate fragment caching the Rails Way with various data-stores and clearing those cached fragments on ActiveRecord callbacks on CRUD operations.

Rails ActiveSupport::Cache::Store is responsible to do all the read and write operations.

1. The setup

By default caching is disabled in rails. You will find the following config commented out in production.rb

   # Use a different cache store in production
   # config.cache_store = :mem_cache_store

You can either enable the cache_store globally in application.rb or keep it environment specific viz. development, production, staging. So you have a choice to select destination cache store for each environment.

We'll keep it default for all envs. and enable it in application.rb

   # set default cache store as redis
   config.cache_store = :redis_store
   # OR
   # config.cache_store = :memcached_store
   # OR
   # config.cache_store = :dalli_store

Alternatively on Heroku you have to specify the config URL that would be used to store Redis data.

 config.cache_store = ActiveSupport::Cache::RailsRedisCache.new(:url => ENV['REDISTOGO_URL'])

Once you have decided the data-store, next enable caching.

 config.action_controller.perform_caching = true

In Gemfile

 gem "redis-store"

2. Cache a fragment

Here's an example to show popular articles by an author using cache block.

<% cache [@author, 'popular_articles'] do %>
  render @popular_articles
<% end %>

This works on cache fetch/miss concept, where every call to the cache method will first look for any existing store with the key(here its author_name:popular_articles). If its a miss(no data found) then it will cache the fragment in the block and return it. And the cycle goes on.

Perfect, now you have successfully cached the above fragment. The rendered HTML would be something like

Popular articles

  • Article 1
  • Article 2
  • Article 3

3. Sweeping strategy

Every caching technique needs a sweeping or expiration system.
Rails ActionController::Caching::Sweeper does the job for you.

Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change. They do this by being half-observers, half-filters and implementing callbacks for both roles.

For our articles example we can implement our ArticleSweeper as:

class ArticleSweeper < ActionController::Caching::Sweeper
  observe Article
  
  def after_save(record)
    expire_fragment [record.try(:author), 'popular_articles']
  end
  
  def after_destroy(record)
    expire_fragment [record.try(:author), 'popular_articles']
  end
end

This would be activated during CRUD operation based on REST. Hence the controller should know which sweeper to look for.

class ArticlesController < ApplicationController
   cache_sweeper  :article_sweeper

  ...
end