ActiveRecord has_many association doesn't call after_destroy when setting a collection

has-many, ruby-on-rails

Solution

According to docs:

collection=objects

Replaces the collections content by deleting and adding objects as appropriate. If the :through option is true callbacks in the join models are triggered except destroy callbacks, since deletion is direct.

Problem

3 models: User, Movie, Like ``` User has_many :likes User has_many :movies, through: :likes ``` This code: ``` user.movies = [ m1, m2, m3 ] ``` calls `Like#after_create` for the new `Like` records that relate user and m1/m2/m3. Then, this code: ``` user.movies = [ m3, m4 ] ``` doesn't call `Like#after_destroy` for the `Like` records that relate user to m1/m2, but does call `Like#after_create` for the new relationship with m4. The `movies` collection could be set manually or with a form that has `user[movie_ids]` checkboxes, and then ``` user.update_attributes(params[:user]) ``` - What is the right Rails approach for setting a collection? - How do I force it to call after_destroy? Update: As @jdoe cited from the docs, it's not achievable when assigning new collection or when deleting from a collection (`user.movies.delete(m1)`). The only way is to use `before_remove`/`after_remove` callbacks on the user model (and in case of a polymorphic relationship -- any other model), with the `has_many` definition: ``` has_many :movies, through: :likes, before_remove: :before_like_destroy, after_remove: after_like_destroy def before_like_destroy(movie) like = self.likes.where(movie_id: movie) # trigger the after_destroy on like like.trigger_before_destroy # to be implemented on like, should call what the original callbacks contained end def after_like_destroy(movie) # no way to get Like object here because it was already removed, need to remember it on the before_destroy somehow end ``` Can't understand the logic behind it. It makes callbacks on relationship models totally useless. If something happened on `after_create` it can't be undone in `after_destroy`, and since it's better practice to have after and before logic together and not separately, it makes all callbacks useless. Think I'm gonna write a gem that does that automagically.

Original source