问题
Just ran into an issue with a has_many :through association and after/before-destroy callbacks not being triggered.
Say, I have users, groups, and an intermediate relation called membership. I have a form that allows users to be enrolled into groups by creating a new membership record when they check off associated checkboxes. Basically an array of group_ids.
Looks something like this:
Which group would you like to join? (check all that apply)
[] Group A
[] Group B
[] Group C
And I wish to record actions such as joining a group or leaving a group to activity log table and do some other less important thigns.
I have the following defined:
class Group < AR::Base
has_many :memberships
has_many :users, :through => :memberships
end
class Membership < AR::Base
belongs_to :user
belongs_to :group
after_create :log_event_to_audit_table
after_destroy :log_event_to_audit_table
end
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
attr_accessible :group_ids # enables mass-assignment
end
When a new membership record is created the after_create is run as expected. However, the after_destroy does not get triggered!
After google-ing and read up the docs I discovered the reason why:
"Automatic deletion of join models is direct, no destroy callbacks are triggered" - from Ruby Guides.
Hmmmmmm...
So the join model's (in this case Membership's) destroy callbacks are not being triggered. Well that's a downer. Any reason as to why?
So my question what is the best way to work around this issue? Should I define my own membership_ids= method in User model that calls membership.destroy directly?
Open to any suggestions about the best practices in such a scenario.
Thanks!
回答1:
After carefully examining the API docs, it turns out has_many
and has_and_belongs_to_many
("HABTM") have a few options just for this case:
before_add
after_add
before_remove
after_remove
class User < ActiveRecord::Base
has_many :groups, :through => :memberships, :after_remove => :your_custom_method
end
Judging by how many responses I got, this must not be a very well documented/used feature.
Just noting it here for myself and others who may stumble like I did.
回答2:
I've struggled with the same problem recently and solved it by extending association and overriding its delete method:
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships do
def delete(*args)
groups = args.flatten
# destroy memberships in order to trigger their callbacks:
proxy_association.owner.memberships.where(group_id: groups).destroy_all
super
end
end
...
end
回答3:
adding dependent: :destroy
to the has many relationship actually calls the before_destroy
and after_destroy
methods in the Membership
class.
class User < ActiveRecord::Base
has_many :groups, through: :memberships, dependent: :destroy
end
来源:https://stackoverflow.com/questions/4465607/hmt-collection-singular-ids-deletion-of-join-models-is-direct-no-destroy-callb