Caching With Rails

原文在我另一个博客上,这边就不在另外排版了,可参见http://caok1231.com/blog/2013/01/30/caching-with-rails/

1.Page caching

Page caching是最简单最高效的一种,它会将Action最后的HTML结果存成public/下的HTML文件,也就是静态网页。

class ProductsController < ActionController

  caches_page :index

  def index; end

end

不过缺点也同样明显,由于是静态网页,对于任何的request都会返回同一个结果,适用面比较窄。 比如用kaminari等定义了翻页功能,就将失效,始终停留在第一页。

如果要真的使用的话,可以在有新的product创建的时候更新index。

class ProductsController < ActionController

  caches_page :index

  def index

    @products = Products.all

  end

  def create

    expire_page :action => :index

  end

end

expire_page会在有create操作的时候,清除cache里的资料重新生成。

2.Action caching

Rails在development模式下的cache默认是关闭的。

打开Rails的cache很简单,只要在环境对应的配置文件config/environments/*.rb中设置即可:

config.action_controller.perform_caching = true

action caching与page caching的区别在于request会经过web server并且被rails application接收,直到所有的before filters被处理。

class ProductsController < ActionController

  before_filter :authenticate, :only => :create

  caches_action :index

  def index

    @products = Product.all

  end

  def create

    expire_action :action => :index

  end

end

缺点也和Page caching一样,无法提供不同使用者有不同內容。

expire_action会在有create操作的时候,清除cache里的资料重新生成。

3.Fragment Caching

动态web应用程序的页面往往由多个components组成,而每个component经常需要采取不同的cache、expire策略。针对这个问题,rails提供了fragment cache。

Fragment caching可以只缓存HTML中的一小段元素,我們可以自由选择要cache的区域。这种cache发生在View中,所以我們须把cache程式写在View中,用cache包起來要cache的Template:

如果一个页面有多个component被cache,则需要添加suffix来区分它们:

<% cache(:action => 'recent', :action_suffix => 'all_products') do %>

  All available products:

如果你想要一个无需绑定到相应action的cache块,可以赋予一个全局的key

<% cache 'all_available_products' do %>

  All available products:

<% end %>

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

<% title @article.name %>  

<% cache @article do %>

  <p class="author"><em>from <%=h @article.author_name %></em></p>  

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

  <!-- Rest of code omitted -->  

<% end %>  

<h3>Add your comment:</h3>  

<%= render :partial => 'comments/form' %>

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

在我们的应用程序里一个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。

清理cache

手动清除

rake tmp:cache:clear

在资料修改或刪除时,在适当的Controller Action中过期这些cache资料

expire_fragment(:controller => 'products', :action => 'recent',  :action_suffix => 'all_prods)

expire_fragment(:key => ['all_available_products', @latest_product.created_at].join(':'))

4.Sweepers

但如上面这样到处写expire方法,显然不是最好的方式。Rails针对此提供了sweeper机制:把cache清理移入一个observer类,此类会监测一个对象的变化,并且通过相应的钩子来清理此对象相关的cache。

class StoreSweeper < ActionController::Caching::Sweeper

  # This sweeper is going to keep an eye on the Product model

  observe Product

  # If our sweeper detects that a Product was created call this

  def after_create(product)

      expire_cache_for(product)

  end

  # If our sweeper detects that a Product was updated call this

  def after_update(product)

    expire_cache_for(product)

  end

  # If our sweeper detects that a Product was deleted call this

  def after_destroy(product)

    expire_cache_for(product)

  end

  private

  def expire_cache_for(record)

    # Expire the list page now that we added a new product

    expire_page(:controller => '#{record}', :action => 'list')

    # Expire a fragment

   expire_fragment(:controller => '#{record}', :action => 'recent', :action_suffix => 'all_products')

  end

end

Cache sweeper在controller里面就是一个after或者aroud filter

class ProductsController < ActionController

  cache_sweeper :store_sweeper, :only => [ :create ]

  def create

  end

end

5,Counter cache

如果需要常计算has_many的Model有多少笔记录,例如显示文章列表时,也要显示每篇有多少留言回复。

<% @topics.each do |topic| %>

  主題:<%= topic.subject %>

  回复數:<%= topic.posts.size %>

<% end %>

这时Rails会产生一笔笔的SQL count查询:

SELECT * FROM `posts` LIMIT 5 OFFSET 0

SELECT count(*) AS count_all FROM `posts` WHERE (`posts`.topic_id = 1 )

SELECT count(*) AS count_all FROM `posts` WHERE (`posts`.topic_id = 2 )

SELECT count(*) AS count_all FROM `posts` WHERE (`posts`.topic_id = 3 )

SELECT count(*) AS count_all FROM `posts` WHERE (`posts`.topic_id = 4 )

SELECT count(*) AS count_all FROM `posts` WHERE (`posts`.topic_id = 5 )

Counter cache功能可以把這個數字存進資料庫,不再需要一筆筆的SQL count查詢,並且會在Post數量有更新的時候,自動更新這個值。

首先,你必须要在Topic Model新增一個栏位叫做posts_count,依照慣例是_count结尾,型別是integer,有预设值0。

rails g migration add_posts_count_to_topic

编辑Migration:

class AddPostsCountToTopic < ActiveRecord::Migration

  def self.up

    add_column :topics, :posts_count, :integer, :default => 0

    # 如果是网站上线后才新增这个功能,这里需要先计算设定好初始值

    Topic.find_each do |topic|

      topic.update_attribute(:posts_count, topic.posts.size)

    end

  end

  def self.down

    remove_column :topics, :posts_count

  end

end

编辑Models,加入:counter_cache => true:

class Topic < ActiveRecord::Base

  has_many :posts

end

class Posts < ActiveRecord::Base

  belongs_to :topic, :counter_cache => true

end

这样同样的@topic.posts.size,就会自动变成使用@topic.posts_count,而不會用SQL count查詢一次。

6.rails.cache

Rails.cache.read("city")   # => nil

Rails.cache.write("city", "Duckburgh")

Rails.cache.read("city")   # => "Duckburgh"

cache.write("today", "Monday")

cache.fetch("today")  # => "Monday"

cache.fetch("city")   # => nil

cache.fetch("city") do

  "Duckburgh"

end

cache.fetch("city")   # => "Duckburgh"

原文在我另一个博客上,这边就不在另外排版了,可参见http://caok1231.com/blog/2013/01/30/caching-with-rails/

猜你喜欢

转载自caok1231.iteye.com/blog/1807441