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.

Updating nested attributes with has_one relationship doesn't work correctly

#2777

Updating the attributes of the child model results in the creation of a new Object with nil values when using a has_one relationship and nested attributes.

>> u = User.find 1
=> #<User id: 1, login: "james", password: "secret", created_at: "2009-06-06 22:50:07", updated_at: "2009-06-06 22:50:07">
>> u.profile
=> #<Profile id: 2, user_id: 1, age: 18, city: "London", comment: "", created_at: "2009-06-06 22:51:45", updated_at: "2009-06-06 22:51:45">
>> u.attributes = {:profile_attributes => {:city => 'nowhere'}}
=> {:profile_attributes=>{:city=>"nowhere"}}
>> u.profile
=> #<Profile id: nil, user_id: 1, age: nil, city: "nowhere", comment: nil, created_at: nil, updated_at: nil>
>> u.profile.changes
=> {"city"=>[nil, "nowhere"], "user_id"=>[nil, 1]}

I've described the full scenario at
http://railsforum.com/viewtopic.php?id=31491

Reported by flip · June 7th, 2009 @ 08:59 PM

State: resolved
Milestone: 2.x
Assigned to: Eloy Duran Eloy Duran
Importance: Low

Activity

  1. Eloy Duran
    Eloy Duran
    • State changed from new to invalid

    You should include the id of the Profile to indicate it's an update you're trying to perform.

    u.attributes = {:id => 2, :profile_attributes => {:city => 'nowhere'}}
    

    For more info take a look at what the form helpers produce.

    June 7th, 2009 @ 09:23 PM

  2. flip
    flip

    ok, i guess you mean:

    u.attributes = {:profile_attributes => {:id => 2, :city => 'nowhere'}}
    

    is this documented somewhere? Using the "old way" my approach would work also without the id:

    u.profile.attributes = {:city => 'nowhere'}
    

    June 7th, 2009 @ 09:34 PM

  3. Eloy Duran
    Eloy Duran

    Oops, yeah that's what I meant :)

    It's shown here: http://github.com/rails/rails/blob/master/activerecord/lib/active_r...

    It is needed to differentiate between a new record or an existing one.

    June 7th, 2009 @ 09:46 PM

  4. Greg
    Greg

    I'm not so sure that this should be invalid. It makes sense to need the id for has_many relationships, but for belongs_to and has_one, you should assume that they want to update the record.

    In the example that flip gave, calling u.save will return no problem, and you'll end up with an orphaned profile in the database. This scenario can easily come up if you are allowing updates via REST/XML...a user can PUT


    nowhere

    to http://&lt;>/users/2 or wherever. The user doesn't know the ID of the nested model, but they should expect that only the city is updated in the profile, a new profile should not be created.

    Unfortunately, I don't have a patch for this right now, but I believe this should be re-opened as a bug. Thanks!

    June 26th, 2009 @ 12:00 AM

  5. Sébastien Luquet
    Sébastien Luquet

    Hi,

    Documentation is about update_attributes

    params = { :member' => { :avatar_attributes => { :id => '2', :icon => 'sad' } } }
    member.update_attributes params['member']

    What about object creation

    params = { :member' => { :avatar_attributes => { :id => '2', :icon => 'sad' } } }
    Member.create params['member']

    Does it create new Member, update Avatar with id 2 and associate the two record together ?

    October 5th, 2009 @ 02:00 PM

  6. Dmitry Polushkin
    Dmitry Polushkin

    I agree that it should be reopened. Not smart to pass id for one to one.

    October 21st, 2009 @ 01:19 PM

  7. Eloy Duran
    Eloy Duran

    I'd rather see a patch that adds something like :only_update => true' to accepts_nested_attributes_for. Or otherwise fix it in your controller if you don't trust the client.

    October 21st, 2009 @ 01:39 PM

  8. Dmitry Polushkin
    Dmitry Polushkin

    Question, how to solve:

    class Team
      has_one :captain, :class_name => 'Player'
      accepts_nested_attributes_for :captain
    end
    
    class Player
      belongs_to :team
    
      validate :validate_team
    
      private
    
      def validate_team
        raise 'Should have team' unless team
      end
    end
    
    Team.create!(:captain_attributes => {:name => 'Name Surname'})
    

    Will be risen 'Should have team' because while attributes assigns to the nested model object, belongs_to associations isn't assigns.

    If you know how to solve that, please tell me.

    Thank you.

    November 1st, 2009 @ 02:33 PM

  9. Eloy Duran
    Eloy Duran
    • State changed from invalid to resolved

    Today a patch for :update_only and the ability to use :inverse_of, which fixes the last issue, have both been pushed. HTH

    December 28th, 2009 @ 11:17 PM

  10. Ryan Bigg
    Ryan Bigg
    • Tag cleared.
    • Importance changed from to Low

    Automatic cleanup of spam.

    October 9th, 2010 @ 10:00 PM

  11. Jeff Kreeftmeijer
  12. bingbing