Пропустить обновление 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

поэтому он не будет запускать обратный вызов декремента.

Другие вопросы по тегам