I slightly modified and extended the example for building nested forms to implement the second level of nesting. Everything is displayed perfectly on the form. Data for the person is displayed at both nesting levels correctly. The corresponding JS scripts work to add and remove nested forms. All 3 are generated using scaffold.
But when I click on update, only the main form and the first nesting level (addresses) are updated. The second nesting level (nested addresses) is not updated. Although I also get parameters from the second nesting level in the controller ("name"=>"UPDATE NAME OF NESTED ADDRESS").
{
"_method"=>"patch",
"authenticity_token"=>"VZ09CR-aO2D4Wv3AwEa5PHXo-mA_--c6QPUN6f0Gb_9SJJSL2gIwzCl4G4SbzRy2t3wxJHytBWiPwysNJMrWgg",
"person"=>{
"first_name"=>"Liz",
"last_name"=>"Smith",
"addresses_attributes"=>{
"0"=>{
"_destroy"=>"false",
"kind"=>"Some kind",
"street"=>"Some street",
"nested_addresses_attributes"=>{
"0"=>{
"_destroy"=>"false",
"name"=>"UPDATE NAME OF NESTED ADDRESS",
"id"=>"1"
}
},
"id"=>"10"}}},
"commit"=>"Update Person",
"controller"=>"people",
"action"=>"update",
"id"=>"3"}
I understand that even the first nesting level is magically handled behind the scenes, but I don't understand how? And how to handle the second level as well? In general, the Create Update, Delete methods do not work for the second nesting level.
class Person < ApplicationRecord
has_many :addresses, inverse_of: :person, :dependent => :destroy
has_many :nested_addresses, through: :addresses, inverse_of: :person, :dependent => :destroy
accepts_nested_attributes_for :addresses, allow_destroy: true, reject_if: :all_blank
accepts_nested_attributes_for :nested_addresses, allow_destroy: true, reject_if: :all_blank
validates :first_name, presence: true
validates :last_name, presence: true
end
class NestedAddress < ApplicationRecord
belongs_to :address
validates :name, presence: true
end
class Address < ApplicationRecord
belongs_to :person, optional: true
has_many :nested_addresses, inverse_of: :address, :dependent => :destroy
accepts_nested_attributes_for :nested_addresses, allow_destroy: true, reject_if: :all_blank
validates :kind, presence: true
validates :street, presence: true
end
def person_params
params.require(:person).permit(:first_name, :last_name, addresses_attributes: [:id, :kind, :street, :_destroy], nested_addresses_attributes: [:id, :name, :_destroy])
end
def address_params
params.require(:address).permit(:kind, :street, :person_id, nested_addresses_attributes: [:id, :name, :_destroy])
end
def nested_address_params
params.require(:nested_address).permit(:name, :address_id)
end
<%= form_with model: @person, local: true do |f| %>
<%= render "shared/validation-messages", object: @person %>
<%= f.label :first_name %>
<%= f.text_field :first_name, class: 'form-control' %>
<%= f.label :last_name %>
<%= f.text_field :last_name, class: 'form-control' %>
<br>
<fieldset>
<legend>Addresses:</legend>
<%= f.fields_for :addresses do |addresses_form| %>
<%= render "address_fields", f: addresses_form %>
<% end %>
<br>
<%= link_to_add_fields "Add Addresses", f, :addresses, 'btn btn-outline-secondary'%>
</fieldset>
<br>
<%= f.submit class: 'btn btn-success' %>
<% if params[:action] === "edit" && params[:controller] === "people" %>
<%= link_to "Delete Person", person_path(@person), method: :delete, data: { confirm: "Are You Sure?" }, class: 'btn btn-outline-danger' %>
<% end %>
<% end %>
<div class="card nested-fields">
<div class="card-header">
<div><%= f.object.id %></div>
</div>
<div class="card-body">
<%= f.hidden_field :_destroy %>
<div>
<%= f.label :kind %>
<%= f.text_field :kind, class: 'form-control' %>
</div>
<div>
<%= f.label :street %>
<%= f.text_field :street, class: 'form-control' %>
</div>
<br>
<fieldset>
<legend>Nested addresses:</legend>
<%= f.fields_for :nested_addresses do |nested_addresses_form| %>
<%= render "nested_address_fields", f: nested_addresses_form %>
<% end %>
<br>
<%= link_to_add_fields "Add Nested Addresses", f, :nested_addresses, 'btn btn-outline-secondary btn-sm' %>
</fieldset>
<br>
<div>
<%= link_to "Remove address", '#', class: "remove_fields btn btn-outline-danger btn-sm" %>
</div>
</div>
</div>
<div class="card nested-fields">
<div class="card-body">
<%= f.hidden_field :_destroy %>
<div>
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<br>
<div>
<%= link_to "Remove nested address", '#', class: "remove_fields btn btn-outline-danger btn-sm" %>
</div>
</div>
<br>
</div>
def link_to_add_fields(name, f, association, cl)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: 'add_fields ' + cl, data: {id: id, fields: fields.gsub("\n", "")}, role: 'button')
end
Your strong parameters permit
call should reflect the actual nesting structure of parameters:
def person_params
params.require(:person).permit(
:first_name, :last_name,
addresses_attributes: [
:id, :kind, :street, :_destroy,
{ nested_addresses_attributes: [:id, :name, :_destroy] } # <= note the hash
]
)
end