I want to run some code before an object is removed from a has_many association.
I thought that I would be able to do this with the before_remove callback however for some reason this isn’t firing and I don’t understand why.
class Person < ActiveRecord::Base
has_many :limbs, before_remove: :print_message
def print_message
puts 'removing a limb'
end
end
class Limb < ActiveRecord::Base
belongs_to :person
end
While this code should print “removing a limb” during the destruction of a limb but it doesn’t.
p = Person.create;
l = Limb.create person: p;
p.limbs.first.destroy
# SQL (2.1ms) DELETE FROM "limbs" WHERE "limbs"."id" = ? [["id", 5]]
# => #<Limb id: 5, person_id: 3, created_at: "2012-01-17 11:28:01", updated_at: "2012-01-17 11:28:01">
Why does this destroy action not cause the print_message method to run?
EDIT – does this before_remove callback exist?
A number of people have asked whether this callback exists. Although I can find very few further references to it, it is documented in the Rails documentation:
It’s an association callback though rather than a root ActiveRecord callback
Edit 2 – why not just use before_destroy on Limb?
Some people are asking why I’m not using the before_destroy callback on Limb. The reason is that I want person to check that there is a minimum number of limbs and that the last one is never destroyed. This is the original problem:
How do you ensure that has_many always "has a minimum"?
before_removecallback exists as an option in Associations callbacks. It’s not the same asbefore_destroy, which is an ActiveRecord callback.This is how you use it:
You’re also calling a
removemethod incorrectly.Here you’re calling it on
Limbinstance, that’s why nothing is triggered.Call it on an association you created:
EDIT
For preserving minimum of associated objects you can do something like this:
This however does not get triggered on
p.limbs.destroy_all, so you have to do something like thisp.limbs.each {|l| p.limbs.destroy(l)}Why it does not get triggered by
destroy_all?Because of this:
It iterates over each element of an association and executes destroy action on an object and not on an association, that’s why.