使用 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
我一直在尝试构建一个相当复杂的食谱对象,该对象应该具有不确定数量的成分("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