Ruby on rails Rails计数器缓存未正确更新

Ruby on rails Rails计数器缓存未正确更新,ruby-on-rails,ruby-on-rails-3.1,rails-activerecord,Ruby On Rails,Ruby On Rails 3.1,Rails Activerecord,使用Rails3.1.3,我试图弄明白为什么在通过update\u属性更改父记录id时,计数器缓存没有被正确更新 class ExhibitorRegistration < ActiveRecord::Base belongs_to :event, :counter_cache => true end class Event < ActiveRecord::Base has_many :exhibitor_registrations, :dependent =>

使用Rails3.1.3,我试图弄明白为什么在通过update\u属性更改父记录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
我是否应该期望此功能正常工作,或者我是否需要手动跟踪更改并自己更新计数器?

来自:

:计数器\u缓存

通过使用
递增计数器
递减计数器
在关联类上缓存所属对象的数量。当创建此类的对象时,计数器缓存将递增,而当销毁此类的对象时,计数器缓存将递减

当对象从一个所有者移动到另一个所有者时,没有提到更新缓存。当然,Rails文档通常是不完整的,因此我们必须查看源代码以进行确认。当您说
:counter_cache=>true
时,您和:

  • 在创建调用的回调后添加一个
  • 在销毁回调之前添加一个调用的
  • 调用以使计数器列为只读
  • 我不认为你期望太高,你只是期望ActiveRecord比它更完整

    不过,并不是所有的东西都丢了,你可以自己不费吹灰之力地补上缺失的部分。如果您想允许重新租用并更新您的计数器,您可以在保存前向参展商注册处添加一个
    回调,以调整计数器本身,类似这样(未测试的演示代码):

    class ExhibitorRegistrationtrue
    在\u保存:修复\u计数器\u缓存之前,:if=>->(er){!er.new\u record?&&er.event\u id\u changed?}
    私有的
    def fix_计数器_缓存
    事件递减计数器(:参展商注册计数,自我事件id)
    事件增量计数器(:参展商注册计数,自我事件id)
    结束
    结束
    

    如果你喜欢冒险,你可以在
    ActiveRecord::Associations::Builder#add_counter_cache_callbacks
    中添加类似的补丁并提交补丁。您所期望的行为是合理的,我认为ActiveRecord支持它是有意义的。

    计数器缓存功能设计为通过关联名称而不是基础id列工作。在您的测试中,而不是:

    registration.update_attributes(:event_id => other_event.id)
    
    试一试


    更多信息可以在这里找到:

    我最近遇到了同样的问题(Rails 3.2.3)。看起来它还没有被修复,所以我不得不继续进行修复。下面是我如何修改ActiveRecord::Base并利用更新后回调来保持计数器缓存的同步

    扩展ActiveRecord::Base

    使用以下内容创建一个新文件
    lib/fix\u counters\u 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)
    
    上面的代码使用方法
    changes
    ,该方法返回一个包含已更改属性的哈希值以及一个旧值和新值的数组。通过测试属性以查看它是否是一种关系(即以/\u id/结尾),可以有条件地确定是否需要运行
    递减计数器
    和/或
    递增计数器
    。必须测试数组中是否存在
    nil
    ,否则将导致错误

    添加到初始值设定项中

    使用以下内容创建一个新文件
    config/initializers/active_record_extensions.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)
    
    需要“修复更新计数器”

    添加到模型中

    对于要更新计数器缓存的每个型号,请添加回调:

    class Comment < ActiveRecord::Base
      after_update :fix_updated_counters
      ....
    end
    
    class注释
    此问题的修复程序已合并到active record master中


    如果您的计数器已损坏,或者您直接用SQL对其进行了修改,则可以修复它

    使用:

    ModelName.reset_counters(id_of_the_object_having_corrupted_count, one_or_many_counters)
    
    示例1:重新计算id为17的帖子上的缓存计数。

    Post.reset_counters(17, :comments)
    
    Article.ids.each { |id| Article.reset_counters(id, :comments) }
    

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

    Post.reset_counters(17, :comments)
    
    Article.ids.each { |id| Article.reset_counters(id, :comments) }
    

    这是行不通的,计数器更新只绑定到创建和销毁,这样它们就不会被更改触发。我只是仔细检查了一下,这是在通过ExhibitorRegistration实例上的update_属性修改活动时递增和递减cexhibitor_registrations_count列。我使用的是Rails 3.0.7如果你看一下
    ActiveRecord::Associations::Builder::BelongsTo#add_counter_cache_callbacks
    你就会明白我在说什么。但是,您将在
    ActiveRecord::Associations::BelongsToAssociation\35; update\u计数器中看到更多的计数器摆弄。我想知道是否有两个独立的代码路径不太一致。@muistooshort当我开始尝试将补丁应用到Rails时,我看到了同样的情况。但是,我希望在控制器中保持update_属性工作,而不必显式查找事件并将其添加到参数中。我仍在研究如何正确地修补Rails,但我要过几天才能做出准确的评估。@MichaelGater:我的看法是,同一件事有两条不同的代码路径,而只有id的路径是不完整的。看起来像一个由两部分组成的bug:文档不正确,因为它没有说明“更改”案例,并且id-only路径不完整,因为它没有处理“更改”案例。谢谢@mu太短,这肯定解决了问题。我认为这在ActiveRecord本身确实值得注意,我会考虑提交一个补丁。@MichaelGuter:酷,别忘了在你的补丁中包括一个文档更新:)@MichaelGuter:你可能也想试试Ben的方法。我将再次检查Rails代码,看看是否遗漏了任何内容。这可能只是一个bug和糟糕/不完整的文档。@Pierre:还有
    lambda{| er |…}
    。有时能帮上忙(但不总是)。这个答案帮了大忙。我还要提到,读者