Nested attribute update_attributes uses insert rather than update
Asked Answered
N

4

29

I have a user and nested profile class as follows:

class User < ActiveRecord::Base
  has_one :profile
  attr_accessible :profile_attributes
  accepts_nested_attributes_for :profile
end

class Profile < ActiveRecord::Base
  belongs_to :user
  attr_accessible :name
end

user = User.find(1)
user.profile.id  # => 1
user.update_attributes(profile_attributes: {name: 'some name'})
user.profile.id  # => 2

I don't understand why rails is throwing away the old profile and creating a new one.

Using

user.profile.update_attributes({name: 'some name'})

just updates the current profile as expected. But in that case I'm not taking advantage of accepts_nested_attributes_for

Does anyone know why the update happens this way? I'd prefer not to end up with a database of profile rows not connected to any user.

Nutty answered 30/3, 2012 at 13:36 Comment(1)
may be you can try, user.update_attributes(profile_attributes: {:id =>user.profile.id, :name: 'some name'})Silsbye
N
21

I solved this problem by adding the update_only option:

accepts_nested_attributes_for :profile, update_only: true

Now a new profile is only created if one does not already exist.

Nutty answered 4/4, 2012 at 14:16 Comment(2)
Because the default (update_only:false) does work if you do include the id of the nested object, and it makes sense because it is the precise way to fetch the nested object from the database. More info read the apidock.com/rails/ActiveRecord/NestedAttributes/ClassMethods/…Miyamoto
Thanks a log for your solution.Euphroe
F
38

For everyone who has the same problem in Rails 4: fields_for already adds the id for your nested forms but you have to permit the :id parameter. I only permitted an :object_name_id parameter and since this does not throw any errors it took me some time until i saw this in the server logs. Hopefully this helps someone wasting less time than me on this :)

Fortdefrance answered 2/11, 2013 at 13:48 Comment(3)
yes! thanks you, this was so hard to debug, stupid strong params! ;-)Foreword
I find it helpful to change config.action_controller.action_on_unpermitted_parameters to :raise instead of the default, :log. Makes it much easier to debug when it blows up in your face! github.com/rails/strong_parameters#handling-of-unpermitted-keysUndecagon
Thanks. This works. params.require(:some).permit(:object, nested_attributes: [:id, :object] )Klink
H
24

If you check your form, you need to set the id attribute within the nested attribute hash for your Profile object. If the id is not set, ActiveRecord assumes it's a new object.

For example, if you had an ERB form building a set of 'user' parameters with a nested 'profile_attributes' parameter hash for the nested profile within the user, you could include a hidden value for the profile id, like this:

<%= hidden_field "user[profile_attributes][id]", @profile.id %>
Hollie answered 30/3, 2012 at 18:26 Comment(4)
How do you set the id attribute within the nested attributes for the Profile object?Ravenravening
Added some example code showing how to inject a hidden form value.Hollie
isn't this not very good idea? as users could just mess with the id value. refer to using update_only: true option as @jason saidTranquillize
If fields_for is used, Rails adds hidden id for you already. (at least in Rails 3.2). Also ensure id is included in the strong parameter filtering.Miyamoto
N
21

I solved this problem by adding the update_only option:

accepts_nested_attributes_for :profile, update_only: true

Now a new profile is only created if one does not already exist.

Nutty answered 4/4, 2012 at 14:16 Comment(2)
Because the default (update_only:false) does work if you do include the id of the nested object, and it makes sense because it is the precise way to fetch the nested object from the database. More info read the apidock.com/rails/ActiveRecord/NestedAttributes/ClassMethods/…Miyamoto
Thanks a log for your solution.Euphroe
A
0

I was hit with this in another version of Rails and I thought I will lose my mind. While adding update_only => true solved it I think it is a bug somewhere in Rails.

Symptoms in my case: I would get the association to the belongs_to deleted and new nested object created - until I first refreshed the page. After that it worked correctly.

In my case I added a before_save method to my nested class and printed what it saved. I also printed the attributes before calling update_attributes. They had the "parent_id" set correctly. I also included the hidden id field in the form, no change - which was normal as it was already included by using fields_for...

Surprise: I saw one update call generating two save calls. First save would have the nested object id but null for the belongs_to id. - so this would update the record to set "parent_id" to null. The second save would have the "parent_id" set but it would have the nested object id set to null.

As I said I fixed it by adding update_only => true, but I think it is still a bug.

I would like to find out if the above symptoms apply to your case too to confirm this is a bug.

Anabas answered 1/3, 2016 at 16:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.