homeASCIIcasts

172: Touch and Cache 

(view original Railscast)

Other translations: Cn

Rails has recently been updated to version 2.3.3. This is a minor update and most of it consists of bug fixes, but there are a few new features too. One of these is called touch, and in this episode we’ll show you how to use it to improve your application’s caching.

If you haven’t upgraded to version 2.3.3 yet you can run

sudo gem update rails

from the command line to install the new version. Then in any applications you want to update, you can change the version number listed at the top of /config/environment.rb.

# Specifies gem version of Rails to use when vendor/rails is not present
RAILS_GEM_VERSION = '2.3.3' unless defined? RAILS_GEM_VERSION

If you’re upgrading an application make sure that it has a good test suite so that you can be sure that upgrading hasn’t broken anything.

Using touch With Fragment Caching

The article page for a blog application is shown below. This page gets a lot of traffic so we want to improve its performance.

The articles page of our application.

One way we could improve the response time is to add fragment caching to the page. This might not be the ideal solution, but it’s a quick-and-dirty fix for the problem. Let’s implement fragment caching on the page and see how we get on.

Caching is disabled by default in development mode so first we’ll have to turn it on. We do this in /config/environments/default.rb.

config.action_controller.perform_caching = true

An alternative to this is would be set up a staging environment. If you need more information about how to do that it was covered back in episode 72. Setting up a staging environment removes the need to enable and disable caching in your development environment.

The fragment caching will be added to the articles’ show view. The view code looks like this.

<% title @article.name %>
<p class="author"><em>from <%=h @article.author_name %></em></p>
<%= simple_format @article.content %>
<p><%= link_to "Back to Articles", articles_path %></p>
<% unless @article.comments.empty? %>
  <h2><%= pluralize(@article.comments.size, 'comment') %></h2>
  <div id="comments">
  <% for comment in @article.comments %>
    <div id="comment">
      <strong><%= link_to_unless comment.site_url.blank?, h(comment.author_name), h(comment.site_url) %></strong>
      <em>on <%= comment.created_at.strftime('%b %d, %Y at %H:%M') %></em>
      <%= simple_format comment.content %>
    </div>
  <% end %>
  </div>
<% end %>
<h3>Add your comment:</h3>
<%= render :partial => 'comments/form' %>

To add fragment caching we call cache and wrap the part of the page we want to cache in its block.

<% title @article.name %>
<% cache @article do %>
  <p class="author"><em>from <%=h @article.author_name %></em></p>
  <!-- Rest of code omitted -->
<% end %>
<h3>Add your comment:</h3>
<%= render :partial => 'comments/form' %>

The cache method takes an optional argument and the argument’s value is used as the key that the cache is stored under (by default the page’s URL is used). If we pass a model, then its cache_key is used. This means that the item will be expired automatically when the article is updated. We can demonstrate this in the console.

>> a = Article.first
=> #<Article id: 1, name: "The Piano, a Marvellous Instrument", content: "The piano is a musical instrument played by means o...", author_name: "Billy Belmer", created_at: "2009-06-14 19:39:40", updated_at: "2009-07-29 19:14:17">
>> a.cache_key
=> "articles/1-20090729191417"

The cache_key is a string made up of the name of the model, and its id and updated_at attributes. The last part of the key is very useful as it means that the key changes every time the model is updated. Therefore the item is expired from the cache when any of the model’s attributes change.

We can show the cache in action by refreshing our article’s page twice and looking at the development log.

 
  Processing ArticlesController#show (for 127.0.0.1 at 2009-07-30 20:22:30) [GET]
    Parameters: {"id"=>"1"}
    Article Load (0.2ms)   SELECT * FROM "articles" WHERE ("articles"."id" = 1) 
  Rendering /Users/eifion/rails/apps_for_asciicasts/ep172/app/views/articles/show.html.erb
  Cached fragment hit: views/articles/1-20090729225258 (0.0ms)
    SQL (0.2ms)   SELECT count(*) AS count_all FROM "comments" WHERE ("comments".article_id = 1) 
    CACHE (0.0ms)   SELECT count(*) AS count_all FROM "comments" WHERE ("comments".article_id = 1) 
    Comment Load (0.3ms)   SELECT * FROM "comments" WHERE ("comments".article_id = 1) 
  Cached fragment miss: views/articles/1-20090729225258 (0.0ms)

  Processing ArticlesController#show (for 127.0.0.1 at 2009-07-30 20:22:45) [GET]
    Parameters: {"id"=>"1"}
    Article Load (0.2ms)   SELECT * FROM "articles" WHERE ("articles"."id" = 1) 
  Rendering /Users/eifion/rails/apps_for_asciicasts/ep172/app/views/articles/show.html.erb
  Cached fragment hit: views/articles/1-20090729225258 (0.0ms)

The first time the page reloaded there was a cached fragment miss as no item matching the key was found. The data was therefore pulled from the database and the cache item created. The second time the cached item is found so we have a cached fragment hit and there is no need to get the comments the database.

If we edit the article, say by changing the title, then the cache item will automatically be expired and the page will be updated. This is because a new cache_key is created when we update the article based on the its changed updated_at attribute.

The article page is updated when the article changes.

Using Touch

All the work we’ve done so far would work in previous versions of Rails, so how does touch fit in? We’ll begin by showing how touch works in the console.

We’ll get the first Article and find its updated_at attribute.

>> a = Article.first
=> #<Article id: 1, name: "The Piano, a Beautiful Instrument.", content: "The piano is a musical instrument played by means o...", author_name: "Billy Belmer", created_at: "2009-06-14 19:39:40", updated_at: "2009-07-29 20:27:34">
>> a.updated_at
=> Wed, 29 Jul 2009 22:27:34 UTC +00:00

If we call touch on the article its updated_at attribute will be changed.

>> a.touch
=> true
>> a.updated_at
=> Wed, 29 Jul 2009 22:27:53 UTC +00:00

This is basically all touch does. When called on a model it changes its updated_at attribute to the current time. This doesn’t seem like a particularly useful method to have, but it comes into its own when dealing with associations.

In our application an Article can have many Comments. If we use the form on an article’s page to add a comment to that article, the comment won’t be shown as the part of the page that shows the comments is cached. The article’s timestamp is not changed when a comment is added so the cache item won’t be expired.

All we have to do to ensure that the article is touched when a comment is added or changed is make a small change to the Comment model.

class Comment < ActiveRecord::Base
  belongs_to :article, :touch => true
end

Adding :touch => true to the belongs_to relationship means that when a comment is created, updated or destroyed the article it belongs to is touched. Now when we add a third comment the cache will be expired and the page updated to show the new comment.

The article page is updated when the article changes.

This technique can be used outside of fragment caching. This works well, for example with Memcached. Memcached will automatically roll off the old caches as new ones are created. There is no need to use sweepers and to have to deal with the extra code that they require.