Association collection callbacks and #build

Hi everyone,

I was surprised to find that when building a collection object, the association callback chain is called. While I’d expect the creation of a record to result in any specified callbacks (i.e., :after_add) being invoked, I wasn’t expecting instantiation to. Was this an intentional decision?

If not, I’d be more than happy to supply a patch.



I think it makes sense to fire before/after_add callback when the
associated object gets build. Doing is really
'adding' the newly build object to the foo.bars collection - so yeah,
it was intentional.

What's your use case for delaying the callback chain ?


There is a related ticket in lighthouse. Looking at it, the consensus seems to be a change in documentation around association callbacks,



In my use case, I’m wanting to fire off an auditing method when a polymorphic object is added to a particular collection. When one of these objects are added to a collection stemming from a Foo object, the auditing needs to take place. But when this object is assigned/created within the scope of a Bar object, which also has_many of these objects, nothing should happen.

Granted, I could rig after_save to check to see if its parent is a Foo, and also check to see if the associated_id has changed to simulate what I’m attempting to achieve - but these collection callbacks seemed to make more sense than doing it that way.

Even the test suite seems to lend itself to only triggering on physical records:

has_many :posts_with_callbacks, :class_name => “Post”, :before_add => :log_before_adding,

:after_add => :log_after_adding,

:before_remove => :log_before_removing,

:after_remove => :log_after_removing

Logging an object that doesn’t actually exist would quickly cause issues, as it did in my use case, IMO.


I think this is a sign that we're lacking some callbacks. My ideal
solution would be to get rid of before/after_add callbacks and replace
them with a super set - something like around_add and arround_commit
callbacks ( much like controller's around filters ) - where 'commit'
is the process of saving records to the db. It'd be good to get more
feedback from people who're actively using these callbacks.

That sounds like a great solution to the tough issue of ordering
semantics. As it stands today, I'm not sure I could explain how
insert/append+before/after/around work together to express precedence.


After grappling with this for a while, here are my thoughts on association callbacks:

First, I now completely understand why :after_add cares nothing for persistence, and is solely a callback for pushing/popping an item from an association collection. At the database level, @category.products.create is going to give you the same results as @product.category = @category. So essentially, if you put a product within a category the latter way, and have some sort of :after_add callback based on the association collection, that callback won’t be fired. It would almost be better to have a callback at the belongs_to association definition if the goal is to have a callback for changes of association.

That being said, what strikes me about :after_add as being odd is that it seems to be the only AR callback - outside of the validation callbacks and maybe after_initialize - that has nothing to do with persistence. And since ActiveRecord by definition is a means of mapping objects to persisted records, I think I was expecting :after_add to have something to do with saving.

With so much happening with ActiveModel (which doesn’t care about persistence), and ActiveRecord (= persistence) adopting callbacks from AM, I think it’s good thing that there’s an active discussion on callbacks happening.