Ruby on Rails 4:提交后缺少表单字段 - 错误是 "Unpermitted parameter: ingredients_attributes"

Ruby on Rails 4: Missing form field after commit - error is "Unpermitted parameter: ingredients_attributes"

问题:

我正在使用 cocoon gemfile 并在没有 haml/slim 的情况下遵循它的 ERB 风格。我这里有我的 github - 我正在处理的 b运行ch 是 "cmodel" - Link

错误是:不允许的参数:ingredient_attributes

真的,我不明白的是关于模型的一切。我明白多元化是一回事。我知道强参数是一回事。我对实施它们的理解有点模糊。我发现 rails guides 和 edge rails guides 对于此处的扩展使用来说太简单了。我已经进行了 80 多次更改,感觉我的安装完全失败了。

我做了一些额外的事情:

  1. 在试图猜测问题的模型中盲目地添加过多 accepts_attributes_for
  2. 我添加了一个非复数的强参数来尝试找出是否是这个问题

  3. 在 quantities_attributes 中添加了成分 - 盲目希望就是这样

  4. 删除了所有成分_属性

  5. 我现在有一个奇怪的模式,将东西放入数据库可能不应该

  6. 从允许的强参数列表中提取成分和数量。然后添加到 quantities_attributes - 没有问题。然后尝试对 ingredient(s)_attributes 进行同样的操作...问题仍然存在。

更新 @ 08:29pm - 12/25/15:

奇怪的副作用... 15 或 20 次测试前,当您单击添加成分时我注意到第二个字段 link 可以添加,但是当您单击编辑时第二列不显示。可能相关?

总而言之,我想这一切都应该有效——它与其他人使用的另一个示例相匹配,根据示例,它没有理由失败。

该代码对强参数区域中的复数完全没有反应 - 令人沮丧。我觉得我的开发方法现在很无聊,在这个垃圾4小时后休息一下。

