Ruby on Rails: Nested Attributes, belongs_to relation
I have a User entity that has a Current Location field (city and country). To hold this info I created an entity called Location which has_many Users.
I'm not entirely sure if I should put in the User model "has_one" or "belongs_to", but for what I read if I wanted it to have the foreign key of the location I should put "belongs_to". I also want to be able to edit the user's Current Location when editing the User. so I am using nested attributes. But when I edit the user I end up adding a new Location each time without ever associating it to the user that was edited. Can you help me out?
My code is the following:
#User Model
class User < ActiveRecord::Base
## Relationships
belongs_to :current_location, :class_name => 'Location'
accepts_nested_attributes_for :current_location
end
#Location Model
class Location < ActiveRecord::Base
#Relationship
has_many :users
end
# part of the _form_edit.haml
- form_edit.fields_for :current_location do |location_form|
= location_form.开发者_StackOverflow社区label :location, "Current Location"
= location_form.text_field :location
#Application Helper
#nested attributes for user and location
def setup_user(user)
returning(user) do |u|
u.build_current_location if u.current_location.nil?
end
end
#in the user controller (added after edit)
def update
@user = @current_user
if @user.update_attributes(params[:user])
flash[:notice] = "Account updated!"
redirect_to account_url
else
render :action => :edit
end
end
The exact problem you're facing, as others have pointed out is that your controller is not receiving the location id as it should. Looks to me the location id is being passed through the wrong parameter. Unfortunately a location id doesn't exist on a new record, so this is not possible in the form.
Your problem stems from the use accepts_nested_attributes_for on a belongs_to relationship. The behaviour isn't clearly defined. This appears to be a documented bug. So the accepts_nested_attributes_for should be on a has one or has many side of a relationship.
Here are some possible solutions:
Move The accepted_nested_attributes_for to the Location model and build your forms the other way around.
-form_for @location do |location_form| ... =location_form.fields_for @user do |user_form| ....
Unfortunately this doesn't allow for a logical way of presenting information. And makes editing the right user difficult.
Use a join model, and make a has one :through relationship.
I'm honestly not sure how well accept_nested_attributes_for works with a :through relationship, but it will definitely solve your problem with linking records.
Ignore accepts_nested_attributes_for and handle the association in your controller the old fashioned way.
Actually keep the accepts_nested_attributes_for. It provides some handy convenience methods, just don't let it get to the update_attributes/create statement.
def update @user = @current_user completed = false location_params = params[:user].delete(:current_location_attributes) User.transaction do @location = Location.find_or_create_by_id(location_params) @user.update_attributes(params[:user]) @user.current_location = @location @user.save! completed = true end if completed flash[:notice] = "Account updated!" redirect_to account_url else render :action => :edit end end
Fields for will populate an id field in the current_location_attributes hash automatically, if it's not creating a new location. However, find_or_create_by_id, requires an :id entry in the hash for it to work. It will create with a correctly auto incremented id if the id isn't in the database. If you are creating a new location you will need to add it. Easiest to add it to the form with =location_form.hidden_field :id, 0 unless current\_location.new\_record?
.
However, you might want to cut down on duplicate location creation, and change the Location.find_or_create_by_id line to Location.find_or_create_by_location. This will also cut down on any errors from failed uniqueness validations.
You do not provide the nested attribute's id. So rails thinks it's a new one.
- form_edit.fields_for :current_location do |location_form|
= location_form.label :location, "Current Location"
= location_form.text_field :location
= location_form.hidden_field :id unless location_form.new_record?
Not sure if the previous answer is really correct. What you need is to specify the id of the user for the location, not the location itself.
- form_edit.fields_for :current_location do |location_form|
= location_form.label :location, "Current Location"
= location_form.text_field :location
= location_form.hidden_field :user_id
By default belongs_to :current_location, :class_name => 'Location'
will expect the Users
table have a current_location_id
field. Once you have this you should be able to do something like:
@user = @current_user
@user.update_attributes(params[:user])
@location = @user.current_location or @user.build_current_location
@location.update_attributes(params[:location])
@user.current_location.save!
@user.save!
精彩评论