Thibaud Clement Thibaud Clement - 4 months ago 28
Ruby Question

Rails 4: counter_cache in has_many :through association with dependent: :destroy

Although similar questions have already been asked:



none of them actually addresses my issue.

I have three models, with a has_many :through association :

class User < ActiveRecord::Base
has_many :administrations
has_many :calendars, through: :administrations
end

class Calendar < ActiveRecord::Base
has_many :administrations
has_many :users, through: :administrations
end

class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end


The join Administration model has the following attributes:

id
user_id
calendar_id
role


I would like to count how many
calendars
each
user
has and how many
users
each
calendar
has.

I was going to go with counter_cache as follows:

class Administration < ActiveRecord::Base
belongs_to :user, counter_cache: :count_of_calendars
belongs_to :calendar, counter_cache: :count_of_users
end


(and, of course, the corresponding migrations to add
:count_of_calendars
to the
users
table and
:count_of_users
to the
calendars
table.)

But then, I stumbled upon this warning in Rails Guides:


4.1.2.4 :dependent

If you set the :dependent option to:


  • :destroy, when the object is destroyed, destroy will be called on its associated objects.

  • :delete, when the object is destroyed, all its associated objects will be deleted directly from the database without calling their
    destroy method.



You should not specify this option on a belongs_to association that is
connected with a has_many association on the other class. Doing so can
lead to orphaned records in your database.



Therefore, what would be a good practice to count how many
calendars
each
user
has and how many
users
each
calendar
has?

leo leo
Answer

Well, dependent: :destroy will destroy the associated records, but it won't update the counter_cache, so you may have wrong count in counter_cache. Instead you can implement a callback that will destroy the associated records, and update your counter_cache.

class Calendar < ActiveRecord::Base

  has_many :administrations
  has_many :users, through: :administrations


  before_destroy :delete_dependents

  private
  def delete_dependents
    user_ids = self.user_ids
    User.delete_all(:calendar_id => self.id)
    user_ids.each do |u_id|
      Calendar.reset_counters u_id, :users
    end
  end
end

And similarly, implement this for User model too

Comments