当前错误的控制台输出:已更新@08:29pm - 12/25/15

    Started PATCH "/recipes/1" for 68.54.21.200 at 2015-12-26 03:00:14 +0000
    Cannot render console from 68.54.21.200! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
    Processing by RecipesController#update as HTML
      Parameters: {"utf8"=>"✓", "authenticity_token"=>"OzJmuRuIMWVbJ1CpnCTyU52GhTEmrsOfiBiHACzIviN02MO48HBaZlX8JKvZvBXErqBfbXULnhcjT3gk2Zrh+A==", "recipe"=>{"title"=>"Recipe name", "description"=>"Recipe Description of Lindy's ...", "instruction"=>"Recipe Instruction", "quantities_attributes"=>{"0"=>{"amount"=>"8", "_destroy"=>"false", "id"=>"63"}, "1"=>{"amount"=>"9", "_destroy"=>"false", "id"=>"64"}, "1451098805028"=>{"amount"=>"11", "ingredient_attributes"=>{"name"=>"11"}, "_destroy"=>"false"}}}, "commit"=>"Update Recipe", "id"=>"1"}
      Recipe Load (0.2ms)  SELECT  "recipes".* FROM "recipes" WHERE "recipes"."id" = ? LIMIT 1  [["id", 1]]
    Unpermitted parameter: ingredient_attributes
       (0.1ms)  begin transaction
      Quantity Load (0.3ms)  SELECT "quantities".* FROM "quantities" WHERE "quantities"."recipe_id" = ? AND "quantities"."id" IN (63, 64)  [["recipe_id", "1"]]
      SQL (0.4ms)  INSERT INTO "quantities" ("amount", "recipe_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["amount", 11], ["recipe_id", "1"], ["created_at", "2015-12-26 03:00:14.818224"], ["updated_at", "2015-12-26 03:00:14.818224"]]
       (9.6ms)  commit transaction
    Redirected to https://cocoontest-mirv.c9users.io/recipes/1
    Completed 302 Found in 19ms (ActiveRecord: 10.7ms)

设置

型号:

class Recipe < ActiveRecord::Base
  has_many :quantities
  has_many :ingredient,
           :through => :quantities
  accepts_nested_attributes_for :quantities,
           :allow_destroy => true
  accepts_nested_attributes_for :ingredient
end

class Quantity < ActiveRecord::Base
  belongs_to :recipe
  belongs_to :ingredient
  accepts_nested_attributes_for :ingredient
end

class Ingredient < ActiveRecord::Base
  has_many :quantities
  has_many :recipes, through: :quantities
  accepts_nested_attributes_for :quantities
end

控制器:更新 @ 08:29pm - 12/25/15

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

  # GET /recipes
  # GET /recipes.json
  def index
    @recipes = Recipe.all
  end

  # GET /recipes/1
  # GET /recipes/1.json
  def show
    @recipe = Recipe.find(params[:id])

  end

  # GET /recipes/new
  def new
    @recipe = Recipe.new
  end

  # GET /recipes/1/edit
  def edit
        @recipe = Recipe.find(params[:id])
  end

  # POST /recipes
  # POST /recipes.json
  def create
    @recipe = Recipe.new(recipe_params)

    respond_to do |format|
      if @recipe.save
        format.html { redirect_to @recipe, notice: 'Recipe was successfully created.' }
        format.json { render :show, status: :created, location: @recipe }
      else
        format.html { render :new }
        format.json { render json: @recipe.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /recipes/1
  # PATCH/PUT /recipes/1.json
  def update
    respond_to do |format|
      if @recipe.update(recipe_params)
        format.html { redirect_to @recipe, notice: 'Recipe was successfully updated.' }
        format.json { render :show, status: :ok, location: @recipe }
      else
        format.html { render :edit }
        format.json { render json: @recipe.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /recipes/1
  # DELETE /recipes/1.json
  def destroy
    @recipe.destroy
    respond_to do |format|
      format.html { redirect_to recipes_url, notice: 'Recipe was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_recipe
      @recipe = Recipe.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def recipe_params
      #seems to work, but used wrong pluralization for quantity, should have been quantities_attributes
      params.require(:recipe).permit(:title, :description, :instruction, 
      :quantities_attributes => [:id, :ingredient, :ingredient_id, :recipe_id, :amount, :_destroy], 
      :ingredient_attributes => [:id, :_destroy, :ingredient_id, :name], 
      :ingredients_attributes => [:id, :_destroy, :ingredient_id, :name]
      )
    end
end

这是观点...来自 github 我在上面 linked 的部分...

edit.html.erb

<h1>Editing Recipe</h1>

    <%= render 'form' %>

    <%= link_to 'Show', @recipe %> |
    <%= link_to 'Back', recipes_path %>

_form.html.erb

<%= form_for @recipe, html: {class: "form-horizontal"} do |f| %>
  <% if @recipe.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@recipe.errors.count, "error") %> prohibited this recipe from being saved:</h2>

      <ul>
      <% @recipe.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <fieldset id="recipe-meta">
    <ol>
      <li class="control-group">
        <%= f.label :title, "Recipe Name", class: "control-label" %>
        <div class="controls"><%= f.text_field :title %></div>
      </li>
      <li class="control-group">
        <%= f.label :description, "A brief description of this recipe", class: "control-label" %>
        <div class="controls"><%= f.text_area :description, rows: 5 %></div>
      </li>
      <li class="control-group">
        <%= f.label :instruction, "Instructions for this recipe", class: "control-label" %>
        <div class="controls"><%= f.text_area :instruction, rows: 10 %></div>
      </li>
    </ol>
  </fieldset>

  <fieldset id="recipe-ingredients">
    <ol>
      <%= f.fields_for :quantities do |quantity| %>
        <%= render 'quantity_fields', f: quantity %>
      <% end %>
    </ol>
    <%= link_to_add_association 'add ingredient', f, :quantities, 'data-association-insertion-node' => "#recipe-ingredients ol", 'data-association-insertion-method' => "append", :wrap_object => Proc.new {|quantity| quantity.build_ingredient; quantity } %>
  </fieldset>
    <%= f.submit %>
  </div>
<% end %>

_quantity_fields.html.erb

<li class="control-group nested-fields">
  <div class="controls">
    <%= f.label :amount, "Amount:" %>
    <%= f.text_field :amount %>

    <%= f.fields_for :ingredient do |quantity_ingredient| %>
      <%= quantity_ingredient.text_field :name %>
    <% end %>

    <%= link_to_remove_association "remove", f %>
  </div>
</li>

架构

ActiveRecord::Schema.define(version: 20151206204139) do

  create_table "ingredients", force: :cascade do |t|
    t.text     "name"
    t.datetime "created_at",    null: false
    t.datetime "updated_at",    null: false
    t.integer  "ingredient_id"
  end

  create_table "quantities", force: :cascade do |t|
    t.integer  "amount"
    t.text     "ingredient"
    t.datetime "created_at",    null: false
    t.datetime "updated_at",    null: false
    t.string   "recipe_id"
    t.integer  "ingredient_id"
  end

  add_index "quantities", ["recipe_id"], name: "index_quantities_on_recipe_id"

  create_table "recipes", force: :cascade do |t|
    t.string   "title"
    t.text     "description"
    t.text     "instruction"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
    t.string   "recipe_id"
  end

  add_index "recipes", ["recipe_id"], name: "index_recipes_on_recipe_id"

end

更新:包括查看编辑页面的源代码...这是目前为止项目产生的html页面...

<!DOCTYPE html>
<html>
<head>
  <title>Workspace</title>
  <link rel="stylesheet" media="all" href="/assets/ingredients.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css?body=1" data-turbolinks-track="true" />
<link rel="stylesheet" media="all" href="/assets/quantities.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css?body=1" data-turbolinks-track="true" />
<link rel="stylesheet" media="all" href="/assets/recipes.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css?body=1" data-turbolinks-track="true" />
<link rel="stylesheet" media="all" href="/assets/scaffolds.self-d2e5ccad1506299feea3a35bfb7c525e101bb3b214303715deb675fdc539948b.css?body=1" data-turbolinks-track="true" />
<link rel="stylesheet" media="all" href="/assets/application.self-e80e8f2318043e8af94dddc2adad5a4f09739a8ebb323b3ab31cd71d45fd9113.css?body=1" data-turbolinks-track="true" />
  <script src="/assets/jquery.self-a714331225dda820228db323939889f149aec0127aeb06255646b616ba1ca419.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/jquery_ujs.self-d456baa54c1fa6be2ec3711f0a72ddf7a5b2f34a6b4f515f33767d6207b7d4b3.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/turbolinks.self-c37727e9bd6b2735da5c311aa83fead54ed0be6cc8bd9a65309e9c5abe2cbfff.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/ingredients.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/quantities.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/recipes.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/cocoon.self-05633fd25797688a657c08b26b8d4217031ee589f5ca76f0a57c11ac7d0e76ec.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/application.self-7862a8a8b42407b4741a1adeeea35f0d13ddc4f702ec532adb0674491d296495.js?body=1" data-turbolinks-track="true"></script>
  <meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="Yco8LpQtKNyieVhjN1HKDzyMMyMfVHJ+UpllUl7iR88uIJkvf9VD36yiLGFyyS2YD6rpf0zxL/b5zpp2q7AYFA==" />
</head>
<body>

<h1>Editing Recipe</h1>

<form class="form-horizontal" id="edit_recipe_2" action="/recipes/2" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="_method" value="patch" /><input type="hidden" name="authenticity_token" value="DxvtQX6V7LtVoez4faEXT5L/t2vOORoprujYH4IXqJFA8UhAlW2HuFt6mPo4OfDYodltN52cR6EFvyc7d0X3Sg==" />

  <fieldset id="recipe-meta">
    <ol>
      <li class="control-group">
        <label class="control-label" for="recipe_title">Recipe Name</label>
        <div class="controls"><input type="text" value="Test2" name="recipe[title]" id="recipe_title" /></div>
      </li>
      <li class="control-group">
        <label class="control-label" for="recipe_description">A brief description of this recipe</label>
        <div class="controls"><textarea rows="5" name="recipe[description]" id="recipe_description">
Test2</textarea></div>
      </li>
      <li class="control-group">
        <label class="control-label" for="recipe_instruction">Instructions for this recipe</label>
        <div class="controls"><textarea rows="10" name="recipe[instruction]" id="recipe_instruction">
Test2</textarea></div>
      </li>
    </ol>
  </fieldset>

  <fieldset id="recipe-ingredients">
    <ol>

        <li class="control-group nested-fields">
  <div class="controls">
    <label for="recipe_quantities_attributes_0_amount">Amount:</label>
    <input type="text" value="111" name="recipe[quantities_attributes][0][amount]" id="recipe_quantities_attributes_0_amount" />


    <input type="hidden" name="recipe[quantities_attributes][0][_destroy]" id="recipe_quantities_attributes_0__destroy" value="false" /><a class="remove_fields existing" href="#">remove</a>
  </div>
</li>
<input type="hidden" value="38" name="recipe[quantities_attributes][0][id]" id="recipe_quantities_attributes_0_id" />
        <li class="control-group nested-fields">
  <div class="controls">
    <label for="recipe_quantities_attributes_1_amount">Amount:</label>
    <input type="text" value="111" name="recipe[quantities_attributes][1][amount]" id="recipe_quantities_attributes_1_amount" />


    <input type="hidden" name="recipe[quantities_attributes][1][_destroy]" id="recipe_quantities_attributes_1__destroy" value="false" /><a class="remove_fields existing" href="#">remove</a>
  </div>
</li>
<input type="hidden" value="39" name="recipe[quantities_attributes][1][id]" id="recipe_quantities_attributes_1_id" />
        <li class="control-group nested-fields">
  <div class="controls">
    <label for="recipe_quantities_attributes_2_amount">Amount:</label>
    <input type="text" value="1111" name="recipe[quantities_attributes][2][amount]" id="recipe_quantities_attributes_2_amount" />


    <input type="hidden" name="recipe[quantities_attributes][2][_destroy]" id="recipe_quantities_attributes_2__destroy" value="false" /><a class="remove_fields existing" href="#">remove</a>
  </div>
</li>
<input type="hidden" value="40" name="recipe[quantities_attributes][2][id]" id="recipe_quantities_attributes_2_id" />
        <li class="control-group nested-fields">
  <div class="controls">
    <label for="recipe_quantities_attributes_3_amount">Amount:</label>
    <input type="text" value="2222" name="recipe[quantities_attributes][3][amount]" id="recipe_quantities_attributes_3_amount" />


    <input type="hidden" name="recipe[quantities_attributes][3][_destroy]" id="recipe_quantities_attributes_3__destroy" value="false" /><a class="remove_fields existing" href="#">remove</a>
  </div>
</li>
<input type="hidden" value="41" name="recipe[quantities_attributes][3][id]" id="recipe_quantities_attributes_3_id" />
        <li class="control-group nested-fields">
  <div class="controls">
    <label for="recipe_quantities_attributes_4_amount">Amount:</label>
    <input type="text" value="33" name="recipe[quantities_attributes][4][amount]" id="recipe_quantities_attributes_4_amount" />


    <input type="hidden" name="recipe[quantities_attributes][4][_destroy]" id="recipe_quantities_attributes_4__destroy" value="false" /><a class="remove_fields existing" href="#">remove</a>
  </div>
</li>
<input type="hidden" value="42" name="recipe[quantities_attributes][4][id]" id="recipe_quantities_attributes_4_id" />
        <li class="control-group nested-fields">
  <div class="controls">
    <label for="recipe_quantities_attributes_5_amount">Amount:</label>
    <input type="text" value="444" name="recipe[quantities_attributes][5][amount]" id="recipe_quantities_attributes_5_amount" />


    <input type="hidden" name="recipe[quantities_attributes][5][_destroy]" id="recipe_quantities_attributes_5__destroy" value="false" /><a class="remove_fields existing" href="#">remove</a>
  </div>
</li>
<input type="hidden" value="43" name="recipe[quantities_attributes][5][id]" id="recipe_quantities_attributes_5_id" />    </ol>
    <a data-association-insertion-node="#recipe-ingredients ol" data-association-insertion-method="append" class="add_fields" data-association="quantity" data-associations="quantities" data-association-insertion-template="&lt;li class=&quot;control-group nested-fields&quot;&gt;
  &lt;div class=&quot;controls&quot;&gt;
    &lt;label for=&quot;recipe_quantities_attributes_new_quantities_amount&quot;&gt;Amount:&lt;/label&gt;
    &lt;input type=&quot;text&quot; name=&quot;recipe[quantities_attributes][new_quantities][amount]&quot; id=&quot;recipe_quantities_attributes_new_quantities_amount&quot; /&gt;


      &lt;input type=&quot;text&quot; name=&quot;recipe[quantities_attributes][new_quantities][ingredient_attributes][name]&quot; id=&quot;recipe_quantities_attributes_new_quantities_ingredient_attributes_name&quot; /&gt;

    &lt;input type=&quot;hidden&quot; name=&quot;recipe[quantities_attributes][new_quantities][_destroy]&quot; id=&quot;recipe_quantities_attributes_new_quantities__destroy&quot; value=&quot;false&quot; /&gt;&lt;a class=&quot;remove_fields dynamic&quot; href=&quot;#&quot;&gt;remove&lt;/a&gt;
  &lt;/div&gt;
&lt;/li&gt;" href="#">add ingredient</a>
  </fieldset>
    <input type="submit" name="commit" value="Update Recipe" />
  </div>
</form>

<a href="/recipes/2">Show</a> |
<a href="/recipes">Back</a>


</body>
</html>

您的 recipe_params 方法需要更改 - 您的表单中实际上有两层嵌套(配方 -> 数量 -> 成分),因此您的参数许可需要反映这一点。

在您的方法中,删除 ingredient_attributesingredients_attributes 部分 - 它们与您提交的参数的格式不匹配。相反,您需要如下内容:

:quantities_attributes => [:id, :ingredient, :ingredient_id, :recipe_id, :amount, 
  :_destroy, :ingredient_attributes => [:id, :_destroy, :ingredient_id, :name]]