使用has_many关系为俄罗斯玩偶缓存构建Rails应用程序

mla*_*erg 23 ruby caching ruby-on-rails fragment-caching russian-doll-caching

在研究了DHH和其他关于基于密钥的缓存过期和俄罗斯娃娃缓存的博客文章之后,我仍然不确定如何处理一种关系类型.具体来说,是一种has_many关系.

我将分享我对示例应用程序的研究结果.这是一个故事讲述,所以坚持下去.假设我们有以下ActiveRecord模型.我们所关心的只是模型的正确改变cache_key,对吗?

class Article < ActiveRecord::Base
  attr_accessible :author_id, :body, :title
  has_many :comments
  belongs_to :author
end

class Comment < ActiveRecord::Base
  attr_accessible :article_id, :author_id, :body
  belongs_to :author
  belongs_to :article, touch: true
end

class Author < ActiveRecord::Base
 attr_accessible :name
  has_many :articles
  has_many :comments
end
Run Code Online (Sandbox Code Playgroud)

我们已经有一篇文章,只有一条评论.两者都是由不同的作者.目标是cache_key在以下情况下对文章进行更改:

  1. 文章的正文或标题发生变化
  2. 它的评论的身体发生了变化
  3. 文章的作者姓名发生了变化
  4. 文章评论的作者姓名发生了变化

因此,默认情况下,我们对案例1和案例2都有好处.

1.9.3-p194 :034 > article.cache_key
 => "articles/1-20130412185804"
1.9.3-p194 :035 > article.comments.first.update_attribute('body', 'First Post!')
1.9.3-p194 :038 > article.cache_key
 => "articles/1-20130412185913"
Run Code Online (Sandbox Code Playgroud)

但不是案例3.

1.9.3-p194 :040 > article.author.update_attribute('name', 'Adam A.')
1.9.3-p194 :041 > article.cache_key
 => "articles/1-20130412185913"
Run Code Online (Sandbox Code Playgroud)

让我们定义一个复合cache_key方法Article.

class Article < ActiveRecord::Base
  attr_accessible :author_id, :body, :title
  has_many :comments
  belongs_to :author

  def cache_key
    [super, author.cache_key].join('/')
  end
end

1.9.3-p194 :007 > article.cache_key
 => "articles/1-20130412185913/authors/1-20130412190438"
1.9.3-p194 :008 > article.author.update_attribute('name', 'Adam B.')
1.9.3-p194 :009 > article.cache_key
 => "articles/1-20130412185913/authors/1-20130412190849"
Run Code Online (Sandbox Code Playgroud)

赢得!但当然这不适用于案例4.

1.9.3-p194 :012 > article.comments.first.author.update_attribute('name', 'Bernard A.')
1.9.3-p194 :013 > article.cache_key
 => "articles/1-20130412185913/authors/1-20130412190849"
Run Code Online (Sandbox Code Playgroud)

那剩下什么选择呢?我们可以对has_many关联做一些事情Author,但has_many不采取{touch: true}选择,可能是有原因的.我想它可以在以下几行中实现.

class Author < ActiveRecord::Base
  attr_accessible :name
  has_many :articles
  has_many :comments

  before_save do
    articles.each { |record| record.touch }
    comments.each { |record| record.touch }
  end
end

article.comments.first.author.update_attribute('name', 'Bernard B.')
article.cache_key
  => "articles/1-20130412192036"
Run Code Online (Sandbox Code Playgroud)

虽然这确实有效.它通过加载,实例化和更新每个文章以及由其他文章逐一进行评论而产生巨大的性能影响.我不相信这是一个合适的解决方案,但它是什么?

当然37signals用例/示例可能会有所不同:project -> todolist -> todo.但我想象一个属于用户的todo项目.

如何解决这个缓存问题?

Gra*_*ett 8

我偶然发现的一种方法是通过缓存键来处理这个问题.has_many_through为文章添加评论者关系:

class Article < ActiveRecord::Base
  attr_accessible :author_id, :body, :title
  has_many :comments
  has_many :commenters, through: :comments, source: :author
  belongs_to :author
end
Run Code Online (Sandbox Code Playgroud)

然后在article/show中我们将构造如下的缓存键:

<% cache [@article, @article.commenters, @article.author] do %>
  <h2><%= @article.title %></h2>
  <p>Posted By: <%= @article.author.name %></p>
  <p><%= @article.body %></p>
  <ul><%= render @article.comments %></ul>
<% end %>
Run Code Online (Sandbox Code Playgroud)

诀窍在于,commenters无论何时添加,删除或更新注释,从关联生成的缓存密钥都将更改.虽然这确实需要额外的SQL查询来生成缓存键,但它可以很好地与Rails的低级缓存一起使用,并且添加像identity_cache gem这样的东西可以轻松地帮助它.

我想看看其他人是否有更清洁的解决方案.