我如何使用accepts_nested_attributes_for?

时间:2010-05-09 03:19:04

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

编辑我的问题以简明扼要并更新我已完成的工作:

如何为公司建立多个地址并为联系人分配单个地址,并在创建或编辑联系人时分配这些地址?

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

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

以下是我的联系人模型:

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中创建字段的方法,以便当有人创建新的联系人时,他们将地址传递给地址模型并将该地址与联系人关联:

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

以下是我的错误控制台的片段: 我正在POST属性,但它不是在Address表中创建....

  

处理ContactsController #create   (适用于2010-05-12 21:16:17的127.0.0.1)

     

[POST]参数:   { “提交”=&gt; “中提交”,   “authenticity_token”=&gt; “中D8 / gx0zy0Vgg6ghfcbAYL0YtGjYIUC2b1aG + dDKjuSs =”,   “接触”=&GT; { “COMPANY_NAME”=&gt; “中Allyforce”,   “title”=&gt;“”,“campaign_id”=&gt;“2”,   “address_attributes”=&GT; { “street1”=&gt; “中ABC”},   “fax”=&gt;“”,“phone”=&gt;“”,   “姓氏”=&gt; “中”,   “date_entered”=&gt; “中2010-05-12”,   “email”=&gt;“”,“first_name”=&gt;“abc”}}

     

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

     

地址创建(16.0ms)[0m
  [0; 1mINSERT INTO“地址”(“城市”,   “zip”,“created_at”,“street1”,   “updated_at”,“street2”,“州”)   VALUES(NULL,NULL,'2010-05-13   04:16:18',NULL,'2010-05-13   04:16:18',NULL,NULL)[0m

     

联系创建(0.0ms)[0m
  [0mINSERT INTO“contacts”(“公司”,   “created_at”,“title”,“updated_at”,   “campaign_id”,“address_id”,   “last_name”,“手机”,“传真”,   “company_id”,“date_entered”,   “first_name”,“email”)VALUES(NULL,   '2010-05-13 04:16:18','','2010-05-13   04:16:18',2,2,'','','',5,   '2010-05-12','abc','')[0m

4 个答案:

答案 0 :(得分:3)

只是一个平庸的问题,如果你在联系表格中使用这个问题不应该以单数形式提出?

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

在你的行动中你还要做

@ycontact.build_address

如果查看源代码,输入字段应如下所示。

  

&LT;输入类型=“文本”大小=“30”   名称= “接触[address_attributes] [street1]”   ID = “contact_address_attributes_street1” &GT;

如果您将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 %>

字段部分包含所有你的fieds togheter with:

 <% 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。我建议用javascript代码创建一个链接,添加address_attributes并删除(切换样式)。

答案 1 :(得分:1)

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

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

当然,您可以将地址列移动到Contact表中,并完全删除Address模型。

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

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

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

你问题的最后一部分实际上取决于数据分布 - 如果你只有几个地址,你可以简单地将它们粘贴在一个主要地址为默认值的select元素中。使用更多地址,您可能需要一个带有自动完成功能的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

然后在视图中使用Railscast中描述的form_forfields_for来获取嵌套的表单元素。如果您的表单不常见,这可能有点棘手。 ryanb here提供了很好的例子,涵盖了几种典型的情况。

答案 2 :(得分:1)

由于您希望地址属于公司和联系人,因此您有两种选择。如果您不需要多个地址(我建议不要假设),那么您可以在公司和联系人中添加“belongs_to:地址”。地址将同时具有“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)将它们聚集在一起。

祝你好运!

答案 3 :(得分:0)

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

哦,你必须将它们包括在内:has_many :Contacts必须是has_many :contacts

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

belongs_to :addressable, :polymorphic => true