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?
I think it makes sense to fire before/after_add callback when the
associated object gets build. Doing foo.bars.build() 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 ?
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:
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.
Pratik,
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.