【问题标题】:How do I use accepts_nested_attributes_for?如何使用 Accepts_nested_attributes_for?
【发布时间】:2011-02-17 07:08:22
【问题描述】:

为了简洁而编辑我的问题并更新我所做的事情:

如何为一个公司建模拥有多个地址并将一个地址分配给一个联系人,并能够在创建或编辑联系人时分配它们?

我想使用嵌套属性在创建新联系人时添加地址。该地址作为其自己的模型存在,因为我可能希望选择从现有地址下拉而不是从头开始输入。

我似乎无法让它工作。我为 nil:NilClass 错误得到一个未定义的方法 `build'

这是我的联系人模型:

class Contact < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :title, :phone, :fax, :email, :company, 
                  :date_entered, :campaign_id, :company_name, :address_id, :address_attributes

  belongs_to :company
  belongs_to :address
  accepts_nested_attributes_for :address
end

这是我的地址模型:

class Address < ActiveRecord::Base
  attr_accessible :street1, :street2, :city, :state, :zip

  has_many :contacts
end

我想在创建新联系人时访问属于公司的其他联系人的所有地址。以下是我代表公司的方式:

class Company < ActiveRecord::Base
  attr_accessible :name, :phone, :addresses

  has_many :contacts

  has_many :addresses, :through => :contacts

end

以下是我尝试在 View for _form for Contact 中创建字段的方式,这样当有人创建新联系人时,他们会将地址传递给 Address 模型并将该地址与联系人相关联:

  <% f.fields_for :address, @contact.address do |builder| %>
  <p>
    <%= builder.label :street1, "Street 1" %> </br> 
    <%= builder.text_field :street1 %>
  <p>
  <% end %>

当我尝试编辑时,街道 1 的字段为空白。而且我不知道如何显示来自 show.html.erb 的值。

底部是我的错误控制台——似乎无法在地址表中创建值:

我的联系人控制器如下:

  def new
    @contact = Contact.new
    @contact.address.build # Iundefined method `build' for nil:NilClass

    @contact.date_entered = Date.today
    @campaigns = Campaign.find(:all, :order => "name")
    if params[:campaign_id].blank? 

    else
      @campaign = Campaign.find(params[:campaign_id])
      @contact.campaign_id = @campaign.id
    end

    if params[:company_id].blank?

    else
      @company = Company.find(params[:company_id])
      @contact.company_name = @company.name
    end

  end

  def create
    @contact = Contact.new(params[:contact])
    if @contact.save
      flash[:notice] = "Successfully created contact."
      redirect_to @contact
    else
      render :action => 'new'
    end
  end

  def edit
    @contact = Contact.find(params[:id])
    @campaigns = Campaign.find(:all, :order => "name")
  end

这是我的错误控制台的 sn-p: 我正在发布属性,但它不是在地址表中创建....

处理联系人控制器#create (127.0.0.1 2010-05-12 21:16:17)

[POST] 参数: {"提交"=>"提交", "authenticity_token"=>"d8/gx0zy0Vgg6ghfcbAYL0YtGjYIUC2b1aG+dDKjuSs=", "联系人"=>{"公司名称"=>"Allyforce", "title"=>"", "campaign_id"=>"2", "address_attributes"=>{"street1"=>"abc"}, "传真"=>"", "电话"=>"", "姓氏"=>"", "date_entered"=>"2010-05-12", "email"=>"", "first_name"=>"abc"}}

