This project is archived and is in readonly mode.
habtm: collection.build doesn't build join associations (unlike collection.create)
-
Christopher Swasey (endash)
Could you clarify what version of rails you are using? Edge rails includes tests which test this functionality, and they all pass.
Also, #build doesn't save the created records, and so they won't have IDs.
-
bentlegen
activerecord (2.0.2)
The problem is that, even if I call #save after #build, the associations aren't saved in the join table. The AR documentation suggests they should.
I'll give Edge Rails a try.
-
Christopher Swasey (endash)
I misread the ticket, and I was wrong about the tests covering this case.
-
Christopher Swasey (endash)
From what I can tell, this isn't a bug:
When you #build off an association, the join table is updated when you call #save on the object from which you called #build.
The association collects all unsaved members, calls #insert_record for each record, and upon saving the record (and getting an ID to use in the join table) it creates the join table record with the newly gotten ID, and the pre-existing ID of the record upon through you called #build.
Basically, what you're trying to do is to use the association in reverse. On a related note, ActiveRecord will not allow you to call #build through an association belonging to an unsaved object. Which is kind of what you're trying to do, in effect.
-
bentlegen
Thanks for the explanation Chris - I understand now.
Still, it seems odd to me that these two code examples behave differently:
project = user.project.build project.save # vs project = Project.new(:user_id => user) project.saveI prefer the syntax of the first example, personally. Maybe I'll look into creating a patch.
-
bentlegen
Sorry - my bad, that 2nd example wouldn't work with habtm.
-
Christopher Swasey (endash)
A quick look through the source seems to suggest that simply adding the existing record to the association collection of the new record would create the desired symmetry.
Then, calling #save on the new object would save it, and because it was a new_record before being saved, it's entire association collection would then be passed one at a time to insert_record, which would then save the existing record and then create the join table record.
I think that would do it, but that's just from 15 minutes of looking at the applicable source files.
-
Christopher Swasey (endash)
Ah... the two records want to validate each other. That results in a nice runaway loop.
Code could be added to the validation to prevent looping, but I'm wondering if its worth the trouble. Something tells me this sort of change would turn into the programming equivalent of quickand: the more you struggle, the deeper you get, until there's no escape.
-
Christopher Swasey (endash)
I am just all kinds of wrong tonight. You can, of course, call #build on a new record just fine. (it's #create that you can't call on a new record)
-
josh
- State changed from new to stale
- Tag set to activerecord, bug, has_and_belongs_to_many
-
jack dempsey (jackdempsey)
Is there a reason this was marked as stale? I'm seeing very something similar on rails 2.3.2. The bottom line is that record.habtm_association.create(...) does NOT work like record.habtm_association.build(...).save as it implies in the docs.
Tested this a few ways locally and can confirm it consistently. If this is expected behavior, then we really should update the docs. If it should work this way, then it sounds like a patch is in order. Either way, this cost me a good part of a day, and I'd appreciate moving this back to new or open.
-
Jonas Schneider
This is still an issue on Rails 2.3.5. Caused me a lot of headaches in the past days.
Full reproduction steps and a failing test case for added pleasure are in this gist: -
Rolf Bjaanes
Had a quick look at the gist, and this is not a bug.
You are saving the instance of the associated object,
but not the object carrying the association to be saved.Following assumes habtm relations for projects and users.
@user = User.first @project = @user.projects.build(:name => "Make your bed") # if you do @project.save here, as in the gist, # association will not be saved, but if you do : @user.save # it will save the @user with its associated projects