This project is archived and is in readonly mode.

#1749 ✓resolved
jreiss

has_one :through not working?

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

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?

Comments and changes to this ticket

  • Altair

    Altair March 13th, 2009 @ 08:46 PM

    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.

  • Altair

    Altair March 13th, 2009 @ 10:54 PM

    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.

  • Jacob Atzen

    Jacob Atzen March 15th, 2009 @ 01:40 PM

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

    Altair March 15th, 2009 @ 06:19 PM

    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.

  • visnu

    visnu June 12th, 2009 @ 12:55 AM

    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.

  • visnu
  • visnu

    visnu June 12th, 2009 @ 01:16 AM

    • Tag changed from :through, has_one to :through, has_one, patch
  • visnu

    visnu June 15th, 2009 @ 11:55 PM

    can anyone verify or give me feedback on my patch?

  • visnu

    visnu June 23rd, 2009 @ 05:48 AM

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

  • Altair

    Altair June 23rd, 2009 @ 06:03 AM

    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.

  • Eloy Duran

    Eloy Duran June 23rd, 2009 @ 07:11 AM

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

  • Wolfram Arnold

    Wolfram Arnold June 29th, 2009 @ 10:54 PM

    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.

  • visnu

    visnu August 8th, 2009 @ 09:26 PM

    • Tag changed from :through, has_one, patch to :through, bugmash, has_one, patch

    sneaking this into the bugmash spotlight

  • Rizwan Reza

    Rizwan Reza August 8th, 2009 @ 09:45 PM

    verified

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

  • Greg Sterndale

    Greg Sterndale August 8th, 2009 @ 11:16 PM

    +1 verified

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

    patch fails to apply to master

  • Elad Meidar

    Elad Meidar August 10th, 2009 @ 12:21 AM

    +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">]
    
  • Pratik

    Pratik August 10th, 2009 @ 12:25 AM

    We need a patch for the master branch too.

  • Tristan Dunn

    Tristan Dunn August 10th, 2009 @ 12:41 AM

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

  • Elad Meidar

    Elad Meidar August 10th, 2009 @ 12:45 AM

    +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">]
    
  • Repository

    Repository August 10th, 2009 @ 12:53 AM

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

  • Repository
  • CancelProfileIsBroken

    CancelProfileIsBroken August 10th, 2009 @ 02:15 AM

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

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>

Referenced by

Pages