公司负载 (0.0ms)[0m [0mSELECT *来自“公司”WHERE(“公司”。“名称”='Allyforce') 限制 1[0m

地址创建 (16.0ms)[0m
[0;1mINSERT INTO "addresses" ("city", “zip”、“created_at”、“street1”、 “updated_at”、“street2”、“状态”) 值(空,空,'2010-05-13 04:16:18',空,'2010-05-13 04:16:18',空,空)[0m

联系创建 (0.0ms)[0m
[0mINSERT INTO“联系人”(“公司”, “created_at”、“title”、“updated_at”、 “campaign_id”、“address_id”、 “姓氏”、“电话”、“传真”、 "company_id", "date_entered", "first_name", "email") 值(NULL, '2010-05-13 04:16:18', '', '2010-05-13 04:16:18', 2, 2, '', '', '', 5, '2010-05-12', 'abc', '')[0m

【问题讨论】:

  • 嗯,我认为在地址表中创建插入值,这里是 Address Create (16.0ms)[0m [0;1mINSERT INTO "addresses" ("city", "zip", "created_at" ,“street1”,“updated_at”,“street2”,“state”)值(NULL,NULL,'2010-05-13 04:16:18',NULL,'2010-05-13 04:16:18' , NULL, NULL)[0m
  • 更新了我的帖子,将代码包含在视图中。
  • 您可以看到在 Address Create 下 street1 的值是空白的...不是 "abc" ....
  • 更新了我的帖子,回复了您的问题。

标签: ruby-on-rails aggregation has-many-through


【解决方案1】:

只是一个老生常谈的问题,如果您在联系表格中使用这个不应该是单数地址?

 <% f.fields_for :address, @contact.address do |builder| %>
   <p>
     <%= builder.label :street1, "Street 1" %> </br> 
     <%= builder.text_field :street1 %>
   <p>
 <% end %>

在你的行动中你也必须去做

@ycontact.build_address

如果您查看源代码,您的输入字段应该是这样的。

我认为您的关联也有问题,如果您将 address_id 存储在联系人中,那么

class Contact < ActiveRecord::Base
  belongs_to :address
  accepts_nested_attributes_for :address
end

class Address < ActiveRecord::Base
  attr_accessible :street1
  has_many :contacts
end

我认为这是你的情况,因为你想为多个联系人分配一个地址。

如果你将你的contact_id存储在你的地址模型中,你也可以反过来做:

class Contact < ActiveRecord::Base
  has_one :address
  accepts_nested_attributes_for :address
end

class Address < ActiveRecord::Base
  attr_accessible :street1
  belongs_to :contact
end

在这种情况下,您不能让用户使用相同的地址。

更新

在你的用户 show.html.eb 中你可以使用

<%= @contact.address.street1 %>

在您公司的 show.html.erb 中,您可以显示如下地址:

<% @company.addresses.each do |address| %>
  <%= address.street1 %>
  ...
<% end %>

在您的用户编辑页面上,表单字段应与 new.html.erb 相同,只是表单标签应如下所示:

<% form_for @contact do |form| %>
  <%=render :partial=>'fields' %>
<% end %>

部分字段包含您的所有字段以及:

 <% f.fields_for :address, @contact.address do |builder| %>
   <p>
     <%= builder.label :street1, "Street 1" %> </br> 
     <%= builder.text_field :street1 %>
   <p>
 <% end %>

这应该可行。

更新 2

您可以通过以下方式访问属于公司的所有地址:

@company.addresses

如果你有

class Company<Activerecord::Base
    has_many :contacts
    has_many :addresses, :through=>:contacts
end

关于草稿或默认地址。您可以根据需要有不同的选择。我想要联系人可以从数据库中选择现有地址之一的选项。您可以在联系表单中选择一个框,例如:

 <%= form.select :address_id, options_from_collection_for_select(@company.addresses, 'id', 'street1')%>

如果用户选择现有地址之一,您也可以使用一些花哨的 javascript 添加或删除地址表单。在这种情况下,您必须从表单中删除 address_attributes。我建议使用添加 address_attributes 和删除(切换样式)的 javascript 代码创建一个链接。

【讨论】:

  • 好吧,我希望拥有相同地址的用户。但是可能有多个地址......想象一个拥有不同员工的大型分布式公司。那么这行得通吗....这是使用 :through => :contacts,对吗?
  • 还...当 Contact belongs_to :address 时,accepts_nested_attributes_for :address 是否有效?似乎我得到一个错误....我认为它只适用于 has_many 或 has_one....我更新了我的问题以简化问题
  • accepts_nested_attributes_for 也适用于 belongs_to,我现在正在使用它。在您想通过公司模型创建地址之前,has_many :through 并不重要。也没有什么意义。
  • 啊,好的,那么我如何访问属于公司的联系人拥有的所有地址?我是否需要遍历公司中的所有联系人并获取contact.address(?)
  • 嗯,我需要重新检查一遍,但我确实尝试过@contact.address.street1,但它没有将 street1 识别为属性......让我检查一下再次删除 :through => 联系人。
【解决方案2】:

如果每个地址只属于一个联系人,(如家庭地址),您可以这样做:

class Company < ActiveRecord::Base
  has_many :contacts
end

class Contact < ActiveRecord::Base
  belongs_to :company
  has_one :address
end

class Address < ActiveRecord::Base
  belongs_to :contact
end

当然,您可以将地址列移到联系人表中,完全摆脱地址模型。

如果每个地址有多个联系人(即,地址是联系人工作的公司设施):

class Company < ActiveRecord::Base
  has_many :contacts
end

class Contact < ActiveRecord::Base
  belongs_to :company
  belongs_to :address
end

class Address < ActiveRecord::Base
  has_many :contacts
end

您可以添加has_many :through 关联以获得company.addresses

class Company < ActiveRecord::Base
  has_many :contacts
  has_many :addresses, :through => :contacts
end

在这两种情况下,地址组成部分(街道、城市、州等)都是地址表中的列。

您问题的最后一部分实际上取决于数据分布——如果您只有几个地址,您可以简单地将它们粘贴在一个选择元素中,并将主地址作为默认值。对于更多地址,您可能需要一个带有自动完成功能的 AJAXified 表单来搜索地址表。

要将新的联系人/地址添加到您的表格中,您可能需要查看嵌套表格。 Railscast 196 是一个很好的介绍。

编辑——关于嵌套属性的更多信息

accepts_nested_attributes_for 应该与 has_manyhas_one 关联一起使用,并且应该与多层嵌套一起使用。似乎您想让一个联系人属于一个公司,一个地址属于一个联系人,在这种情况下:

class Company < ActiveRecord::Base
  has_many :contacts
  accepts_nested_attributes_for :contact
end

class Contact < ActiveRecord::Base
  belongs_to :company
  has_one :address # or has_many, if you prefer
end

class Address < ActiveRecord::Base
  belongs_to :contact
end

然后在视图中使用 form_forfields_for 如 Railscast 中所述来获取嵌套的表单元素。如果您的表格不寻常,这可能会有点棘手。来自 ryanb here 的优秀示例涵盖了几种典型情况。

【讨论】:

  • 您好!如 Rails 食谱中所述,这与使用多态有何不同?每个地址实际上确实有多个潜在联系人;所以每家公司都有多个地址....谢谢。
  • 嗨,我似乎不能在 Contacts 模型中使用 accept+_nested_attribute_for: 作为accepts_nested_attributes_for : 地址,因为它是一个belongs_to 关联。我查看了 API 并正在尝试 has_one ...但这听起来对吗?
  • 当您想与其他模型(例如公司和个人)共享模型(例如地址)时,多态关联很有用。一个公司可以有一个地址,一个人可以有一个地址,所以如果地址格式相同,重用模型是有意义的。如果没有多态性,您将需要单独的 CompanyAddress 和 PersonAddress 模型。我将编辑答案以回复您的其他评论。
  • 嗨,我 *seeM8 让它的部分工作正常,但构建器出现错误 - 非常感谢您的帮助,这绝对让我进入了正确的方向。让我更新一下我在问题中所做的事情,这样你就可以看到我们有多接近......感谢 zetitic!
  • 所以我一直认为公司拥有地址的方式(在现实生活和模型中)是因为属于公司的联系人拥有一个地址,因此有效地为公司提供地址是 :through => :contacts 你的回答能帮我建模吗?
【解决方案3】:

由于您希望地址同时属于公司和联系人,因此您有两种选择。如果您不需要多个地址(我可能建议您不要假设),那么您可以在公司和联系人中添加一个“belongs_to :address”。然后地址将同时具有“has_many :companies”和“has_many :contacts”。

更有意义的更好选择是使用多态关联,其中地址表将具有 :addressable_type 和 :addressable_id 列以链接回任一模型。

class Company < ActiveRecord::Base
  attr_accessible :name, :phone, :addresses

  has_many :contacts  
  accepts_nested_attributes_for :contacts

  has_many :addresses, :as => :addressable
  accepts_nested_attributes_for :addresses

end

class Contact < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :title, :phone, :fax, :email, :company, 
                  :date_entered, :campaign_id, :company_name

  belongs_to :company
  has_many :addresses, :as => :addressable
  accepts_nested_attributes_for :addresses
end

class Address < ActiveRecord::Base
  attr_accessible :street1

  belongs_to :addressable, :polymorphic => true
end

现在,您的麻烦在于希望有一个单一的关联“company.addresses”来获取它的地址和联系人的地址。我可能会建议远离这种情况并使用不同的方法。

您需要将视图中的这些地址映射到正确的记录,因此最好将它们分开:

<% form_for @company do |company_form| %>
  <% company_form.fields_for :addresses do |address_form| %>
    <%= address_form.text_field :street1 %>
  <% end %>
  <% company_form.fields_for :contacts do |contact_form| %>
    <% contact_form.fields_for :addresses do |contact_address_form| %>
      <%= contact_address_form.text_field :street1 %>
    <% end %>
  <% end %>
<% end %>

你的地址表有这样的多态列:

class CreateAddresses < ActiveRecord::Migration
  def self.up
    create_table :addresses do |t|
      t.string :street1
      t.string :addressable_type
      t.integer :addressable_id
      t.timestamps
    end
  end

  def self.down
    drop_table :addresses
  end
end

如果您不像这样单独嵌套记录,我不确定如何保持所有链接直接。

如果您确实需要获取公司的所有地址以用于显示目的,您可以使用标准的 ruby​​ 可枚举操作(如 collect)将它们收集在一起。

祝你好运!

【讨论】:

  • 嗨,我仍在尝试决定是否使用多态...从概念上讲,公司有地址:通过联系人...换句话说,我有一个与相关联的地址的唯一原因一家公司是因为一个联系人属于它。你怎么看?
  • 是的,我确实需要查看所有 company.addresses ...它允许用户在添加新联系人时选择一个潜在的地址 .... 那么这是否意味着不使用多态性?
  • 多态是有意义的,因为如果随着应用程序的增长,您可能希望将地址与其他对象相关联。如果你没有预见到这一点,那就没有必要了。如果您不使用多态性,则可以使用 has_many :through,但仅用于显示目的。您仍然需要使用嵌套表单来访问特定联系人以添加/修改公司联系人的地址。附言这对投票有足够的帮助吗?
  • 无论如何,您始终可以遍历公司的联系人以获取地址,然后链接到该地址 ID 以获取新联系人。如果你想将同一个地址链接到多个记录(即另一个联系人),你需要一个连接表而不是使用 belongs_to。希望这是有道理的。
  • 是投了赞成票,但仍在尝试实施......在这一点上,我不认为我会将地址与其他对象相关联......但也许它无论如何都会更容易......但是,真正的挑战是,当我创建一个新联系人时,创建地址并将外键分配给该地址......
【解决方案4】:

我建议让联系人拥有多个地址。它无论如何都会在野外发生。如果只决定一个,那就是has_one :address

哦,你必须将它们小写:has_many :Contacts 必须是 has_many :contacts

在您引用的地址模型中(数据库需要addressable_idaddressable_type 字段)

belongs_to :addressable, :polymorphic => true

【讨论】:

  • 添加到新联系人的地址如何可供该公司未来的新联系人使用?
猜你喜欢
  • 2015-12-11
  • 1970-01-01
  • 1970-01-01
  • 2012-07-13
  • 1970-01-01
  • 1970-01-01
  • 2013-02-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多