Rails 2 级嵌套表单

Rails 2 level nested forms

我稍微修改和扩展了构建嵌套表单的示例以实现第二级嵌套。一切都完美地显示在表格上。此人的数据在两个嵌套级别均正确显示。相应的 JS 脚本用于添加和删除嵌套表单。所有 3 个都是使用脚手架生成的。

但是当我点击更新时,只有主窗体和第一层嵌套(地址)被更新。第二嵌套级别(嵌套地址)未更新。尽管我也从控制器中的第二个嵌套级别获取参数 ("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"}

我知道即使是第一层嵌套也是在幕后神奇地处理的,但我不明白是怎么做到的?以及如何处理第二级?通常,Create Update、Delete 方法不适用于第二个嵌套级别。

型号


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

people/_form.html.erb

<%= 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 %>

people/_address_fields.html.erb

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

people/_nested_address_fields.html.erb

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

helpers/application_helper.rb

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

您的强参数 permit 调用应反映参数的实际嵌套结构:

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