Пропустить обновление counter_cache рельсов
У меня есть модель, которая использует встроенную в rails ассоциацию counter_cache для увеличения / уменьшения количества. У меня есть требование, при котором мне нужно отключить это, когда я уничтожаю модель для конкретной ситуации. Я пытался сделать что-то вроде Model.skip_callback(:destroy, :belongs_to_counter_cache_after_update)
но он, кажется, не работает должным образом (то есть все равно в конечном итоге уменьшает соответствующую модель). Любые полезные указатели будут оценены.
2 ответа
Один из вариантов - временно переопределить метод, отвечающий за обновление счетчика кэша в случае уничтожения. Например, если у вас есть следующие две модели
class Category < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :category, counter_cache: true
end
Теперь вы можете попытаться найти методы, отвечающие за обновление счетчика кэша, с помощью следующих
2.1.5 :038 > Product.new.methods.map(&:to_s).grep(/counter_cache/)
Здесь показаны все методы экземпляра продукта, связанные с counter_cache, со следующими результатами
=> ["belongs_to_counter_cache_before_destroy_for_category", "belongs_to_counter_cache_after_create_for_category", "belongs_to_counter_cache_after_update_for_category"]
Из названий методов видно, что
"belongs_to_counter_cache_after_create_for_category"
может быть ответственным за обновление счетчика кеша после уничтожения. Поэтому я решил временно переопределить этот метод одним поддельным методом, который ничего не делает (чтобы пропустить обновление счетчика кэша)
Product.class_eval do
def fake_belongs_to_counter_cache_before_destroy_for_category; end
alias_method :real_belongs_to_counter_cache_before_destroy_for_category, :belongs_to_counter_cache_before_destroy_for_category
alias_method :belongs_to_counter_cache_before_destroy_for_category, :fake_belongs_to_counter_cache_before_destroy_for_category
end
Теперь, если вы уничтожите какой-либо объект продукта, он не будет обновлять кеш счетчика в таблице категорий. Но очень важно восстановить реальный метод после того, как вы запустили свой код для уничтожения определенных объектов. Чтобы восстановить реальные методы класса, вы можете сделать следующее
Product.class_eval do
alias_method :belongs_to_counter_cache_before_destroy_for_category, :real_belongs_to_counter_cache_before_destroy_for_category
remove_method :real_belongs_to_counter_cache_before_destroy_for_category
remove_method :fake_belongs_to_counter_cache_before_destroy_for_category
end
Чтобы гарантировать, что определения методов всегда восстанавливаются после ваших конкретных задач уничтожения, вы можете написать метод класса, который обеспечит выполнение кода переопределения и восстановления
class Product < ActiveRecord::Base
belongs_to :category, counter_cache: true
def self.without_counter_cache_update_on_destroy(&block)
self.class_eval do
def fake_belongs_to_counter_cache_before_destroy_for_category; end
alias_method :real_belongs_to_counter_cache_before_destroy_for_category, :belongs_to_counter_cache_before_destroy_for_category
alias_method :belongs_to_counter_cache_before_destroy_for_category, :fake_belongs_to_counter_cache_before_destroy_for_category
end
yield
self.class_eval do
alias_method :belongs_to_counter_cache_before_destroy_for_category, :real_belongs_to_counter_cache_before_destroy_for_category
remove_method :real_belongs_to_counter_cache_before_destroy_for_category
remove_method :fake_belongs_to_counter_cache_before_destroy_for_category
end
end
end
Теперь, если вы уничтожите какой-либо объект продукта, как указано ниже
Product.without_counter_cache_update_on_destroy { Product.last.destroy }
он не будет обновлять кеш счетчика в таблице категорий.
Рекомендации:
Отключение обратных вызовов ActiveModel https://jeffkreeftmeijer.com/2010/disabling-activemodel-callbacks/ Методы временного переопределения: https://gist.github.com/aeden/1069124
Вы можете создать флаг, чтобы решить, когда должен выполняться обратный вызов, например:
class YourModel
attr_accessor :skip_counter_cache_update
def decrement_callback
return if @skip_counter_cache_update
# Run callback to decrement counter cache
...
end
end
поэтому, прежде чем уничтожить ваш объект модели, просто установите значение для skip_counter_cache_update
:
@object = YourModel.find(some_id)
@object.skip_counter_cache_update = true
@object.destroy
поэтому он не будет запускать обратный вызов декремента.