homeASCIIcasts

172: Touch and Cache 

(view original Railscast)

Other translations: En

Other formats:

Written by liguokai (liguokai.javaeye.com)

Rails近期升级到了2.3.3版。尽管这次只是一次较小的升级,主要是对之前版本的一些错误修正(bug fixes), 这个新版本还是给我们提供了一些新的特性(features)。Touch就是其中的一个。我们将在这集为大家展现如何使用touch去改进你的应用程序中的缓存。

如果你还没有升级rails到2.3.3,你可以在命令行运行:

sudo gem update rails

升级到新版本. 然后你可以在任何你想升级的应用程序里面修改/config/environment.rb文件的最上面一行:

/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  

如果你准备升级一个应用程序,请确保该应用程序有个优秀的测试套件(test suite)。 这样你可以保证该升级不会对破坏你原有的应用程序。

与片段缓存(Fragment Caching)一起使用touch

以下显示的是一个博客应用程序的article页面。这个页面的流量很大,所以我们想要改进他的性能。

The articles page of our application.

我们可以添加片段缓存(Fragment Caching)去缩短页面的相应时间。这个可能不是最理想的解决方案,但它却能让我们快速的解决问题。我们先对这个页面应用片段缓存(Fragment Caching)看看回是怎么样。

在开发模式下,缓存(caching)是默认被禁用的。应此我们先要打开缓存。我们需要修改/config/environments/default.rb文件:

/config/environments/default.rb

config.action_controller.perform_caching = true

另一种方式是创建一个临时环境(staging environment). 如果你想了解更多关于如何创建临时环境(staging envrionment),你可以查看第72集. 如果你创建了一个临时环境(staging environment),你就不需要在你的开发环境里面激活或禁用缓存(caching)。

片段缓存(fragment caching)将会添加在article的show视图内。视图代码如下:

/app/views/articles/show.html.erb

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

我们调用cache方法并把要缓存的片段包含在缓存块内:

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

cache方法接受一个可选参数。这个参数被用作缓存的key(默认情况下,页面的URL会被作为缓存的key)。如果我们把模型(model)当作参数,那么模型的cache_key属性将被作为这个key。这就是说,当article更新的时候这个缓存片段就会过期。我们可以在控制台(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"

cache_key由模型(model)名,模型的idupdated_at属性组成。key的最后一段非常有用,因为这一段组成部分,这个key每次都会应为模型的更新而改变。这样每次模型的任意属性有更改,这个缓存片段都会过期。

我们通过刷新2次我们的article页面来演示缓存。在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)

第一次加载页面的时候,因为缓存内没有该key对应的缓存片段,所以找不到对应的缓存片段(cached fragment)。这时数据就会从数据库里面取出来,并创建缓存。第二次加载页面的时候对应该key的缓存片段被找到,这时我们就不需要从数据库里面读取数据。

如果我们编辑article,比如修改标题,这个缓存片段会自动失效并且页面被更新。这时因为当我们更新article的时候,模型(model)的updated_at属性也被修改,模型的cache_key也应此而更新。

The article page is updated when the article changes.

使用Touch

我们现在所有的改动都应该能在早期版本的rails上面工作。那么touch是怎么工作的呢?我们将从touch如何在控制台(console) 下面工作开始讲起:

我们首先取得第一个article并找到它的updated_at属性。

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

如果我们对article调用touch方法,那么它的updated_at属性也会被修改。

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

这就是touch。当model调用touch方法,model就会修改它的updated_at属性值为现在的时间。这看起来不像个特别有用的方法,但当用在associations时touch方法就会显示出它的价值。

在我们的应用程序里一个Article可能有很多Comments。如果我们使用article页面的表单对article添加一条comment,这条comment将不会作为article页面的一部分被显示。这是因为article已经被缓存了,article页面只会显示缓存里面的comments。当一条comment被添加时article的时间戳未被修改,所以缓存片段不会过期。

要想实现当添加或修改comment时article页面显示新的comment,我们仅仅需要对comment模型(model)做一点点修改:

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

belongs_to关系添加 :touch => true 意味着当创建,更新或者删除一条comment的时候,该comment属于(belongs_to)的article被touched。现在我们添加一条comment,缓存会失效并且页面会更新而且显示刚添加的comment。

The article page is updated when the article changes.

这项技术不仅限在片段缓存(fragment caching)上使用。touch也能很好的和Memcached工作,当新缓存创建时Memcached会自动清除旧缓存。不需要使用sweepers更不需要去写一堆sweepers需要的多余代码。