Использование моделей Rails с acceptpts_nested_attributes_for - PullRequest
4 голосов
/ 18 сентября 2009

Я пишу простую модель Rails под названием Person, которая has_many :phone_numbers, и я пытаюсь сохранить телефонные номера в сложной форме без написания методов установки вручную. 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 =, но это вызовет огромное дублирование усилий, я бы скорее научился делать это правильно. Кто-нибудь может увидеть, что я делаю не так?

Ответы [ 2 ]

6 голосов
/ 08 октября 2009

Вы забываете вызывать fields_for как метод в форме человека. Иначе вы фактически не используете fields_for в контексте accept_nested_attributes_for. Решение Майкла пытается обмануть Rails, чтобы он рассматривал представление как правильно определенную форму acceptpts_nested_attributes_for.

Правильный синтаксис того, что вы пытаетесь сделать, таков:

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

Вы найдете, что код выглядит чище и проще для отладки, если предоставить символ в качестве идентификатора, содержащий имя ассоциации дочерней модели, как определено в вашей модели Person.

Кроме того, каждый используемый вами блок может вызывать проблемы, если @ person.phone_numbers пусто. Вы можете убедиться, что есть хотя бы один набор полей Phone Number с линией, аналогичной той, которую я использовал с

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

Со всеми исправлениями этот код будет делать то, что вы хотите.

View

<% 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 на github для рабочего примера. Он также поставляется с кодом для динамического добавления новых записей для отношений has_many из представления / формы.

4 голосов
/ 18 сентября 2009

Вчера я играл с accepts_nested_attributes_for, пытаясь выяснить форму Rails с тремя моделями и пространством имен . Мне нужно настроить форму немного по-другому, попробуйте использовать: person[phone_numbers_attributes][]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...