使用 Cacoon 接受嵌套 strong_params

Accepting nested strong_params using Cacoon

我一直在尝试构建一个相当复杂的食谱对象,该对象应该具有不确定数量的成分("macaroni"、"cheese"、"butter")和方向("boil noodles", "add cheese").

食谱表单对象是使用 Cacoon gem 构建的,允许用户添加那些嵌套的 ingredient/direction 值。我在控制器中定义了 strong_params,这些参数已成功发送到 create/update RecipesController 操作;但是控制器仍在回滚提交。

关于我忘记了什么的任何线索?

注意

如果我保存的配方没有包含任何嵌套属性,则配方创建成功,然后我可以edit/update配方并添加嵌套属性。所以它只是抱怨创建操作...

这是相关的控制台输出:

Processing by RecipesController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"[token removed for brevity]", "recipe"=>{"name"=>"French bread", "cuisine_id"=>"2", "ingredients_attributes"=>{"0"=>{"quantity"=>"500", "measure"=>"grams", "item"=>"flour", "_destroy"=>"false"}, "1"=>{"quantity"=>"250", "measure"=>"ml", "item"=>"water", "_destroy"=>"false"}, "2"=>{"quantity"=>"10", "measure"=>"grams ", "item"=>"yeast", "_destroy"=>"false"}}, "directions_attributes"=>{"0"=>{"direction"=>"Knead dough", "_destroy"=>"false"}, "1"=>{"direction"=>"Let rise 2 hours", "_destroy"=>"false"}, "2"=>{"direction"=>"Bake in 500 degree oven for 30 min.", "_destroy"=>"false"}}}, "button"=>""}
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ?  [["id", 1], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  Cuisine Load (0.1ms)  SELECT  "cuisines".* FROM "cuisines" WHERE "cuisines"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
   (0.1ms)  rollback transaction

recipe.rb

    class Recipe < ApplicationRecord
     belongs_to :user
     belongs_to :cuisine

     has_many :ingredients, dependent: :destroy
     has_many :directions, dependent: :destroy
     validates :name, presence: true


     accepts_nested_attributes_for :ingredients,
                                  reject_if: :all_blank,
                                  allow_destroy: true

     accepts_nested_attributes_for :directions,
                                  reject_if: :all_blank,
                                  allow_destroy: true
   end

recipes_controller.rb

    class RecipesController < ApplicationController
      before_action :verify_user
      before_action :set_recipe, only: [:show, :edit, :update, :destroy]

      [...]

      def new
        @recipe = current_user.recipes.build
      end

      def create
        @recipe = current_user.recipes.build(recipe_params)

        if @recipe.save
         redirect_to @recipe, notice: "Recipe added!"
        else
         flash.now[:alert] = "There was a problem saving the recipe. Please try again."
         render :new
        end
      end

      [...]

    private

     def set_recipe
      @recipe = Recipe.find(params[:id])
     end

    def recipe_params
      params.require(:recipe).permit(:name, :cuisine_id,
                                   ingredients_attributes: [:quantity, :measure, :item, :_destroy],
                                   directions_attributes: [:direction, :_destroy])
     end
    end

_form.html.erb

<%= form_for recipe, html: { multipart: true } do |f| %>
  <div class="form-group">
    <%= f.label :name %>
    <%= f.text_field :name, placeholder: 'Recipe name', class: "form-control" %>
  </div>
  <div class="form-group">
    <%= f.label :cuisine_id %><br>
    <%= f.select(:cuisine_id, options_from_collection_for_select(Cuisine::all, :id, :name),
    {:prompt => 'Please Choose'}, :class => "form-control") %>
  </div>

    <div class="col-md-8">
      <div class="form-group">
        <h3>Ingredients</h3>
          <%= f.fields_for :ingredients do |ingredient| %>
            <%= render 'ingredient_fields', f: ingredient %>
          <% end %>
        <div class="links">
          <%= link_to_add_association 'Add Ingredient', f, :ingredients, class: "form-button btn btn-default" %>
        </div>
      </div>
    </div>

    <div class="col-md-4">
      <div class="form-group">
        <h3>Directions</h3>
        <%= f.fields_for :directions do |direction| %>
          <%= render 'direction_fields', f: direction %>
        <% end %>
        <div class="links">
          <%= link_to_add_association 'Add Direction', f, :directions, class: "form-button btn btn-default" %>
        </div>
      </div>
    </div>
    <div class="form-inline clearfix col-md-12">
      <%= f.button :submit, class: "form-button btn btn-success" %>
      <%= link_to "Back", url_for(:back), class: "form-button btn btn-default" %>
    </div>
<% end %>

_ingredient_fields.html.erb

<div class="form-inline clearfix">
  <div class="nested-fields">
    <%= f.number_field :quantity, placeholder: 'qnt', class: "form-control" %>
    <%= f.text_field :measure, placeholder: 'measure', class: "form-control" %>
    <%= f.text_field :item, placeholder: 'ingredient', class: "form-control" %>
    <%= link_to_remove_association "Remove", f, class: "form-button btn btn-default" %>
    </div>
  </div>

_direction_fields.html.erb

<div class="form-inline clearfix">
  <div class="nested-fields">
    <%= f.text_area :direction, placeholder: 'Direction', class: "form-control" %>
    <%= link_to_remove_association "Remove", f, class: "btn btn-default form-button" %>
  </div>
</div>

像这样尝试将 :id 放在嵌套模型的参数括号中

params.require(:recipe).permit(:name, :cuisine_id,
                               ingredients_attributes: [:id, :quantity, :measure, :item, :_destroy],
                               directions_attributes: [:id, :direction, :_destroy])

所以问题是 Rails 5 的错误,与 belongs_to_required_by_default 方法被设置为 false 有关。这解释了为什么可以使用嵌套属性更新记录但不能创建记录。您可以通过完全禁用父对象验证或在 parent_model.rb 文件中包含 inverse_of: :parent_model_name 来解决此问题。

关于问题的更多信息here