15.3. Fragment Caching

With fragment caching you can cache individual parts of a view. You can safely use it in combination with Section 15.2, “HTTP Caching” and Section 15.4, “Page Caching”. The advantages once again are a reduction of server load and faster web page generation, which means increased usability.
Please install a new example application (see the section called “A Simple Example Application”).

Enabling Fragment Caching in Development Mode

First, we need to go to the file config/environments/development.rb and set the item config.action_controller.perform_caching to true:
config.action_controller.perform_caching = true
Otherwise, we cannot try out the fragment caching in development mode. In production mode, fragment caching is enabled by default.

Caching Table of Index View

On the page http://0.0.0.0:3000/companies, a very computationally intensive table with all companies is rendered. We can cache this table as a whole. To do so, we need to enclose the table in a <% cache('name_of_cache') do %> ... <% end %> block:
<% cache('name_of_cache') do %>

[...]

<% end %>
Please edit the file app/views/companies/index.html.erb as follows:
<h1>Listing companies</h1>

<% cache('table_of_all_companies') do %>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Number of employees</th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>

  <tbody>
    <% @companies.each do |company| %>
      <tr>
        <td><%= company.name %></td>
        <td><%= company.employees.count %></td>
        <td><%= link_to 'Show', company %></td>
        <td><%= link_to 'Edit', edit_company_path(company) %></td>
        <td><%= link_to 'Destroy', company, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>
<% end %>

<br />

<%= link_to 'New Company', new_company_path %>
Then you can start the Rails server with rails server and go to the URL http://0.0.0.0:3000/companies. In the development log, you will now see the following entry:
Write fragment views/table_of_all_companies (2.9ms)
  Rendered companies/index.html.erb within layouts/application (119.8ms)
Completed 200 OK in 209ms (Views: 143.1ms | ActiveRecord: 15.0ms)
Writing the cache took 2.9 ms. In total, rendering the page took 209 ms.
If you repeatedly go to the same page, you will get a different result in the log:
Read fragment views/table_of_all_companies (0.2ms)
  Rendered companies/index.html.erb within layouts/application (0.8ms)
Completed 200 OK in 37ms (Views: 34.6ms | ActiveRecord: 0.3ms)
Reading the cache took 0.2 ms and rendering the page in total 37ms. Only a fifth of the processing time!

Deleting Fragment Cache

With the method expire_fragment you can clear specific fragment caches. Basically, we can build this idea into the model in the same way as shown in the section called “Deleting Page Caches Automatically”.
The model file app/models/company.rb would then look like this:
class Company < ActiveRecord::Base
  validates :name,
            presence: true,
            uniqueness: true

  has_many :employees, dependent: :destroy

  after_create   :expire_cache
  after_update   :expire_cache
  before_destroy :expire_cache

  def to_s
    name
  end

  def expire_cache
    ActionController::Base.new.expire_fragment('table_of_all_companies')
  end
end
As the number of employees also has an effect on this table, we would also have to expand the file app/models/employees.rb accordingly:
class Employee < ActiveRecord::Base
  belongs_to :company, touch: true

  validates :first_name,
            presence: true

  validates :last_name,
            presence: true

  validates :company,
            presence: true

  after_create   :expire_cache
  after_update   :expire_cache
  before_destroy :expire_cache

  def to_s
    "#{first_name} #{last_name}"
  end

  def expire_cache
    ActionController::Base.new.expire_fragment('table_of_all_companies')
  end  
end
Deleting specific fragment caches often involves a lot of effort in terms of programming. One, you often miss things and two, in big projects it's not easy to keep track of all the different cache names. Often it is easier to automatically create names via the method cache_key. These then expire automatically in the cache (see the section called “Auto-Expiring Caches”).

Auto-Expiring Caches

Managing fragment caching is rather complex with the naming convention used in the section called “Caching Table of Index View”. On the one hand, you can be sure that the cache does not have any superfluous ballast if you have programmed neatly, but on the other, it does not really matter. A cache is structured in such a way that it deletes old and no longer required elements on its own. If we use a mechanism that gives a fragment cache a unique name, as in the asset pipeline (see Chapter 12, Asset Pipeline), then we would not need to go to all the trouble of deleting fragment caches.
That is precisely what the method cache_key is for. cache_key gives you a unique name for an element. Let's try it in the console. First, we get the always identical cache_key of the first company item two times in a row ("companies/1-20130717215001729269000"), then we touch the item (a touch sets the attribute updated_at to the current time) and finally we output the new cache_key ("companies/1-20130718081128853737000"):
$ rails console
Loading development environment (Rails 4.0.0)
>> Company.first.cache_key
  Company Load (0.1ms)  SELECT "companies".* FROM "companies" ORDER BY "companies"."id" ASC LIMIT 1
=> "companies/1-20130717215001729269000"
>> Company.first.cache_key
  Company Load (0.3ms)  SELECT "companies".* FROM "companies" ORDER BY "companies"."id" ASC LIMIT 1
=> "companies/1-20130717215001729269000"
>> Company.first.touch
  Company Load (0.2ms)  SELECT "companies".* FROM "companies" ORDER BY "companies"."id" ASC LIMIT 1
  SQL (4.2ms)  UPDATE "companies" SET "updated_at" = '2013-07-18 08:11:28.853737' WHERE "companies"."id" = 1
=> true
>> Company.first.cache_key
  Company Load (0.4ms)  SELECT "companies".* FROM "companies" ORDER BY "companies"."id" ASC LIMIT 1
=> "companies/1-20130718081128853737000"
>> exit
$
Let's use this knowledge to edit the index view in the file app/views/companies/index.html.erb:
<h1>Listing companies</h1>

<% cache(@companies) do %>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Number of employees</th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>

  <tbody>
    <% @companies.each do |company| %>
      <tr>
        <td><%= company.name %></td>
        <td><%= company.employees.count %></td>
        <td><%= link_to 'Show', company %></td>
        <td><%= link_to 'Edit', edit_company_path(company) %></td>
        <td><%= link_to 'Destroy', company, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>
<% end %>

<br />

<%= link_to 'New Company', new_company_path %>
Here, we not only use a fragment cache for the whole table, but also one for each line. So the initial call will take longer than before. But if any individual companies change, only one line has to be re-rendered in each case.

Buy the new Rails 5.1 version of this book.

There is no general answer to the question in how much detail you should use fragment caching. Just go ahead and experiment with it, then look in the log to see how long things take.

Change Code in the View results in an expired Cache

Rails tracks an MD5 sum of the view you use. So if you change the file (e.g. app/views/companies/index.html.erb) the MD5 changes and all old caches will expire.

Cache Store

The cache store manages the stored fragment caches. If not configured otherwise, this is the Rails MemoryStore. This cache store is good for developing, but less suitable for a production system because it acts independently for each Ruby on Rails process. So if you have several Ruby on Rails processes running in parallel in the production system, each process holds its own MemoryStore.

MemCacheStore

Most production systems use memcached (http://memcached.org/) as cache store. To enable memcached as cache store in the production system, you need to add the following line in the file config/environments/production.rb:
config.cache_store = :mem_cache_store
The combination of appropriately used auto-expiring caches and memcached is an excellent recipe for a successful web page.
For a description of how to install a Rails production system with memcached, please read Chapter 16, Web Server in Production Mode.

Other Cache Stores

In the official Rails documentation you will find a list of other cache stores at http://guides.rubyonrails.org/caching_with_rails.html#cache-stores.

Thank you for your support and the visibility by linking to this website on Twitter and Facebook. That helps a lot!