Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

has_one :through not working?

#1749

Hi all! I'm having the strangest problem, here's the code, using Rails 2.2.2:

class User < ActiveRecord::Base
    has_one :profile
    has_one :contact, :through => :profile
end

class Contact < ActiveRecord::Base
    has_one :profile
    has_one :user, :through => :profile
end

class Profile < ActiveRecord::Base
    belongs_to :contact
    belongs_to :user
end

and the migrations:

create_table :users do |t|
  t.string :username, :null => false
  t.string :encrypted_password, :null => false
  t.string :salt, :null => false
  t.timestamps
end

create_table :contacts do |t|
  t.string :first, :last, :null => false
  t.string :address, :address2, :locality, :region, :country, :postal_code
  t.string :website, :email, :company
  t.timestamps
end

create_table :profiles do |t|
  t.integer :user_id
  t.integer :contact_id
  t.timestamps
end

Now, if that's all correct (which i believe it is) then i dont understand why this happens:

u = User.new => #u.contact = Contact.new => #u.contact => nil u.profile = Profile.new => #u.profile.contact = Contact.new => #u.profile.contact => #u.contact => nil

BUT, if i do:

u = User.new => #u.save => true c = Contact.new => #c.save => true u.contact = c => #u.contact => #

Why do I need to save the records before assigning them? What's going on here?

Reported by jreiss · January 13th, 2009 @ 04:35 PM

State: resolved
Milestone: none
Assigned to: nobody
Importance: none

