Ruby on Rails:嵌套属性、belongs_to 关系

发布于 2024-08-08 12:18:11 字数 1174 浏览 4 评论 0原文

我有一个具有当前位置字段(城市和国家/地区)的用户实体。为了保存这些信息,我创建了一个名为 Location 的实体,它有很多用户。

我不完全确定是否应该放入用户模型“has_one”或“belongs_to”,但根据我读到的内容,如果我希望它具有位置的外键,我应该放入“belongs_to”。我还希望能够在编辑用户时编辑用户的当前位置。所以我使用嵌套属性。但是,当我编辑用户时,我最终每次都会添加一个新位置,而不会将其与编辑的用户相关联。你能帮我一下吗?

我的代码如下:

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

剧终人散尽 2024-08-15 12:18:11

正如其他人指出的那样,您面临的确切问题是您的控制器没有收到应有的位置 ID。在我看来,位置 ID 是通过错误的参数传递的。不幸的是,新记录中不存在位置 ID,因此在表单中这是不可能的。

您的问题源于对belongs_to关系使用accepts_nested_attributes_for。该行为没有明确定义。这似乎是一个已记录的错误。因此,accepts_nested_attributes_for 应该位于关系的一个或多个方面。

以下是一些可能的解决方案:

  1. 将 Accepted_nested_attributes_for 移至位置模型,并以相反的方式构建表单。

    -form_for @location do |location_form|
     ...
     =location_form.fields_for @user do |user_form|
       ....
    

    不幸的是,这不允许以逻辑方式呈现信息。并且使编辑正确的用户变得困难。

  2. 使用连接模型,并建立一个具有一:通过关系。

    老实说,我不确定accept_nested_attributes_for与:through关系的配合效果如何,但它肯定会解决链接记录的问题。

  3. 忽略accepts_nested_attributes_for并以老式方式处理控制器中的关联。

    实际上保留accepts_nested_attributes_for。它提供了一些方便的方法,只是不要让它到达 update_attributes/create 语句。

    定义更新 
      @用户 = @当前用户 
      已完成=假
      location_params = params[:用户].delete(:当前位置属性)
    
      用户.交易做
        @location = Location.find_or_create_by_id(location_params)
        @user.update_attributes(参数[:用户]) 
        @用户.当前位置 = @位置
        @用户.保存!
        已完成=真
      结尾
      如果完成
        flash[:notice] = "帐户已更新!"重定向到 account_url 
      别的 
        渲染:动作=> :编辑 
      结尾
    结尾
    

如果不创建新位置,则 的字段将自动填充 current_location_attributes 哈希中的 id 字段。但是,find_or_create_by_id 需要哈希中的 :id 条目才能工作。如果 id 不在数据库中,它将使用正确的自动递增 id 创建。如果您要创建新位置,则需要添加它。最简单的方法是使用 =location_form.hidden_​​field :id, 0 除非 current\_location.new\_record? 将其添加到表单中。

但是,您可能希望减少重复的位置创建,并将 Location.find_or_create_by_id 行更改为 Location.find_or_create_by_location。这也将减少因唯一性验证失败而产生的任何错误。

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:

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

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

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

蓝海似她心 2024-08-15 12:18:11

您不提供嵌套属性的 ID。所以 Rails 认为这是一个新的。

- 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?

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?
海的爱人是光 2024-08-15 12:18:11

不确定之前的答案是否真的正确。您需要指定该位置的用户 ID,而不是位置本身。

- 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

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
素衣风尘叹 2024-08-15 12:18:11

默认情况下 belongs_to :current_location, :class_name => “Location” 期望 Users 表具有 current_location_id 字段。一旦你有了这个,你应该能够做类似的事情:

@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!

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!
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文