# action, fragment, russian doll caching

29 Mar 2014 - New York

This is the second post on caching in Rails apps. In the last one, I discussed page caching, which stores the entire output of a request on the web server’s file system.

A limitation of this strategy is that it intercepts requests before they reach your app, and thus doesn’t work well for controllers that expose endpoints requiring a before_action (e.g., as with pages that require authentication).

## Action caching

Action caching offers the same level of granularity as page caching (i.e., the entire response is cached), but happens deeper into the request-response cycle: after a request hits the Rails stack and before_actions execute on it.

It’s been removed from core Rails (as of 4.0), and must be installed via the actionpack-action_caching gem.

Setup is mostly the same as with page caching, except you have more cache store options (more on that below).

## Fragment caching

Fragment caching allows a portion of a template to be wrapped in a cache block, which will serve the fragment out of the cache store whenever possible.

<% cache(action: 'recent', action_suffix: 'all_products') do %>
<%= render @products %>
<% end %>


To expire a cached fragment, Rails provides the expire_fragment method:

expire_fragment(controller: 'products', action: 'recent', action_suffix: 'all_products')


But it’s better to avoid having to do this manually by setting our cache keys strategically.

## Enter memcached

With memcached, we don’t have to expire cached fragments manually. Instead, we continuously add to the cache store, with each new entry keyed by a generated hash (more on that below).

That way, invalidated fragments will just be replaced with valid ones, and we can let the cache store automatically garbage-collect invalidated fragments.

Setting it up is easy:

# config/application.rb (or config/environments/*.rb)
config.cache_store = :mem_cache_store


for non-local environments, you’ll want to specify the addresses of all memcache servers in your cluster.

# config/application.rb (or config/environments/*.rb)
config.cache_store = :mem_cache_store, 'cache1.example.com', 'cache2.example.com'

##### Other cache stores

memory store (for small apps)

config.cache_store = :memory_store, { size: 64.megabytes }


filestore (the rails default)

config.cache_store = :file_store, "/path/to/cache/directory"


ehcache (for jruby)

config.cache_store = :ehcache_store


null store (i.e., don’t cache. for development)

config.cache_store = :null_store


custom cache store (for plugging in an arbitrary cache store)

config.cache_store = MyCacheStore.new


## Cache keys

### Hashing for caching

To generate a hash to use as a cache key, define a method that maps from whatever attributes can potentially invalidate the cached fragment to a unique string.

The caching performed above will be invalidated when the number of products changes or whenever a product is updated, so we can define the following helper:

module ProductsHelper
def cache_key_for_products
product_count = Product.count
time_of_most_recent_update = Product.maximum(:updated_at).try(:utc).try(:to_s, :number)
"products/all-#{product_count}-#{time_of_most_recent_update}"
end
end


The call to cache above then becomes

<% cache(cache_key_for_products) do %>
<%= render @products %>
<% end %>


In general, a hash function should

### ActiveRecord’s cache_key

You can also pass an AR model to cache, and let Rails magic take care of the rest:

  <!-- _product.rb, called from <%= render @products %> -->
<% cache(product) do %>
<% end %>


Under the hood, cache_key is invoked on the model, producing a cache key composed of the model name, its id, and the updated_at timestamp: products/23-20130109142513.

Note that you can override cache_key or implement it on a PORO as needed.

## Russian Doll caching

Russian doll caching is a technique that combines the two approaches to cache key generation above in order to maximize the amount of data that’s cached by nesting cached fragments.

<% cache(cache_key_for_products) do %>
<% render @products %>
<% end %>

<!-- _product.rb -->

<% cache(product) do %>
<% end %>


The benefit of this approach is that if only one product is updated, we needn’t re-render the entire set. That invidivual product can be rendered and re-cached, and the set as a whole can be rendered using all the other, still valid, cached fragments.

## Low-Level Caching

To manually set or retrieve an item from the Rails cache, use Rails.cache.fetch.

The method works as both a setter when passed a block…

def competing_price
Rails.cache.fetch('product/competing_price', expires_in: 12.hours) do
Competitor::API.find_price(id)
end
end


…and as a getter when passed only a key.

Rails.cache.fetch('product/competing_price')