简体   繁体   中英

deep nested accepts_nested_attributes_for not rendering attribute data in edit template

I'm having a bit of a challenge with understanding accepts_nested_attributes_for.

I have a Resident class that is composed of various child classes (address, doctor, guardian, etc). My understanding of the steps necessary to make accepts_nested_attributes_for work is as follows:

  1. Create the necessary association(s)
  2. Add accepts_nested_attributes_for :resource to the parent class whose form I will use to commit the nested attributes
  3. whitelist nested attributes like so. attr_accessible :address_attributes, :doctor_attributes, etc...
  4. In the parent controller, build the associated resources. ie @resident.addresses.build (for has_many associations), @resident.build_doctor (for has_one association)
  5. add a fields_for block for each child resource to include their attribute values.

I have the address working for my a resident record, but my challenge arises when I try to add the other classes. For instance, doctor also needs an address and so does guardian.

Would I also add accepts nested_attributes_for :address to my doctor.rb model? And if so, would that mean that I would need a controller for this resource also invoking build methods for it's components? And would this be the time to use nested fields_for in my form template? (example follows)

  <%= f.fields_for :doc1 do |doc| %>
    <%= doc.text_field :fname %>
    <%= doc.text_field :lname %>
  <%= doc.fields_for :address do |doc_address| %>
    <%= doc_address.text_field :street_address %>
    <%= doc_address.text_field :city %>
    <%= doc_address.text_field :state %>
  <% end %>
  <%= doc.fields_for :primary_phone do |phone1| %>
    <%= phone1.phone_field :area_code %>
    <%= phone1.phone_field :number %>
  <% end %>
<% end %>

Here are the associated files:

resident.rb

class Resident < ActiveRecord::Base
  attr_accessible :address_attributes, :guardian_attributes, :desrep_attributes, :doctor_attributes,
                  :em_contact1_attributes, :em_contact2_attributes, :fname, :lname, :gender, :pic, :soc, :dob, 
                  :marital_stat, :placement_name, :placement_address, :res_start_date, 
                  :res_end_date, :religious_prefs, :insurance_info, :burial_provisions, 
                  :case_number, :vet_stat_num, :admission_height, :admission_weight, 
                  :resident_initials, :allergies,:admin_id

  belongs_to :admin
  has_one :address, as: :addressable
  has_one :guardian
  has_one :desrep, class_name: "DesignatedRepresentative"
  has_one :doc1, class_name: "Doctor"
  has_one :em_contact1, class_name: "EmergencyContact"
  has_one :em_contact2, class_name: "EmergencyContact"
  has_one :primary_phone, class_name: "PhoneNumber"
  has_one :secondary_phone, class_name: "PhoneNumber"

  has_many :assessment_plan_forms, dependent: :destroy
  has_many :blood_pressure_record_forms, dependent: :destroy
  has_many :fund_record_form1s, dependent: :destroy
  has_many :fund_record_form2s, dependent: :destroy
  has_many :incident_accident_forms, dependent: :destroy
  has_many :med_record_forms, dependent: :destroy
  has_many :personal_care_forms, dependent: :destroy
  has_many :resident_care_agreement_forms, dependent: :destroy
  has_many :visitation_appointment_forms, dependent: :destroy
  has_many :weight_record_forms, dependent: :destroy

  accepts_nested_attributes_for :address, allow_destroy: true
  accepts_nested_attributes_for :guardian, allow_destroy: true
  accepts_nested_attributes_for :desrep, allow_destroy: true
  accepts_nested_attributes_for :doc1, allow_destroy: true
  accepts_nested_attributes_for :em_contact1, allow_destroy: true
  accepts_nested_attributes_for :em_contact2, allow_destroy: true

  validates_presence_of :fname, :lname

  def full_name
    "#{ fname } #{ lname }"
  end

  def guard_fname
    guarian.fname
  end
end

address.rb

class Address < ActiveRecord::Base
  attr_accessible :street_address, :city, :state, :zip, :addressable_type, :addressable_id

  belongs_to :addressable, polymorphic: true
end

doctor.rb

class Address < ActiveRecord::Base
  attr_accessible :street_address, :city, :state, :zip, :addressable_type, :addressable_id

  belongs_to :addressable, polymorphic: true
end

emergency_contact.rb

class EmergencyContact < ActiveRecord::Base
  attr_accessible :address_attributes, :primary_phone_attributes, :secondary_phone_attributes, :fax_attributes,
                  :fname, :lname, :primary, :email, :resident_id

  belongs_to :resident

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

  accepts_nested_attributes_for :address
  accepts_nested_attributes_for :primary_phone
  accepts_nested_attributes_for :secondary_phone
  accepts_nested_attributes_for :fax
end

residents_controller.rb - (build code is inside the 'new' method)

class ResidentsController < ApplicationController
  before_filter :authenticate_admin!

  def index
    @residents = Resident.all
  end

  def new
    @resident = Resident.new
    @resident.build_address
    @resident.build_guardian
    @resident.build_desrep
    @resident.build_em_contact1
    @resident.build_em_contact2
  end
