I have a counter_cache
column in my model. I use acts_as_paranoid
for this model (Paranoia gem). How to update a counter cache column for an associated record when I restore a record?
Update a counter cache column after restoring a record with paranoia (acts_as_paranoid)
1.6k Views Asked by Sergey Alekseev AtThere are 3 best solutions below

This is a pretty simple solution, though some will disagree with the semantics.
module ActiveRecord
# trigger create callbacks for counter_culture when restoring
class Base
class << self
def acts_as_paranoid_with_counter_culture
acts_as_paranoid
simulate_create = lambda do |model|
model.run_callbacks(:create)
model.run_callbacks(:commit)
end
after_restore(&simulate_create)
end
end
end
end
Then you just have to replace your invocations of acts_as_paranoid
with acts_as_paranoid_with_counter_culture
.

Here is a model concern solution to this problem.
You should already have the paranoid gem installed if you are facing this problem but for completeness-sake, include the paranoid gem in your Gemfile and install it using the 'bundle' command.
# Gemfile
gem 'paranoid'
Create a concern.
# app/models/concerns/paranoid_deletable.rb
module ParanoidDeletable
extend ActiveSupport::Concern
included do
# activate the paranoid behavior
acts_as_paranoid
# before restoring the record, manually increment the counter
before_restore :increment_counter_cache
end
module ClassMethods
def counter_column_name
"#{self.name.underscore.pluralize}_count"
end
end
def counter_associations
associated_counters = []
# get all belongs_to associations
self.reflect_on_all_associations(:belongs_to).collect do |association|
return unless association.options[:counter_cache]
associated_klass_name = association.options[:polymorphic] ? self.send("#{association.name}_type") : association.class_name
associated_klass_name.constantize.column_names.each do |column_name|
# collect the association names and their classes if a counter cache column exists for this (self) class.
associated_counters << { association_name: association.name, klass_name: associated_klass_name } if(column_name == self.class.counter_column_name)
end
end
# return the array of { association_name, association_klass } hashes
associated_counters
end
private
def increment_counter_cache
# before restore...
self.counter_associations.each do |counter_association|
association_name = counter_association[:association_name]
klass_name = counter_association[:klass_name]
# ...increment all associated counters
klass_name.constantize.increment_counter(self.class.counter_column_name.to_sym, self.send("#{association_name}_id".to_sym))
end
end
end
Then in your model.
# app/models/post.rb
class Post < ActiveRecord::Base
include ParanoidDeletable
belongs_to :user, :counter_cache => true
...
end
A few setup notes:
1) your pluralized belongs_to :association_name must match your counter cache column name: "#{association_name}_count". For example:
# Will work
console > user.posts_count
=> 212
# Won't work
console > user.how_much_they_talk_count
=> 212
2a) If you are using a polymorphic relationship, it's associations need to be set up appropriately in both models. For example:
# app/models/post.rb
....
has_many :comments, as: :commentable
....
# app/models/comment.rb
...
belongs_to :commentable, polymorphic: true, counter_cache: true
...
2b) If you are using a polymorphic relationship, the reference type field needs to be named as follows: "#{association_name}_type". For example:
# Will work
console > comment.commentable_type
=> "Post"
# Won't work
console > comment.commentable_class_name
=> "Post"
Disclaimer: I tried to make this modular but it's a first-pass and I haven't tested it thoroughly.
You may use a
before_restore
callback. Place.increment_counter
method for an associated record inside thisbefore_restore
callback.reset_counters
method or+= 1
won't work.