您有几个不同的选项可以跳过计数器缓存的更新,而您选择的选项实际上取决于您希望如何构建应用程序。我将讨论绕过计数器缓存的不同方法,并提及您在这样做时可能需要考虑的一些注意事项。
基本上,您可以通过三种不同的方式跳过计数器缓存的更新:
- Monkey patch your model to disable the counter cache callback
- Use an update method that doesn't trigger callbacks
- 定义一个替代模型,指向没有相同回调行为的同一个表,并在批量插入数据库时使用它
请注意,上面的前两个选项通常与禁用 ActiveRecord 中的回调有关,这是有道理的,因为计数器缓存是通过回调在内部实现的。
当 Rails 加载与计数器缓存关联的模型时,它会动态定义回调方法。如果你想禁用这些回调,你必须首先弄清楚回调名称是什么。
有两种主要方法可以确定 Rails 为实现这些回调而定义的方法。您可以阅读 Rails 源代码以找出它将通过字符串插值生成的名称,或者您可以使用自省来确定您的类响应哪些方法。我将举例说明如何使用自省找出 ActiveRecord 定义的回调,以便自动实现计数器缓存。
假设您有一个名为 SpecialReply 的类,它继承自 ActiveRecord::Base (this example comes from the test suite with Rails) 的 Reply 类。它有一个计数器缓存列,定义如下:
class SpecialReply < ::Reply
belongs_to :special_topic, :foreign_key => 'parent_id', :counter_cache => 'replies_count'
end
在控制台中,您可以使用.methods 查看您的类响应了哪些方法。这会产生很多噪音,因为Object 的每个实例都已经响应了很多方法,因此您可以按如下方式缩小列表范围:
1.9.3-p194 :001 > sr = SpecialReply.new
1.9.3-p194 :002 > sr.methods - Object.methods
在您说的第二行,显示我的 SpecialReply 实例响应的所有方法,减去所有对象响应的方法。这通常有助于通过过滤掉并非特定于您正在查看的类类型的方法来进行自省。
不幸的是,即使在此过滤之后,由于 ActiveRecord 添加到其所有后代类的方法,也会产生很多噪音。在这种情况下,grep 很有用 - 因为 ActiveRecord 有助于创建包含字符串 counter_cache (see the meta-programming used by ActiveRecord to generate a counter cache method for a belongs_to association) 的计数器回调方法,您可以使用以下内容找到与计数器缓存相关的回调定义:
1.9.3-p194 :001 > sr = SpecialReply.new
1.9.3-p194 :002 > sr.methods.map(&:to_s).grep(/counter_cache/)
请注意,由于grep 对字符串进行操作,而methods 返回符号方法名称的数组,因此我们首先使用to_proc (&:) 将所有符号转换为字符串,然后 grep包含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
您应该能够在您的程序中遵循类似的过程来确定 ActiveRecord 添加的方法名称,以便您可以在 existing instructions for removing callbacks 之后将它们剔除。
您在上述选项中的选择实际上取决于您的程序结构,以及您愿意为提高加载数据的效率而考虑的折衷方案。值得注意的是,前两个选项可以通过从外部修改类的行为(猴子补丁)使您的代码可读性降低,并且可以通过绕过数据更新的业务规则(缓存列的更新)使您的系统不稳定。出于这些原因,我会考虑是否可以创建另一个类以优化方式加载数据,同时最大限度地减少对数据可读性或一致性的影响。