The source issue is: #8672
This PR is only a failing test case as I am not completely sure how to tackle the problem.
I investigated and can confirm that the order in which after_create and has_and_belongs_to_many associations are defined does impact the functionality.
If the callback is defined before the association every HABTM record created inside the callback will be inserted twice. The reason is AutosaveAssociation#save_collection_association. It will pick up the already created records and save them again. This happens because @new_record_before_save will be true and since it's an after_create callback, the associations are inserted directly with <<.
My test case results in the following failure:
test case to illustrate order dependent callbacks
@jonleighton @rafaelfranca is it known and expected that HABTM associations and callbacks are order dependent? If this is the expected behavior, feel free to close. Otherwise we should look for a possible solution.
Definitely looks like unexpected behaviour to me.
No clue if I can squash this or not, but I'm starting to investigate. I'll update in here with whatever I find, at the least.
Was any progress made on this?
I'll try to spend a little more time on it tonight, but no promises if I'll get anywhere. I was just trying to figure it out when I had to give up on it last time.
Status update: spent hours learning all the surrounding code, figured out that @senny is right on the money. No idea for a fix yet, but I'll give that a stab tomorrow.
I was thinking about this issue lately and it could be a tricky one to solve. Let me know if you find an angle.
@senny the problem is that AutosaveAssociation is using the public callback API to implement its functionality which means that it's subject to the definition order. There used to be a lot of these problems but most of them went away when @jonleighton refactored the association code.
The belongs_to :foo, :touch => true code is also using the callbacks api so that may be definition order dependent as well.
belongs_to :foo, :touch => true
I basically came to the same conclusion as @pixeltrix. I think the best solution is to have separate callback chains for these purposes. Here's why:
The downsides I can see:
One final alternative that presents itself to me is to have an entirely separate, not publicly exposed, set of callback chains. Of course, this could be enforced more by code (of course, anyone can actually access them if they really want) or more by convention (documented as "don't touch unless you really know what you're doing"). A "separate set" is flexible concept, of course, so it could mean separate objects managing it, or a conceptually distinct set, managed by the same code, but distinct by convention.
I'm working on the separate by convention approach right now to see how the code turns out. If everyone is radically opposed to that, I'll stop and move on to something else.
I'm closing this one as it is just a test-case without a fix. It will remain linked in the issue.