我在食谱和成分之间存在多对多的关系。我正在尝试构建一个允许我添加配方的表单。
(这个问题的变体已被反复询问,我花了好几个小时,但基本上对accepts_nested_attributes_for
的内容感到困惑。)
在您对以下所有代码感到害怕之前,我希望您会看到它真的是一个基本问题。以下是非恐怖的细节......
当我显示一个表单来创建一个食谱时,我收到错误“未初始化的常量Recipe :: IngredientsRecipe”,指向我的表单部分中的一行
18: <%= f.fields_for :ingredients do |i| %>
如果我更改此行以使“成分”单数
<%= f.fields_for :ingredient do |i| %>
然后显示表单,但是当我保存时,我收到了一个质量分配错误Can't mass-assign protected attributes: ingredient
。
class Recipe < ActiveRecord::Base
attr_accessible :name, :ingredient_id
has_many :ingredients, :through => :ingredients_recipes
has_many :ingredients_recipes
accepts_nested_attributes_for :ingredients
accepts_nested_attributes_for :ingredients_recipes
end
class Ingredient < ActiveRecord::Base
attr_accessible :name, :recipe_id
has_many :ingredients_recipes
has_many :recipes, :through => :ingredients_recipes
accepts_nested_attributes_for :recipes
accepts_nested_attributes_for :ingredients_recipes
end
class IngredientsRecipes < ActiveRecord::Base
belongs_to :ingredient
belongs_to :recipe
attr_accessible :ingredient_id, :recipe_id
accepts_nested_attributes_for :recipes
accepts_nested_attributes_for :ingredients
end
由rails generate scaffold
inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'recipe', 'recipes'
end
recipes/_form.html.erb
)<%= form_for(@recipe) do |f| %>
<div class="field">
<%= f.label :name, "Recipe" %><br />
<%= f.text_field :name %>
</div>
<%= f.fields_for :ingredients do |i| %>
<div class="field">
<%= i.label :name, "Ingredient" %><br />
<%= i.collection_select :ingredient_id, Ingredient.all, :id, :name %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
如果我更改了视图f.fields_for :ingredient
,则表单会加载(它会正确找到Recipe::IngredientRecipe
,但是当我保存时,我会收到如上所述的质量分配错误。这是日志
Started POST "/recipes" for 127.0.0.1 at 2012-11-20 16:50:37 -0500
Processing by RecipesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"/fMS6ua0atk7qcXwGy7NHQtuOnJqDzoW5P3uN9oHWT4=", "recipe"=>{"name"=>"Stewed Tomatoes", "ingredient"=>{"ingredient_id"=>"1"}}, "commit"=>"Create Recipe"}
Completed 500 Internal Server Error in 2ms
ActiveModel::MassAssignmentSecurity::Error (Can't mass-assign protected attributes: ingredient):
app/controllers/recipes_controller.rb:43:in `new'
app/controllers/recipes_controller.rb:43:in `create'
并且控制器中的失败行只是
@recipe = Recipe.new(params[:recipe])
因此传递的参数(包括嵌套属性)在某种程度上是不正确的。但我已经尝试了许多修复 - 一次破坏 - 另一种。我怎么不明白?
答案 0 :(得分:8)
感谢所有人提供的线索,我发现我的方法出了什么问题。这是我解决它的方式。
我最初尝试使用简单的HABTM多对多关系,其中连接表是按照标准Rails约定命名的:ingredients_recipes
。然后我意识到,在某种程度上,accepts_nested_attributes_for
是为一对多的关系而设计的。所以我转换为使用has_many_through
,创建了一个模型IngredientsRecipes
。
该名称是核心问题,因为在使用build
创建表单元素时,Rails需要能够从复数转换为单数。这导致它寻找不存在的类Recipe::IngredientsRecipe
。当我更改我的表单以便使用fields_for :ingredient
显示的表单时,但仍然无法使用批量分配错误进行保存。当我将:ingredients_attributes
添加到attr_accessible
时,它甚至失败了。当我将@recipe.ingredients.build
添加到RecipesController#new
时,它仍然失败。
将模型更改为单数形式是解决问题的最终关键。 IngredientsRecipe
会起作用,但我选择RecipeIngredients
,因为它更有意义。
总结一下:
accepts_nested_attributes_for
与has_and_belongs_to_many
一起使用;需要使用has_many
选项through
。 (谢谢@kien_thanh)accepts_nested_attributes_for
会创建一个必须以attr_accessible
形式添加到<plural-foreign-model>_attributes
的访问者,例如在Recipe
我添加了attr_accessible :name, :ingredients_attributes
(感谢@beerlington)new
方法中显示表单之前,必须在创建新实例后在外部模型上调用build
,如3.times { @recipe.ingredients.build }
中所示。这导致HTML的名称类似于recipe[ingredients_attributes][0][name]
(Thanks @bravenewweb)答案 1 :(得分:4)
如果检查生成的表单,您会注意到嵌套字段的名称类似于“ingredients_attributes”。您收到批量分配错误的原因是您需要将这些字段添加到attr_accessible
声明中。
这样的东西应该修复它(你需要重新检查字段名称):
class Recipe < ActiveRecord::Base
attr_accessible :name, :ingredients_attributes
#...
end
更新:有一个similar answer here
答案 2 :(得分:1)
将通话保留为
<%= f.fields_for :ingredients do |i| %>
但在此之前
<% @recipe.ingredients.build %>
我猜这样可以让你的表单以正确的方式创建,但是你的模型可能还有其他错误,如果我还有更多的时间,我可以更详细地看看它,但是:/ p>
对于accepts_nested_attributes_for所做的事情,当您将格式正确的params散列传递给Model.new或Model.create或Model.update时,它允许保存相关模型上的那些属性(如果它们在params中)哈希值。此外,如果在Beerlington所述的父模型中无法访问属性,则需要使属性可访问。
答案 3 :(得分:1)
我认为你只需要建立一对多的关联,一个食谱有很多成分,一个成分属于一个食谱,所以你的模型看起来像:
class Recipe < ActiveRecord::Base
attr_accessible :name, :ingredients_attributes
has_many :ingredients
accepts_nested_attributes_for :ingredients
end
class Ingredient < ActiveRecord::Base
attr_accessible :name, :recipe_id
belongs_to :recipe
end
你是正确的形式,所以我不在这里再写。现在在你的new和create控制器中将是这样的:
def new
@recipe = Recipe.new
# This is create just one select field on form
@recipe.ingredients.build
# Create two select field on form
2.times { @recipe.ingredients.build }
# If you keep code above for new method, now you create 3 select field
end
def create
@recipe = Recipe.new(params[:recipe])
if @recipe.save
...
else
...
end
end
params[:recipe]
怎么样?如果您只有一个选择字段,可能是这样的:
params = { recipe: { name: "Stewed Tomatoes", ingredients_attributes: [ { id: 1 } ] } }
如果您有2个成分选择字段:
params = { recipe: { name: "Stewed Tomatoes", ingredients_attributes: [ { id: 1 }, { id: 2 } ] } }