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
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
- 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