has_many through association dependent destroy under condition of who called destroy
Asked Answered
A

2

8

Is there a way to check, within a before_destroy hook, what object (class) called destroy?

In the following example, when a patient is destroyed, so are their appointments (which is what I want); however I don't want to allow a physician to be destroyed if there are any appointments associated with that physician.

Again, is there a way to do such a check in the before_destory callback? If not, is there any other way to accomplish this "destruction check" based on the "direction" of the call (i.e. based on who called)?

class Physician < ActiveRecord::Base
  has_many :appointments, dependent: :destroy
  has_many :patients, through: :appointments
end


class Patient < ActiveRecord::Base
  has_many :appointments, dependent: :destroy
  has_many :physicians, through: :appointments
end


class Appointment < ActiveRecord::Base
  belongs_to :patient
  belongs_to :physician

  before_destroy :ensure_not_referenced_by_anything_important

  private

  def ensure_not_referenced_by_anything_important
    unless patients.empty?
      errors.add(:base, 'This physician cannot be deleted because appointments exist.')
      false
    end
  end
end
Adhibit answered 6/4, 2012 at 6:19 Comment(0)
D
11

Just say:

class Physician < ActiveRecord::Base
  has_many :appointments, dependent: :restrict_with_exception
  has_many :patients, through: :appointments
end

Note the dependent: :restrict_with_exception. This will cause Active Record to refuse to destroy any Physician records that have associated Appointment records.

See the API docs and the association basics guide.

Danille answered 6/4, 2012 at 8:28 Comment(1)
:restrict was deprecated Aug 10, 2012 in a branch destined for Rails 4. The association basics guide was also updated. :restrict_with_exception provides the same functionality as :restrict did; there is also another similar option, :restrict_with_error, that causes an error to be added to the owner if there is an associated object.Adhibit
B
20

Note that dependent: :destroy on a has_many :through relationship only deletes the association and not the associated record (i.e. the join records will be deleted, but the associated records won't). So if you delete a patient it will only delete the appointment and not the physician. Read the detailed explanation in the API docs.

I have pasted the relevant paragraphs below.

What gets deleted?

There is a potential pitfall here: has_and_belongs_to_many and has_many :through associations have records in join tables, as well as the associated records. So when we call one of these deletion methods, what exactly should be deleted?

The answer is that it is assumed that deletion on an association is about removing the link between the owner and the associated object(s), rather than necessarily the associated objects themselves. So with has_and_belongs_to_many and has_many :through, the join records will be deleted, but the associated records won’t.

This makes sense if you think about it: if you were to call post.tags.delete(Tag.find_by_name('food')) you would want the food tag to be unlinked from the post, rather than for the tag itself to be removed from the database.

Braque answered 24/2, 2013 at 7:37 Comment(2)
Great explanation, Thanks! I was having a hard time figuring out if dependent: :destroy deleted the association and the record on the other side.Chimaera
This seems like a thorough answer, however, I have set it up as you said and when I try to delete a Patient (using rails admin), the Physician is removed also.Siamese
D
11

Just say:

class Physician < ActiveRecord::Base
  has_many :appointments, dependent: :restrict_with_exception
  has_many :patients, through: :appointments
end

Note the dependent: :restrict_with_exception. This will cause Active Record to refuse to destroy any Physician records that have associated Appointment records.

See the API docs and the association basics guide.

Danille answered 6/4, 2012 at 8:28 Comment(1)
:restrict was deprecated Aug 10, 2012 in a branch destined for Rails 4. The association basics guide was also updated. :restrict_with_exception provides the same functionality as :restrict did; there is also another similar option, :restrict_with_error, that causes an error to be added to the owner if there is an associated object.Adhibit

© 2022 - 2024 — McMap. All rights reserved.