Update the counter cache column after paranoid record restore (act_as_paranoid)
There is a column in my model counter_cache
. I use acts_as_paranoid
for this model ( Paranoia gem ). How do I update the counter cache column for a related record when restoring the record?
source to share
You can use a callback before_restore
. Place the .increment_counter
method for the associated entry inside this callback before_restore
.
reset_counters
method or += 1
won't work.
source to share
Below is a typical solution to this problem.
You should already have the paranoid gem installed if you encounter this problem, but for completeness, include the paranoid gem in your Gemfile and install it using the "bundle" command.
# Gemfile
gem 'paranoid'
Create a problem.
# 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 notes on installation:
1) your pluralized is owned_to: join_name must be the same as the counter cache column name: "# {community_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 polymorphic relationships, the associations must be configured 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 must be named like this: "# {community_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 this is the first pass and I haven't tested it fully.
source to share
This is a fairly straightforward solution, although 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 need to replace your calls acts_as_paranoid
with acts_as_paranoid_with_counter_culture
.