在 Rails 中创建嵌套的两级表单

Creating a nested two level form in Rails

我有一个 Restaurant 模型,它有很多 DishCategory,也有很多 DishDishCategory。因此,例如,类别 Dinner 将是与其关联的一些菜肴。为了帮助构建表单,我正在使用 cocoon gem.

我熟悉为传统的嵌套表单设置视图代码,但现在我要更深入一层,我不确定如何编写以及将菜肴的输入放在哪里。我是否在部分内容中为 dishes 设置了另一个 simple_fields_for 块?按照文档,这是目前的设置方式:

型号

class Restaurant < ActiveRecord::Base
  has_many :dish_categories, dependent: :destroy
  has_many :dishes, through: :dish_categories, dependent: :destroy

  accepts_nested_attributes_for :dish_categories, :reject_if => :all_blank, :allow_destroy => true
end

class DishCategory < ActiveRecord::Base
  belongs_to :restaurant

  has_many :dishes, dependent: :destroy

  accepts_nested_attributes_for :dishes, :reject_if => :all_blank, :allow_destroy => true
end

class Dish < ActiveRecord::Base
  belongs_to :dish_category
end

餐厅表格

<%= simple_form_for(@restaurant) do |f| %>
  <%= f.error_notification %>

  <div class="form-inputs">
    <%= f.input :name %>
  </div>

  <div class="form-inputs dish-category-inputs">
    <%= f.simple_fields_for :dish_categories do |f| %>
      <%= render 'dish_category_fields', f: f %>
    <% end %>
  </div>

  <div class='add-dish-category'>
    <%= link_to_add_association 'Add a Dish Category', f, :dish_categories %>
  </div>

  <div class="form-actions">
    <%= f.button :submit %>
  </div>
<% end %>

菜肴类别字段

<div class="dish-category-form nested-fields">
    <%= f.input :name %>

    <%= link_to_remove_association "Remove Category", f %>
</div>

是的,您需要使用 simple_fields_for 来制作部分菜肴。并且不要忘记在 DishCategory 模型中使用 accepts_nested_attributes_for :dishes。

所以看起来我确实需要创建一个额外的部分。据我了解,links_to_add_association 中的第三个参数需要与您希望嵌套的表单的模型关联的部分。

菜肴类别字段

<div class="dish-category-form nested-fields">
    <%= f.input :name %>

    <%= link_to_remove_association "Remove Category", f %>
</div>

<div class="form-inputs dish-category-inputs">
  <%= f.simple_fields_for :dishes do |f| %>
    <%= render "dish_fields", f: f %>
  <% end %>
</div>

<div class='add-dish'>
    <%= link_to_add_association "Add Dish", f, :dishes %>
</div>

菜田

<div class="dish-category-form nested-fields">
    <%= f.input :name %>

    <%= link_to_remove_association "Remove Dish", f %>
</div>

注意:

当涉及到餐厅控制器代码时,它可能会让人们误以为将您的参数列入白名单。将 dishes_attributes 放在 dish_categories_attributes 中很重要,以便正确保存所有内容:

餐厅管理员

class RestaurantsController < ApplicationController
  ...
  private
    def restaurant_params
      params.require(:restaurant).permit(:name, dish_categories_attributes: [:id, :restaurant_id, :name, :_destroy, dishes_attributes: [:id, :dish_category_id, :name, :description, :_destroy]])
    end
end

我有同样的问题,因为我努力让 cocoon 与两个级别的 has_many 关联一起工作。就我而言,问题最终源于将 cocoon 与 Trailblazer 的 Reform 表单对象结合使用。我发布了一个答案,以防其他人遇到同样的问题。

问题(此处:https://github.com/nathanvda/cocoon/blob/master/lib/cocoon/view_helpers.rb#L95)是 cocoon 将关联对象的嵌套字段基于模型的纯实例,而不是包裹在 Reform 表单对象中的模型。 (不是批评,只是它的设计方式。)这很好,直到您想要为该嵌套对象 on 上的关联呈现字段,因为嵌套属性的设置在您的 Reform 中定义表单对象,而不是您的模型。

基本上 f.object 在您的表单部分中是实际模型,而不是表单对象。除非模型本身配置了 accept_nested_attributes,否则 fields_for 不会将属性视为 nested_attributes_association 并且 html 标记不会以这样的方式命名字段-rails 可以将它们解析为正确嵌套的参数。

我的解决方案是使用 wrap_object 来确保 Cocoon 使用的记录是包装在相关表单对象中的模型,如下所示:

<%= link_to_add_association 'Add Supporting Image', f, :images, partial: 'web_app/report/supporting_image_fields', force_non_association_create: true, wrap_object: Proc.new {|image| Report::WebAppCreation::ImageWithoutHotspotsForm.new(image) } %>

(可能有更好的方法来获取相关的改革表格,但这提供了思路)。