I have a model that uses rails' built-in counter_cache association to increment/decrement counts. I have a requirement wherein I need to disable this when I destroy the model for a specific situation. I have tried to do something like Model.skip_callback(:destroy, :belongs_to_counter_cache_after_update)
but it doesn't seem to work as expected (i.e it still ends up decrementing the associated model). Any helpful pointers would be appreciated.

- 21
- 2
-
If you are ok to skip all callbacks, you can use `Model.first.delete` – Md. Farhan Memon Jun 10 '17 at 03:29
-
Yeah I'm aware of methods to skip callbacks but I have a specific need to disable just the counter callback. For now, I'm using `destroy_all` and then reset the counter (which seems wasteful). – db_db Jun 11 '17 at 01:15
2 Answers
One option is to temporarily override the method responsible for updating the cache count in case of destroy. For example if you have following two models
class Category < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :category, counter_cache: true
end
Now you can try to find the methods responsible for updating cache count with following
2.1.5 :038 > Product.new.methods.map(&:to_s).grep(/counter_cache/)
This shows all the product instance methods which are related to counter_cache, with following results
=> ["belongs_to_counter_cache_before_destroy_for_category", "belongs_to_counter_cache_after_create_for_category", "belongs_to_counter_cache_after_update_for_category"]
From the names of the methods it shows that
"belongs_to_counter_cache_after_create_for_category"
might be responsible for counter cache update after destroy. So I decided to temporarily override this method with one fake method which doesn't do anything(to skip counter cache update)
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
Now if you will destroy any product object, it will not update counter cache in Category table. But its very important to restore the actual method after you have run your code to destroy specific objects. To restore to actual class methods you can do following
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
To ensure that the methods definitions always restored after your specific destroy tasks, you can write a class method, that will make sure to run both override and restore code
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
Now if you destroy any product object as given following
Product.without_counter_cache_update_on_destroy { Product.last.destroy }
it will not update the counter cache in Category table.
References:
Disabling ActiveModel callbacks https://jeffkreeftmeijer.com/2010/disabling-activemodel-callbacks/
Temporary overriding methods: https://gist.github.com/aeden/1069124

- 23
- 5
You can create a flag to decide when callback should be run, something like:
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
so before you destroy your object of a Model, just set value for skip_counter_cache_update
:
@object = YourModel.find(some_id)
@object.skip_counter_cache_update = true
@object.destroy
so it will not run decrement callback.

- 8,219
- 5
- 33
- 56
-
1The callback to decrement counter cache is built into rails and I want to disable that instead of creating my own counter methods. – db_db Jun 12 '17 at 19:02