habtm: collection.build doesn't build join associations (unlike collection.create)
Reported by bentlegen | May 18th, 2008 @ 08:51 PM
I have a habtm relationship between two models. When I try to #build one object through the collection helper of a record that persists in the database, it doesn't add any ids to that record. On the flip side, #create does store the ids.
Example:
class Project < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :projects
end
# A db-persistent user record
user = User.find(10)
# collection.build
project = user.projects.build
project.users # [] (empty)
project.user_ids # [] (empty)
# collection.create
project = user.projects.create
project.users # [#<Account id: 10 ...>]
project.user_ids # [10]
Comments and changes to this ticket
-
Christopher Swasey (endash) May 19th, 2008 @ 03:38 AM
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 May 19th, 2008 @ 03:51 AM
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) May 19th, 2008 @ 03:51 AM
I misread the ticket, and I was wrong about the tests covering this case.
-
Christopher Swasey (endash) May 19th, 2008 @ 04:10 AM
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 May 19th, 2008 @ 04:20 AM
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.
-
-
Christopher Swasey (endash) May 19th, 2008 @ 04:47 AM
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) May 19th, 2008 @ 05:27 AM
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) May 19th, 2008 @ 06:09 AM
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)
-
Joshua Peek August 20th, 2008 @ 01:52 AM
- → Tag changed from to activerecord bug has_and_belongs_to_many
- → State changed from new to stale
Please Login or create a free account to add a new comment.
You can update this ticket by sending an email to from your email client. (help)
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile »
Source available from github
The Git repository resides at http://github.com/rails
Check out the current development trunk (Edge Rails) with:
git clone git://github.com/rails/rails.git
Creating or reviewing a patch
See the contributor guide.
Creating a feature request
Please don't. If you want a new feature in Rails, you'll have to pull up your sleeves and get busy yourself. Or convince someone else to do it. See the contributor guide on how to get going. But posting them here is just going to lead to ticket root.
Creating a bug report
When creating a bug report, be sure to include as much relevant information as possible. Post the code sample that causes the problem. Preferably, alter the unit tests and show through either changed or added tests how the expected behavior is not occuring.
Security vulnerabilities should be reported via an email to security@rubyonrails.org, do not use trac for reporting security vulnerabilities. All content in trac is publicly available as soon as it is posted.
Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kinda bug, you're creating this ticket in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the ticket automatically will see any activity or that others will jump to fix it. Creating a ticket like this is mostly to help yourself start on the path of fixing the problem and for others to sign on to with a "I'm having this problem too".