end

views/residents/_form.html.erb (this shows the working address fields_for for a resident)

<%= f.fields_for :address do |builder| %>
  <div class="control-group">
    <%= builder.label :street_address, "Address:", class: "control-label" %>
    <div class="controls">
      <%= builder.text_field :street_address, class: "text_field", placeholder: "Resident's Address" %>
    </div>
  </div>
  <div class="control-group">
    <%= builder.label :city, "City:", class: "control-label" %>
    <div class="controls">
      <%= builder.text_field :city, class: "text_field", placeholder: "Resident's City" %>
    </div>
  </div>
  <div class="control-group">
    <%= builder.label :state, "State:", class: "control-label" %>
    <div class="controls">
      <%= builder.text_field :state, class: "text_field", placeholder: "Resident's State" %>
    </div>
  </div>
  <div class="control-group">
    <%= builder.label :zip, "Zip:", class: "control-label" %>
    <div class="controls">
      <%= builder.text_field :zip, class: "text_field", placeholder: "Resident's Zip Code" %>
    </div>
  </div>
<% end %>

views/residents/_form.html.erb ( this shows the doctor object as 'doc1', that isn't working)

<%= fields_for :doc1 do |doc| %>
  <div class="control-group">
    <%= doc.label :fname, "Doctor's First Name:", class: "control-label" %>
    <div class="controls">
      <%= doc.text_field :fname, class: "text_field", placeholder: "Doctor's First Name" %>     
    </div>
  </div>
  <div class="control-group">
    <%= doc.label :lname, "Doctor's Last Name:", class: "control-label" %>
    <div class="controls">
      <%= doc.text_field :lname, class: "text_field", placeholder: "Doctor's Last Name" %>
    </div>
  </div>
  <div class="control-group">
    <%= doc.label :initials, "Initials:", class: "control-label" %>
    <div class="controls">
      <%= doc.text_field :initials, class: "text_field", placeholder: "Doctor's Initials" %>     
    </div>
  </div>
  <div class="control-group">
    <%= doc.label :phone1, "Doctor's Primary Phone:", class: "control-label" %>
    <div class="controls">
      <%= doc.phone_field :phone1_area_code, placeholder: "Area Code", style: "width: 25%;" %>
      <%= doc.phone_field :phone1_number, class: "text_field", placeholder: "i.e. 800-555-1212" %>
    </div>
  </div>
  <div class="control-group">
    <%= doc.label :phone2, "Doctor's Secondary Phone:", class: "control-label" %>
    <div class="controls">
      <%= doc.phone_field :phone2_area_code, placeholder: "Area Code", style: "width: 25%;" %>
      <%= doc.phone_field :phone2_number, class: "text_field", placeholder: "i.e. 800-555-1212" %>
    </div>
  </div>
  <div class="control-group">
    <%= doc.label :fax, "Doctor's Fax:", class: "control-label" %>
    <div class="controls">
      <%= doc.phone_field :fax_area_code, placeholder: "Area Code", style: "width: 25%;" %>
      <%= doc.phone_field :fax_number, class: "text_field", placeholder: "Doctor's Fax Number" %>
    </div>
  </div>
  <div class="control-group">
    <%= doc.label :email, "Doctor's Email:", class: "control-label" %>
    <div class="controls">
      <%= doc.text_field :email, class: "text_field", placeholder: "Doctor's Email Address" %>
    </div>
  </div>
  <div class="control-group">
    <%= doc.label :street_address, "Doctor's Address:", class: "control-label" %>
    <div class="controls">
      <%= doc.text_field :street_address, class: "text_field", placeholder: "Doctor's Street Address" %>
    </div>
  </div>
  <div class="control-group">
    <%= doc.label :city, "Doctor's City:", class: "control-label" %>
    <div class="controls">
      <%= doc.text_field :city, class: "text_field", placeholder: "Doctor's City" %>
    </div>
  </div>
  <div class="control-group">
    <%= doc.label :state, "Doctor's State:", class: "control-label" %>
    <div class="controls">
      <%= doc.text_field :state, class: "text_field", placeholder: "Doctor's State" %>
    </div>
  </div>
  <div class="control-group">
    <%= doc.label :zip, "Doctor's Zip:", class: "control-label" %>
    <div class="controls">
      <%= doc.text_field :zip, class: "text_field", placeholder: "Doctor's Zip Code" %>
    </div>
  </div>
<% end %>

Before using accepts_nested_attributes for I wanted to simply lazy load my objects in or pass them through the initializer. But there is more reading I'll have to do first, because I could not get the results I wanted using those techniques so far.

I can show the remaining view code and any additional files upon request.

Thanks in advance.

Update: 05-25-14 - I am still working on figuring this out, and have written this blog post about where I'm at currently with my understanding.

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 (ie 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 (ie 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM