deep nested accepts_nested_attributes_for not rendering attribute data in edit template

爷,独闯天下 提交于 2019-12-05 18:26:05

Ok, to create a double (or more than double) nested form. Here are the steps I've followed to get things working.

  1. Define the proper associations (remember to add class_name: "NameOfClass" and/or inverse_of: :model_name, if necessary, to eliminate Rails from having to guess)

Ex:

teacher.rb

class Teacher < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :address_attributes,  :primary_phone_attributes, :secondary_phone_attributes,
                  :teacher_aids_attributes, :students_attributes


  has_many :students, inverse_of: :teacher
  has_many :teacher_aids, inverse_of: :teacher

  has_one :address, as: :addressable, class_name: "Address"
  has_one :email
  has_one :primary_phone, as: :phoneable, class_name: "PhoneNumber"
  has_one :secondary_phone, as: :phoneable, class_name: "PhoneNumber"

  accepts_nested_attributes_for :address, :primary_phone, :secondary_phone, :teacher_aids, :students
end

teacher_aid.rb

class TeacherAid < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :address_attributes, :primary_phone_attributes, :secondary_phone_attributes, :teacher_id

  belongs_to :teacher, inverse_of: :teacher_aids

  has_many :students, inverse_of: :teacher_aids

  has_one :address, as: :addressable, class_name: "Address"
  has_one :email
  has_one :primary_phone, as: :phoneable, class_name: "PhoneNumber"
  has_one :secondary_phone, as: :phoneable, class_name: "PhoneNumber"

  accepts_nested_attributes_for :address, :primary_phone, :secondary_phone
end

student.rb

class Student < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :address_attributes, :primary_phone_attributes, :secondary_phone_attributes,
                  :teacher_id, :teacher_aid_id

  belongs_to :teacher
  belongs_to :teacher_aid

  has_one :address, as: :addressable, class_name: "Address"
  has_one :email
  has_one :primary_phone, as: :phoneable, class_name: "PhoneNumber"
  has_one :secondary_phone, as: :phoneable, class_name: "PhoneNumber"

  accepts_nested_attributes_for :address, :primary_phone, :secondary_phone
end

Note how I'm also adding accepts_nested_attributes_for in both the Student and TeacherAid classes. This is because they also are composed of an address and two phone_number objects.

  1. Add the nested attributes hash to your class' attr_accessible in order to white list these nested attributes.

ex:

A teacher has one address...

attr_accessible :teacher_first_name, :teacher_last_name, :address_attributes

A teacher has many apples...

attr_accessible :teacher_first_name, :teacher_last_name, :apples_attributes

Note the change from singular to plural spelling for has_one vs has_many.

  1. Add accepts_nested_attribtues_for :name_of_nested_resource to all classes that want their attributes or attributes of a composed object listed on the parent form template.

  2. In the parent controller (i.e. the one you want to use to store attributes of all composed objects) build the nested instances in the new method like so.

ex:

teachers_controller.rb

class TeachersController < ApplicationController
  def index
    @teachers = Teacher.all
  end

  def new
    @teacher = Teacher.new
    @teacher.build_address
    @teacher.build_primary_phone
    @teacher.build_secondary_phone

    @teacher_aid = @teacher.teacher_aids.build
    @teacher_aid.build_address
    @teacher_aid.build_primary_phone
    @teacher_aid.build_secondary_phone

    @student = @teacher.students.build
    @student.build_address
    @student.build_primary_phone
    @student.build_secondary_phone
  end
end

Note: That I haven't created any build methods in my descendant classes even though they are composed of other objects. As I understand it, this is due to Rails creating the nested objects through the parent (i.e. In this case, Teacher).

Here's an example from the Rails Guides of how the nested hash will look:

Ex:

teacher_aids_controller.rb

class TeacherAidsController < ApplicationController
  before_filter :get_teacher_aid, except: [:index, :new, :create]

  def index
    @teacher_aids = TeacherAid.all
  end

  def new
    @teacher_aids = TeacherAid.all
  end
end

students_controller.rb

class StudentsController < ApplicationController
  before_filter :get_student, except: [:index, :new, :create]

  def index
    @students = Student.all
  end

  def new
    @student = Student.new 
  end
end
  1. When adding a double (or more than double) nested :address_attributes or whatever the name, we do need to nest a fields_for in our view template like so.

ex:

teachers/_form.html.erb

  <%= form_for @teacher, html: { multipart: true, class: "form-horizontal" } do |f| %>
    <%= f.text_field :first_name, placeholder: "Teacher's First Name" %><br />
    <%= f.text_field :last_name, placeholder: "Teacher's Last Name" %><br />
    <%= f.fields_for :address do |teacher_address| %>
      <%= teacher_address.text_field :street_address, placeholder: "Teachers street_address" %>
      <%= teacher_address.text_field :city, placeholder: "Teacher's city" %>
      <%= teacher_address.text_field :state, placeholder: "Teacher's state" %>
      <%= teacher_address.text_field :zip, placeholder: "Teacher's zip" %>
    <% end %>
    <%= f.fields_for :primary_phone do |teacher_phone1| %>
      <%= teacher_phone1.phone_field :area_code, placeholder: "Primary Area Code" %>
      <%= teacher_phone1.phone_field :number, placeholder: "Primary Number" %>
    <% end %>
    <%= f.fields_for :secondary_phone do |teacher_phone2| %>
      <%= teacher_phone2.phone_field :area_code, placeholder: "Secondary Area Code" %>
      <%= teacher_phone2.phone_field :number, placeholder: "Secondary Number" %>
    <% end %>
    <%= f.fields_for :teacher_aids do |teach_aid| %>
      <%= teach_aid.text_field :first_name, placeholder: "Aid's First Name" %><br />
      <%= teach_aid.text_field :last_name, placeholder: "Aid's Last Name" %><br />
      <%= teach_aid.fields_for :address do |address| %>
        <%= address.text_field :street_address, placeholder: "Aids Street Address" %><br />
        <%= address.text_field :city, placeholder: "Aids city" %><br />
        <%= address.text_field :state, placeholder: "Aids state" %><br />
        <%= address.text_field :zip, placeholder: "Aids zip" %><br />
      <%end %>
      <%= teach_aid.fields_for :primary_phone do |phone1| %>
        <%= phone1.phone_field :area_code, placeholder: "Aid's Area Code" %>
        <%= phone1.phone_field :number, placeholder: "Aid's Primary Number" %>
      <% end %>
      <%= teach_aid.fields_for :secondary_phone do |phone2| %>
        <%= phone2.phone_field :area_code, placeholder: "Aid's Area Code" %>
        <%= phone2.phone_field :number, placeholder: "Aid's Secondary Number" %>
      <% end %>
    <% end %>

Note how :address, :primary_phone and :secondary_phone are all nested under their various fields_for for their respective object.

My current obstacle is now my primary_phone and secondary_phone polymorphic models are only displaying the primary phone data. Meaning that for some reason, has_one :primary_phone, as: :phoneable, class_name: "PhoneNumber" and has_one :secondary_phone, as: :phoneable, class_name: "PhoneNumber" aren't being treated as two separate things. But that is the topic of another question.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!