Activity

  1. Altair
    Altair

    I've been having the same problem with my models. Is this correct behavior?

    It's quite annoying to have to set associations after the original model has been saved. I understand why this might be necessary, but I also believe that the :through association should be taking care of updating the record after the original model is saved.

    Another time that this becomes a problem is in HABTM relationships that specify a :join_model.

    March 13th, 2009 @ 08:46 PM

  2. Altair
    Altair

    Correction, HABTM still works in a test case I wrote up. It must be an application peculiarity of my own.

    Still, has_one :through remains a problem.

    March 13th, 2009 @ 10:54 PM

  3. Jacob Atzen
    Jacob Atzen

    Basically this is just the way Rails works. What you want to do is probably something like:

    
    u = User.new
    u.build_profile
    u.profile.build_contact
    u.save
    

    March 15th, 2009 @ 01:40 PM

  4. Altair
    Altair

    I misread the original ticket, but there is, however, still a problem with has_one :through. Note that this issue is non-existent in a has_many :through.

    Suppose that you have the same schema as outlined above. Executing the following results in incorrect behavior:

    
    >> Profile.all
    => []
    >> u = User.create
    => #<User id: 1, created_at: "2009-03-15 18:08:52", updated_at: "2009-03-15 18:08:52">
    >> c = Contact.new
    => #<Contact id: nil, created_at: nil, updated_at: nil>
    >> c.user = u
    => #<User id: 1, created_at: "2009-03-15 18:08:52", updated_at: "2009-03-15 18:08:52">
    >> Profile.all
    => [#<Profile id: 1, user_id: 1, contact_id: nil, created_at: "2009-03-15 18:09:21", updated_at: "2009-03-15 18:09:21">]
    >> c.save
    => true
    >> c.user
    => nil
    >> Profile.all
    => [#<Profile id: 1, user_id: 1, contact_id: nil, created_at: "2009-03-15 18:09:21", updated_at: "2009-03-15 18:09:21">]
    

    What's happening here is that when you assign a :user to a :contact that has not yet been saved, an entry in :profile is created before the :contact even has an id (hence the nil for contact_id). With a has_many :through association, this is not an issue; entries in the association we are traversing :through are created only on the save. Clearly, the association we are going :through should not be creating records before a save on the parent model.

    March 15th, 2009 @ 06:19 PM

  5. visnu
    visnu

    I ran into this same problem. has_one :through isn't honoring the contract of a normal has_one association. from the current documentation:

    Assigning an object to a has_one association automatically saves that object and the object being replaced (if there is one), in order to update their primary keys - except if the parent object is unsaved (new_record? == true).

    I've attached a test and a working patch.

    June 12th, 2009 @ 12:55 AM

  6. visnu
  7. visnu
    visnu
    • Tag changed from :through, has_one to :through, has_one, patch

    June 12th, 2009 @ 01:16 AM

  8. visnu
    visnu

    can anyone verify or give me feedback on my patch?

    June 15th, 2009 @ 11:55 PM

  9. visnu
    visnu

    what else can I do to draw more attention to this issue?

    June 23rd, 2009 @ 05:48 AM

  10. Altair
    Altair

    I haven't had a chance to apply your patch; I've moved on to other things.

    Unfortunately I don't really think you can do much to draw more attention to the issue. The thing is that there are a couple ways you can work around the problem. What I ended up doing just as a short-term solution was using a has_many, and just enforcing that the "many" was only one.

    You could try directly emailing some of the core developers, but I imagine they get all sorts of email every day, and you'll probably get lost in the shuffle. My guess is that this might have been caught already anyway, and will probably be fixed at least by the time 3.0 is released.

    That all said, I'll see if I can scrounge up some time to take a look at testing your patch. After all, you did put the effort in. Which is appreciated, by the way.

    June 23rd, 2009 @ 06:03 AM

  11. Eloy Duran
    Eloy Duran
    • Assigned user set to Michael Koziarski
    • Milestone changed from 2.x to 2.3.4

    I think Michael is the person for this one.

    And indeed, tickets with fixes like these will definitely be taken care of. It's just hard to put a date on it. Thanks.

    June 23rd, 2009 @ 07:11 AM

  12. Wolfram Arnold
    Wolfram Arnold

    Has anyone tries setting :autosave => true? Would this fix it? I haven't tried this but from reading the issue this is what I'd try.

    June 29th, 2009 @ 10:54 PM

  13. visnu
    visnu
    • Tag changed from :through, has_one, patch to :through, bugmash, has_one, patch

    sneaking this into the bugmash spotlight

    August 8th, 2009 @ 09:26 PM

  14. Rizwan Reza
    Rizwan Reza

    verified

    +1 This patch works in 2-3-stable, all tests pass. Does not work in master.

    August 8th, 2009 @ 09:45 PM

  15. Greg Sterndale
    Greg Sterndale

    +1 verified

    has_one_through_for_new_records.diff patch applied and tests pass for 2-3-stable

    patch fails to apply to master

    August 8th, 2009 @ 11:16 PM

  16. Elad Meidar
    Elad Meidar

    +1 verified, +1 patch fixes on 2-3-stable with tests passing

    on master, same issue and patch fails.

    Loading development environment (Rails 3.0.pre)
    >> Profile.all
    => []
    >> u = User.create
    => #<User id: 1, created_at: "2009-08-09 23:08:47", updated_at: "2009-08-09 23:08:47">
    >> c = Contact.new
    => #<Contact id: nil, created_at: nil, updated_at: nil>
    >> c.user = u
    => #<User id: 1, created_at: "2009-08-09 23:08:47", updated_at: "2009-08-09 23:08:47">
    >> Profile.all
    => [#<Profile id: 1, user_id: 1, contact_id: nil, created_at: "2009-08-09 23:08:57", updated_at: "2009-08-09 23:08:57">]
    >> c.save
    => true
    >> c.user
    => nil
    >> Profile.all
    => [#<Profile id: 1, user_id: 1, contact_id: nil, created_at: "2009-08-09 23:08:57", updated_at: "2009-08-09 23:08:57">]
    

    August 10th, 2009 @ 12:21 AM

  17. Pratik
    Pratik

    We need a patch for the master branch too.

    August 10th, 2009 @ 12:25 AM

  18. Tristan Dunn
    Tristan Dunn

    I verified the failure on master and I've attached a patch for master.

    August 10th, 2009 @ 12:41 AM

  19. Elad Meidar
    Elad Meidar

    +1 Verified patch on master, applies cleanly and tests run

    Loading development environment (Rails 3.0.pre)
    >> Profile.all
    => []
    >> u = User.create
    => #<User id: 1, created_at: "2009-08-09 23:41:33", updated_at: "2009-08-09 23:41:33">
    >> c = Contact.new
    => #<Contact id: nil, created_at: nil, updated_at: nil>
    >> c.user = u
    => #<User id: 1, created_at: "2009-08-09 23:41:33", updated_at: "2009-08-09 23:41:33">
    >> Profile.all
    => []
    >> c.save
    => true
    >> c.user
    => #<User id: 1, created_at: "2009-08-09 23:41:33", updated_at: "2009-08-09 23:41:48">
    >> Profile.all
    => [#<Profile id: 1, user_id: 1, contact_id: 1, created_at: "2009-08-09 23:41:48", updated_at: "2009-08-09 23:41:48">]
    

    August 10th, 2009 @ 12:45 AM

  20. Repository
    Repository
    • State changed from new to resolved

    (from [ecc9b705d7d12f853c794ca564b8e72995563eae]) Allow ho:through#build when the owner is a new record [#1749 state:resolved]

    Signed-off-by: Pratik Naik pratiknaik@gmail.com
    http://github.com/rails/rails/commit/ecc9b705d7d12f853c794ca564b8e7...

    August 10th, 2009 @ 12:53 AM

  21. Repository
  22. CancelProfileIsBroken
    CancelProfileIsBroken
    • Assigned user cleared.
    • Tag changed from :through, bugmash, has_one, patch to :through, has_one, patch
    • Milestone cleared.

    August 10th, 2009 @ 02:15 AM