Rails counter_cache 未正确更新
使用 Rails 3.1.3,我试图找出为什么在通过 update_attributes 更改父记录 id 时我们的计数器缓存没有正确更新。
class ExhibitorRegistration < ActiveRecord::Base
belongs_to :event, :counter_cache => true
end
class Event < ActiveRecord::Base
has_many :exhibitor_registrations, :dependent => :destroy
end
describe ExhibitorRegistration do
it 'correctly maintains the counter cache on events' do
event = Factory(:event)
other_event = Factory(:event)
registration = Factory(:exhibitor_registration, :event => event)
event.reload
event.exhibitor_registrations_count.should == 1
registration.update_attributes(:event_id => other_event.id)
event.reload
event.exhibitor_registrations_count.should == 0
other_event.reload
other_event.exhibitor_registrations_count.should == 1
end
end
此规范失败,表明事件上的计数器缓存没有减少。
1) ExhibitorRegistration correctly maintains the counter cache on events
Failure/Error: event.exhibitor_registrations_count.should == 0
expected: 0
got: 1 (using ==)
我是否应该期望这能起作用,或者我是否需要手动跟踪更改并自己更新计数器?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
来自精细手册:
没有提到当对象从一个所有者移动到另一个所有者时更新缓存。当然,Rails 文档通常不完整,因此我们必须查看源代码进行确认。当你说
:counter_cache =>; true
,您触发对私人的呼叫add_counter_cache_callbacks
方法 和add_counter_cache_callbacks
执行此操作:after_create
回调,该回调调用increment_counter
。before_destroy
回调,调用减量计数器
。attr_readonly
使计数器列只读。我不认为您期望太多,您只是期望 ActiveRecord 比实际情况更完整。
不过,一切并没有丢失,您可以自己填补缺失的部分,而不需要太多的努力。如果您想允许重新设置父级并更新计数器,您可以向您的 ExhibitorRegistration 添加一个
before_save
回调来调整计数器本身,如下所示(未经测试的演示代码):如果您喜欢冒险,您可以将类似的内容修补到
ActiveRecord::Associations::Builder#add_counter_cache_callbacks
中并提交补丁。您所期望的行为是合理的,我认为 ActiveRecord 支持它是有意义的。From the fine manual:
There's no mention of updating the cache when an object is moved from one owner to another. Of course, the Rails documentation is often incomplete so we'll have to look at the source for confirmation. When you say
:counter_cache => true
, you trigger a call to the privateadd_counter_cache_callbacks
method andadd_counter_cache_callbacks
does this:after_create
callback which callsincrement_counter
.before_destroy
callback which callsdecrement_counter
.attr_readonly
to make the counter column readonly.I don't think you're expecting too much, you're just expecting ActiveRecord to be more complete than it is.
All is not lost though, you can fill in the missing pieces yourself without too much effort. If you want to allow reparenting and have your counters updated, you can add a
before_save
callback to your ExhibitorRegistration that adjusts the counters itself, something like this (untested demo code):If you were adventurous, you could patch something like that into
ActiveRecord::Associations::Builder#add_counter_cache_callbacks
and submit a patch. The behavior you're expecting is reasonable and I think it would make sense for ActiveRecord to support it.如果您的计数器已损坏或者您直接通过 SQL 对其进行了修改,则可以修复它。
使用:
示例 1:重新计算 id = 17 的帖子的缓存计数。
来源
示例 2:重新计算所有文章的缓存计数。
If your counter has been corrupted or you've modified it directly by SQL, you can fix it.
Using:
Example 1: Re-compute the cached count on the post with id = 17.
Source
Example 2: Re-compute the cached count on all your articles.
我最近遇到了同样的问题(Rails 3.2.3)。看起来它还没有修复,所以我必须继续进行修复。下面是我如何修改 ActiveRecord::Base 并利用 after_update 回调来保持 counter_cache 同步。
扩展 ActiveRecord::Base
使用以下内容创建一个新文件
lib/fix_counters_update.rb
:上面的代码使用 ActiveModel::Dirty 方法
changes
返回包含已更改属性的哈希以及包含旧值和新值的数组。通过测试该属性是否是关系(即以 /_id/ 结尾),您可以有条件地确定是否需要运行decrement_counter
和/或increment_counter
。测试数组中是否存在nil
至关重要,否则会导致错误。添加到初始值设定项
创建一个新文件
config/initializers/active_record_extensions.rb
,其中包含以下内容:require 'fix_update_counters'
添加到模型< /strong>
对于您想要更新计数器缓存的每个模型,添加回调:
I recently came across this same problem (Rails 3.2.3). Looks like it has yet to be fixed, so I had to go ahead and make a fix. Below is how I amended ActiveRecord::Base and utilize after_update callback to keep my counter_caches in sync.
Extend ActiveRecord::Base
Create a new file
lib/fix_counters_update.rb
with the following:The above code uses the ActiveModel::Dirty method
changes
which returns a hash containing the attribute changed and an array of both the old value and new value. By testing the attribute to see if it is a relationship (i.e. ends with /_id/), you can conditionally determine whetherdecrement_counter
and/orincrement_counter
need be run. It is essnetial to test for the presence ofnil
in the array, otherwise errors will result.Add to Initializers
Create a new file
config/initializers/active_record_extensions.rb
with the following:require 'fix_update_counters'
Add to models
For each model you want the counter caches updated add the callback:
此问题的修复已合并到活动记录主
https://github.com/rails/rails /问题/9722
A fix for this has been merged in to active record master
https://github.com/rails/rails/issues/9722
counter_cache 函数旨在通过关联名称而不是基础 id 列进行工作。在您的测试中,而不是:
尝试
更多信息可以在此处找到:http:// api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
The counter_cache function is designed to work through the association name, not the underlying id column. In your test, instead of:
try
More information can be found here: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html