原文在我另一个博客上,这边就不在另外排版了,可参见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/