如何在运行时禁用Rails关联counter_cache

Chr*_*ing 6 postgresql activerecord ruby-on-rails

我正在批量导入记录,并且不希望每次都更新计数器.我想在批量upsert期间跳过counter_cache sql更新,然后在循环结束时调用reset_counters.

我试过了:

my_model = MyModel.find_or_initialize_by_slug row[:slug]
my_model.association(:my_association).reflection.options[:counter_cache] = false
my_model.attributes = {:name => "Christopher"}
my_model.save!
Run Code Online (Sandbox Code Playgroud)

但我可以在sql输出中看到它仍在更新counter_cache.

注意:我不能使用activerecord-import,因为我想执行upserts并且我正在使用postgresql

Jus*_*geb 5

您有一些不同的选项可以跳过计数器缓存的更新,而您选择的更新实际上取决于您希望如何构建应用程序.我将讨论您可以绕过计数器缓存的不同方法,并提及您可能想要做的一些注意事项.

基本上,有三种不同的方法可以跳过计数器缓存的更新:

  1. Monkey修补模型以禁用计数器缓存回调
  2. 使用不触发回调的更新方法
  3. 定义指向不具有相同回调行为的同一个表的替代模型,并在批量插入数据库时​​使用该模型

请注意,上面的前两个选项通常与在ActiveRecord中禁用回调相关,这是有道理的,因为计数器缓存是通过回调在内部实现的.

当Rails加载与计数器缓存有关联的模型时,它会动态定义回调方法.如果要将这些作为回调禁用,则必须首先弄清楚回调名称是什么.

有两种主要方法可以确定Rails为实现这些回调而定义的方法.您可以阅读Rails源代码以找出它将通过String intorpolation生成的名称,或者您可以使用内省来确定您的类响应的方法.我将举例说明如何使用内省来找出ActiveRecord定义的回调,以便自动实现计数器缓存.

假设你有一个名为SpecialReply的类,它来自一个来自ActiveRecord :: Base的Reply类(这个例子来自带有Rails的测试套件).它有一个计数器缓存列,定义如下:

class SpecialReply < ::Reply
  belongs_to :special_topic, :foreign_key => 'parent_id', :counter_cache => 'replies_count'
end
Run Code Online (Sandbox Code Playgroud)

在控制台中,您可以通过使用查看您的类响应的方法.methods.这会产生很多噪音,因为每个实例都Object已经响应了很多方法,所以你可以按如下方式缩小列表范围:

1.9.3-p194 :001 > sr = SpecialReply.new
1.9.3-p194 :002 > sr.methods - Object.methods
Run Code Online (Sandbox Code Playgroud)

在你说的第二行,告诉我我的SpecialReply实例响应的所有方法,减去所有对象响应的方法.这通常会通过过滤掉您正在查看的类类型并不特定的方法来帮助进行内省.

不幸的是,即使经过这种过滤,由于ActiveRecord为其所有后代类添加的方法,也会产生很多噪音.在这种情况下grep是有用的-因为ActiveRecord的有益创建一个包含字符串计数器回调方法counter_cache(见的元编程使用的ActiveRecord,产生了一个计数器缓存法belongs_to协会),你可以找出定义相关的回调计数器,具有以下缓存:

1.9.3-p194 :001 > sr = SpecialReply.new
1.9.3-p194 :002 > sr.methods.map(&:to_s).grep(/counter_cache/)
Run Code Online (Sandbox Code Playgroud)

请注意,由于grep对String进行操作并methods返回符号方法名称数组,因此我们首先使用a to_proc(&:)将所有符号转换为字符串,然后将包含的符号格式化为counter_cache.这让我有了以下方法,似乎它们可能是由ActiveRecord自动生成的,作为实现计数器缓存的回调:

belongs_to_counter_cache_after_create_for_special_topic
belongs_to_counter_cache_before_destroy_for_special_topic
belongs_to_counter_cache_after_create_for_topic
belongs_to_counter_cache_before_destroy_for_topic
belongs_to_counter_cache_after_create_for_topic_with_primary_key
belongs_to_counter_cache_before_destroy_for_topic_with_primary_key
Run Code Online (Sandbox Code Playgroud)

您应该能够在程序中遵循类似的过程来确定ActiveRecord添加的方法名称,以便您可以按照现有的删除回调的说明将其删除.

您从上述选项中的选择实际上取决于您的程序结构,以及您愿意考虑的权衡,以提高加载数据的效率.值得注意的是,前两个选项可以通过从外部修改类的行为(猴子修补)来降低代码的可读性,并且可以通过绕过数据更新的业务规则(缓存列的更新)来使系统不稳定.出于这些原因,我会考虑是否可以创建另一个类来以优化的方式加载数据,同时最大限度地减少对可读性或数据一致性的影响.