Rails counter_cache未正确更新

Mic*_*erl 21 ruby-on-rails ruby-on-rails-3.1 rails-activerecord

使用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
Run Code Online (Sandbox Code Playgroud)

此规范失败,表明事件上的计数器缓存未递减.

1) ExhibitorRegistration correctly maintains the counter cache on events
   Failure/Error: event.exhibitor_registrations_count.should == 0
     expected: 0
          got: 1 (using ==)
Run Code Online (Sandbox Code Playgroud)

我是否应该期望这个工作或我是否需要手动跟踪更改并自行更新计数器?

mu *_*ort 48

精细手册:

:counter_cache

通过使用increment_counter和缓存关联类上的所属对象的数量decrement_counter.计数器缓存在创建此类的对象时递增,在销毁时递减.

当对象从一个所有者移动到另一个所有者时,没有提到更新缓存.当然,Rails文档通常是不完整的,因此我们必须查看源代码以进行确认.当你说:counter_cache => true,你触发私人add_counter_cache_callbacks方法的调用,add_counter_cache_callbacks执行此操作:

  1. 添加一个after_create调用的回调increment_counter.
  2. 添加一个before_destroy调用的回调decrement_counter.
  3. 调用attr_readonly将计数器列只读.

我不认为你期望太多,你只是期望ActiveRecord比它更完整.

一切都没有丢失,你可以自己填写缺失的部分而不需要太多的努力.如果要允许重排根,有你的柜台更新,你可以添加一个before_save回调到您的ExhibitorRegistration,调整计数器本身,像这样(未经测试的演示代码):

class ExhibitorRegistration < ActiveRecord::Base
  belongs_to :event, :counter_cache => true
  before_save :fix_counter_cache, :if => ->(er) { !er.new_record? && er.event_id_changed? }

private

  def fix_counter_cache
    Event.decrement_counter(:exhibitor_registration_count, self.event_id_was)
    Event.increment_counter(:exhibitor_registration_count, self.event_id)
  end

end
Run Code Online (Sandbox Code Playgroud)

如果你喜欢冒险,你可以修补这样的东西ActiveRecord::Associations::Builder#add_counter_cache_callbacks并提交补丁.您期望的行为是合理的,我认为ActiveRecord支持它是有意义的.

  • 这个答案是一个很大的帮助.我还要提到读者应该注意,如果他们的counter_cache'd关系(让我们称之为xyz)是多态的,他们应该:1.如果xyz_id或xyz_type列发生变化,请确保fix_counter_cache,2.记得在count_counter上xyz_type_was表示的类,xyz_type表示的类的increment_counter. (2认同)

Is *_* Ma 13

如果您的计数器已损坏或您已通过 SQL 直接修改它,您可以修复它。

使用:

ModelName.reset_counters(id_of_the_object_having_corrupted_count, one_or_many_counters)
Run Code Online (Sandbox Code Playgroud)

示例 1:重新计算 id = 17 的帖子的缓存计数。

Post.reset_counters(17, :comments)
Run Code Online (Sandbox Code Playgroud)

来源

示例 2:重新计算所有文章的缓存计数。

Article.ids.each { |id| Article.reset_counters(id, :comments) }
Run Code Online (Sandbox Code Playgroud)


Cur*_*ley 5

我最近遇到了同样的问题(Rails 3.2.3).看起来还有待修复,所以我必须继续修复.下面是我如何修改ActiveRecord :: Base并利用after_update回调来保持我的counter_caches同步.

扩展ActiveRecord :: Base

lib/fix_counters_update.rb使用以下内容创建新文件:

module FixUpdateCounters

  def fix_updated_counters
    self.changes.each {|key, value|
      # key should match /master_files_id/ or /bibls_id/
      # value should be an array ['old value', 'new value']
      if key =~ /_id/
        changed_class = key.sub(/_id/, '')
        changed_class.camelcase.constantize.decrement_counter(:"#{self.class.name.underscore.pluralize}_count", value[0]) unless value[0] == nil
        changed_class.camelcase.constantize.increment_counter(:"#{self.class.name.underscore.pluralize}_count", value[1]) unless value[1] == nil
      end
    }
  end 
end

ActiveRecord::Base.send(:include, FixUpdateCounters)
Run Code Online (Sandbox Code Playgroud)

上面的代码使用ActiveModel :: Dirty方法changes,该方法返回包含已更改属性的哈希值以及旧值和新值的数组.通过测试属性以查看它是否是关系(即以/ _id /结尾),您可以有条件地确定是否decrement_counter和/或increment_counter需要运行.测试nil数组中是否存在是偶然的,否则将导致错误.

添加到初始化程序

config/initializers/active_record_extensions.rb使用以下内容创建新文件:

require 'fix_update_counters'

添加到模型

对于您希望计数器缓存更新的每个模型,添加回调:

class Comment < ActiveRecord::Base
  after_update :fix_updated_counters
  ....
end
Run Code Online (Sandbox Code Playgroud)