【问题标题】:Using Rails models with accepts_nested_attributes_for使用带有 accept_nested_attributes_for 的 Rails 模型
【发布时间】:2009-09-17 22:03:04
【问题描述】:

我正在编写一个名为 Person 的简单 Rails 模型 has_many :phone_numbers 并且我正在尝试以复杂的形式保存电话号码,而无需手动编写 setter 方法。 accepts_nested_attributes_for 应该做我想做的,但我无法让它工作。这是我到目前为止的代码:

迁移

class CreatePeople < ActiveRecord::Migration
  def self.up
    create_table :people do |t|
      t.string :first_name
      t.string :last_name
      t.integer :address_id      
      t.string :email

      t.timestamps
    end
  end

  def self.down
    drop_table :people
  end
end

class CreatePhoneNumbers < ActiveRecord::Migration
  def self.up
    create_table :phone_numbers do |t|
      t.string :number, :limit => 10
      t.string :extension, :limit => 5
      t.string :description, :null => false
      t.integer :telephone_id
      t.string :telephone_type

      t.timestamps
    end
  end

  def self.down
    drop_table :phone_numbers
  end
end

模型

class Person < ActiveRecord::Base
  has_one :address, :as => :addressable, :dependent => :destroy
  has_many :phone_numbers,
    :as => :telephone,
    :dependent => :destroy

  accepts_nested_attributes_for :phone_numbers

  attr_protected :id

  validates_presence_of :first_name, :last_name, :email
end

class PhoneNumber < ActiveRecord::Base
  attr_protected :id

  belongs_to :telephone, :polymorphic => true
end

查看

<% form_for @person, :builder => CustomFormBuilder do |f| %>
  <%= f.error_messages %>

  <%= f.text_field :first_name %>
  <%= f.text_field :last_name %>

  <% fields_for "person[address]", @person.address, :builder => CustomFormBuilder do |ff| %>  
    <%= ff.text_field :address_1 %>
    <%= ff.text_field :address_2 %>
    <%= ff.text_field :city %>
    <%= ff.text_field :state %>
    <%= ff.text_field :zip %>  
  <% end %>

  <h2>Phone Numbers</h2>
  <% @person.phone_numbers.each do |phone_number| %>
    <% fields_for "person[phone_numbers][]", phone_number, :builder => CustomFormBuilder do |ff| %>
      <%= ff.text_field :description %>
      <%= ff.text_field :number %>
      <%= ff.text_field :extension %>
    <% end %>
  <% end %>

  <%= f.text_field :email %>

  <%= f.submit 'Create' %>

<% end %>

控制器

def new
  @person = Person.new 
  @person.build_address
  @person.phone_numbers.build

  respond_to { |format| format.html }
end

def create
  @person = Person.new(params[:person])

  respond_to do |format|
    if @person.save
      flash[:notice] = "#{@person.name} was successfully created."
      format.html { redirect_to(@person) }
    else
      format.html { render :action => 'new' }
    end
  end
end

我已验证正在创建 phone_numbers= 方法,但帖子仍然导致:

PhoneNumber(#69088460) expected, got HashWithIndifferentAccess(#32603050)

RAILS_ROOT: H:/projects/test_project

C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_proxy.rb:263:in `raise_on_type_mismatch'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_collection.rb:319:in `replace'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_collection.rb:319:in `each'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_collection.rb:319:in `replace'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations.rb:1290:in `phone_numbers='
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2740:in `send'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2740:in `attributes='
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2736:in `each'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2736:in `attributes='
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2434:in `initialize'
H:/projects/salesguide/app/controllers/accounts_controller.rb:46:in `new'
H:/projects/test_project/app/controllers/accounts_controller.rb:46:in `create'

我可以通过手动编写 phone_numbers= 方法来使其工作,但这会导致大量重复工作,我更愿意学习如何正确地做到这一点。谁能看到我做错了什么?

【问题讨论】:

    标签: ruby-on-rails ruby forms


    【解决方案1】:

    您忘记了调用 fields_for 作为人员表单上的方法。否则,您实际上并没有在 accept_nested_attributes_for 上下文中使用 fields_for 。 Michael 的解决方案试图诱使 Rails 将提交视为正确定义的 accept_nested_attributes_for 表单。

    您尝试做的正确语法是:

    parent_form_object.fields_for id, object_containing_values, {form_for options}, &block
    

    如果您提供一个符号作为 id,包含您的 Person 模型中定义的子模型的关联名称,您会发现代码看起来更简洁,更易于调试。

    此外,如果 @person.phone_numbers 为空,您使用的 each 块可能会导致问题。您可以确保至少有一组电话号码字段的行与我使用的行相似

    <% @phs = @person.phone_numbers.empty? ? @person.phone_numbers.build : @person.phone_numbers %> 
    

    经过所有更正,此代码将按照您的意愿执行。

    查看

    <% form_for @person, :builder => CustomFormBuilder do |f| %>
      <%= f.error_messages %>
    
      <%= f.text_field :first_name %>
      <%= f.text_field :last_name %>
    
      <% f.fields_for :address, @person.address, :builder => CustomFormBuilder do |address_form| %>  
        <%= address_form.text_field :address_1 %>
        <%= address_form.text_field :address_2 %>
        <%= address_form.text_field :city %>
        <%= address_form.text_field :state %>
        <%= address_form.text_field :zip %>  
      <% end %>
    
      <h2>Phone Numbers</h2>
        <% @phs = @person.phone_numbers.empty? ? @person.phone_numbers.build : @person.phone_numbers %>
        <% f.fields_for :phone_numbers, @phs, :builder => CustomFormBuilder do |phone_number_form| %>
          <%= phone_number_form.text_field :description %>
          <%= phone_number_form.text_field :number %>
          <%= phone_number_form.text_field :extension %>
        <% end %>
    
      <%= f.text_field :email %>
    
      <%= f.submit 'Create' %>
    
    <% end %>
    

    您可能会发现查看complex-form-examples repository on github 的工作示例很有用。它还附带代码,可以从视图/表单中为 :has_many 关系动态添加新条目。

    【讨论】:

      【解决方案2】:

      昨天我在玩accepts_nested_attributes_for 时试图弄清楚Rails form with three models and namespace。我需要稍微不同地设置表单,尝试使用:person[phone_numbers_attributes][]

      【讨论】:

      • 我无法相信我浏览过的所有文档都没有更清楚地指出这一点。谢谢。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-02-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-20
      相关资源
      最近更新 更多