# Caching
# Russian Doll Caching
You may want to nest cached fragments inside other cached fragments. This is called Russian doll caching
.
The advantage of Russian doll caching
is that if a single product is updated, all the other inner fragments can be reused when regenerating the outer fragment.
As explained in the previous section, a cached file will expire if the value of updated_at
changes for a record on which the cached file directly depends. However, this will not expire any cache the fragment is nested within.
For example, take the following view:
<% cache product do %>
<%= render product.games %>
<% end %>
Which in turn renders this view:
<% cache game do %>
<%= render game %>
<% end %>
If any attribute of game is changed, the updated_at
value will be set to the current time, thereby expiring the cache.
However, because updated_at
will not be changed for the product object, that cache will not be expired and your app will serve stale data. To fix this, we tie the models together with the touch method:
class Product < ApplicationRecord
has_many :games
end
class Game < ApplicationRecord
belongs_to :product, touch: true
end
# SQL Caching
Query caching is a Rails
feature that caches the result set returned by each query. If Rails
encounters the same query again for that request, it will use the cached result set as opposed to running the query against the database again.
For example:
class ProductsController < ApplicationController
def index
# Run a find query
@products = Product.all
...
# Run the same query again
@products = Product.all
end
end
The second time the same query is run against the database, it's not actually going to hit the database. The first time the result is returned from the query it is stored in the query cache (in memory) and the second time it's pulled from memory.
However, it's important to note that query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action. If you'd like to store query results in a more persistent fashion, you can with low level caching.
# Fragment caching
Rails.cache
, provided by ActiveSupport, can be used to cache any serializable Ruby object across requests.
To fetch a value from the cache for a given key, use cache.read
:
Rails.cache.read('city')
# => nil
Use cache.write
to write a value to the cache:
Rails.cache.write('city', 'Duckburgh')
Rails.cache.read('city')
# => 'Duckburgh'
Alternatively, use cache.fetch
to read a value from the cache and optionally write a default if there is no value:
Rails.cache.fetch('user') do
User.where(:is_awesome => true)
end
The return value of the passed block will be assigned to the cache under the given key, and then returned.
You can also specify a cache expiry:
Rails.cache.fetch('user', :expires_in => 30.minutes) do
User.where(:is_awesome => true)
end
# Page caching
You can use the ActionPack page_caching gem (opens new window) to cache individual pages. This stores the result of one dynamic request as a static HTML file, which is served in place of the dynamic request on subsequent requests. The README contains full setup instructions. Once set up, use the caches_page
class method in a controller to cache the result of an action:
class UsersController < ActionController::Base
caches_page :index
end
Use expire_page
to force expiration of the cache by deleting the stored HTML file:
class UsersController < ActionController::Base
caches_page :index
def index
@users = User.all
end
def create
expire_page :action => :index
end
end
The syntax of expire_page
mimics that of url_for
and friends.
# HTTP caching
Rails >= 3 comes with HTTP caching abilities out of the box. This uses the Cache-Control
and ETag
headers to control how long a client or intermediary (such as a CDN) can cache a page.
In a controller action, use expires_in
to set the length of caching for that action:
def show
@user = User.find params[:id]
expires_in 30.minutes, :public => true
end
Use expires_now
to force immediate expiration of a cached resource on any visiting client or intermediary:
def show
@users = User.find params[:id]
expires_now if params[:id] == 1
end
# Action caching
Like page caching, action caching caches the whole page. The difference is that the request hits the Rails stack so before filters are run before the cache is served. It's extracted from Rails to actionpack-action_caching gem (opens new window).
A common example is caching of an action that requires authentication:
class SecretIngredientsController < ApplicationController
before_action :authenticate_user!, only: :index, :show
caches_action :index
def index
@secret_ingredients = Recipe.find(params[:recipe_id]).secret_ingredients
end
end
Options include :expires_in
, a custom :cache_path
(for actions with multiple routes that should be cached differently) and :if
/:unless
to control when the action should be cached.
class RecipesController < ApplicationController
before_action :authenticate_user!, except: :show
caches_page :show
caches_action :archive, expires_in: 1.day
caches_action :index, unless: { request.format.json? }
end
When the layout has dynamic content, cache only the action content by passing layout: false
.