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:
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.
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.
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
.
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.
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
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.