This project is archived and is in readonly mode.

#220 ✓stale
bentlegen

habtm: collection.build doesn't build join associations (unlike collection.create)

Reported by bentlegen | May 18th, 2008 @ 08:54 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)

    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

    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)

    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)

    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

    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.save
    

    I prefer the syntax of the first example, personally. Maybe I'll look into creating a patch.

  • bentlegen

    bentlegen May 19th, 2008 @ 10:21 AM

    Sorry - my bad, that 2nd example wouldn't work with habtm.

  • Christopher Swasey (endash)

    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)

    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)

    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)

  • josh

    josh August 20th, 2008 @ 01:52 AM

    • State changed from “new” to “stale”
    • Tag set to activerecord, bug, has_and_belongs_to_many
  • jack dempsey (jackdempsey)

    jack dempsey (jackdempsey) July 14th, 2009 @ 08:36 PM

    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

    Jonas Schneider January 30th, 2010 @ 07:59 PM

    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:

    http://gist.github.com/290695

  • Rolf Bjaanes

    Rolf Bjaanes April 14th, 2010 @ 06:34 PM

    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
    
    
  • bingbing

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>

Pages