如何在具有 multiple-directional 关联的 Rails 中创建三重嵌套 objects

How to create triple nested objects in Rails with multiple-directional associations

一篇名为 Triple nested Forms in Rails 的文章很好地描述了如何创建用于保存三个嵌套 objects 的表单。给出的示例是创建一个 Showhas_many Seasons,每个 Season has_many Episodes。另外,Episode --> belongs_to --> Season --> belongs_to --> Show。 节目是这样创建的:

def new
@show = Show.new
@show.seasons.build.episodes.build
end

表单如下所示:

<%= form.fields_for :seasons do |s| %>
    <%= s.label :number %>
    <%= s.number_field :number %>    <%= s.fields_for :episodes do |e| %>
      <%= e.label :title %>
      <%= e.text_field :title %>
    <% end %> 
 <% end %>
<% end %>

这看起来很简单,因为所有关联 运行 都指向一个方向。我正在尝试做一些类似但更复杂的事情。我有一个 Parent 模型,其中每个 Parent 有多个 Children,每个 Child 都注册了一个 School。在指定 ChildrenChild 的复数后,关联必须是这样的:

Parent has_many Children, accepts_nested_attributes_for :children
Child belongs_to Parent, belongs_to School, accepts_nested_attributes_for :school
School has_many Children, accepts_nested_attributes_for :children

从图形上看,它看起来像这样:

Parent <-- belongs_to <-- Child --> belongs_to --> School

每个 Parent 也与一个用户相关联,如下所示:

User has_many :parents

Parents、Children 和学校的数据在以下表格中输入(使用简单表格 gem 生成),其中学校作为下拉列表输入从学校 table:

填充的选择器
@schools = School.all

<%= simple_form_for (@parent) do |f| %>
     <%= f.input :name, label: 'name' %>
     <%= f.simple_fields_for :children, @children do |child_form| %>
         <%= child_form.input :name, label: "Child Name" %>
         <%= child_form.simple_fields_for :school, @school do |school %>
             <%= school.collection_select :id, @schools, :id, :name, {}, {} %>
            <% end %>
     <% end %>
<% end %>

我设置了 new 控制器方法来创建一个 Parent,在现有的 School 中注册了三个 Children。然后我尝试将 Childrenschools table 中已经存在且 id = 1 的 School 相关联。

def new
   @parent = Parent.new
   # creating 3 children
   @children = Array.new(3) {@parent.children.build}
   @school = School.find(1)
   @school.children.build
end

这会引发错误

Couldn't find School with ID=1 for Child with ID=

错误位于创建方法的第一行,如下所示:

def create
    @parent = Parent.new(parent_params.merge(:user => current_user))
    if @parent.save
        redirect_to root_path
    else
        render :new, :status => :unprocessable_entity
    end
end

def parent_params
    params.require(:parent).permit(:name, :child_attributes => [:id, :name, age, :school_attributes => [:id, :name]])
end

由于错误文本指出 "Child with ID= ",因此必须在分配新 Children 的 ID 之前抛出错误。为什么ID=1的School存在于schoolstable中却找不到?或者,这是否意味着 School 记录在尝试保存该实例之前未与 Child 的实例正确关联?如果是这样,我该如何修复关联?

最常见的 missconceptions/misstakes 嵌套属性之一是认为您需要它来进行简单的关联分配。你不知道。您只需要将 id 传递给 assocation_name_id= setter.

如果使用嵌套属性甚至根本无法满足您的要求。它不会在您执行 child.school_attributes = [{ id: 1 }] 时从现有记录创建关联,而是会尝试创建新记录或更新现有学校记录。

如果用户同时创建学校,您只需要接受学校的嵌套属性。在那种情况下,使用 Ajax 而不是将所有内容都塞进一个大型动作中可能是一个更好的主意。

<%= simple_form_for (@parent) do |f| %>
  <%= f.input :name, label: 'name' %>
  <%= f.simple_fields_for :children, @children do |child_form| %>
    <%= child_form.input :name, label: "Child Name" %>
    <%= child_form.associaton :school, 
        collection: @schools, label_method: :name %>
  <% end %>
<% end %>
def parent_params
  params.require(:parent).permit( :name, 
    child_attributes: [:id, :name, :age, :school_id]]
  )
end