[英]Rails 3, many-to-many form using accepts_nested_attributes_for, how do I set up correctly?
I have a many-to-many relationship between Recipes and Ingredients . 我在食谱和配料之间有多对多的关系。 I am trying to build a form that allows me to add an ingredient to a recipe . 我正在尝试构建一个允许我在配方中添加成分的表单。
(Variants of this question have been asked repeatedly, I have spent hours on this, but am fundamentally confused by what accepts_nested_attributes_for
does.) (这个问题的变体已被反复询问过,我已经花了几个小时的时间,但基本上对accepts_nested_attributes_for
作用感到困惑。)
Before you get scared by all the code below I hope you'll see it's really a basic question. 在您对以下所有代码感到害怕之前,我希望您会发现它确实是一个基本问题。 Here are the non-scary details... 这是非恐怖的细节......
When I display a form to create a recipe, I am getting the error "uninitialized constant Recipe::IngredientsRecipe", pointing to a line in my form partial 当我显示一个表单来创建一个食谱时,我收到错误“未初始化的常量Recipe :: IngredientsRecipe”,指向我的表单中的一行
18: <%= f.fields_for :ingredients do |i| %>
If I change this line to make "ingredients" singular 如果我改变这一行,使“成分”单数
<%= f.fields_for :ingredient do |i| %>
then the form displays, but when I save I get a mass assignment error Can't mass-assign protected attributes: ingredient
. 然后表单显示,但是当我保存时,我得到一个质量分配错误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
As RESTful resources generated by rails generate scaffold
由rails generate scaffold
RESTful资源rails generate scaffold
inflections.rb
并且,因为复数的“配方”是不规则的, inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'recipe', 'recipes'
end
recipes/_form.html.erb
) 查看( 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 %>
If I change the view f.fields_for :ingredient
then the form loads (it finds Recipe::IngredientRecipe
correctly, but then when I save, I get a mass-assignment error as noted above. Here's the log 如果我更改视图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'
and the failing lines in the controller is simply 并且控制器中的故障线很简单
@recipe = Recipe.new(params[:recipe])
So the params being passed, including the nested attributes, are incorrect in some way. 因此传递的参数(包括嵌套属性)在某种程度上是不正确的。 But I have tried lots of variants that fix-one-break-another. 但我已经尝试了许多修复 - 一次破坏 - 另一种。 What am I failing to understand? 我什么不明白?
Thanks to clues from all, I have found what was wrong with my approach. 感谢所有人的线索,我发现我的方法出了什么问题。 Here's how I solved it. 这是我解决它的方式。
I had originally tried with a simple HABTM many-to-many relationship, where the join table was named following standard Rails convention: ingredients_recipes
. 我最初尝试使用简单的HABTM多对多关系,其中连接表是按照标准Rails约定命名的: ingredients_recipes
。 Then I realized that in a way, accepts_nested_attributes_for
is designed for a 1-to-many relationship. 然后我意识到, accepts_nested_attributes_for
在某种程度上是针对1对多关系而设计的。 So I converted to using has_many_through
, creating a model IngredientsRecipes
. 所以我转换为使用has_many_through
,创建了一个模型IngredientsRecipes
。
That name was the core problem, because Rails needs to be able to convert from plural to singular when using build
to create form elements. 这个名称是核心问题,因为当使用build
来创建表单元素时,Rails需要能够从复数转换为单数。 This caused it to look for the non-existant class Recipe::IngredientsRecipe
. 这导致它寻找不存在的类Recipe::IngredientsRecipe
。 When I changed my form so it used fields_for :ingredient
the form displayed, but still failed to save with a mass assignment error. 当我更改了我的表单,因此它使用了fields_for :ingredient
显示的表单,但仍然无法通过质量分配错误进行保存。 It even failed when I added :ingredients_attributes
to attr_accessible
. 当我添加:ingredients_attributes
到attr_accessible
时,它甚至失败了。 It still failed when I added @recipe.ingredients.build
to RecipesController#new
. 当我将@recipe.ingredients.build
添加到RecipesController#new
时,它仍然失败了。
Changing the model to a singular form was the final key to resolve the problem. 将模型更改为单数形式是解决问题的最终关键。 IngredientsRecipe
would have worked, but I chose RecipeIngredients
, as it makes more sense. IngredientsRecipe
本来RecipeIngredients
,但我选择了RecipeIngredients
,因为它更有意义。
So to summarize: 总结一下:
accepts_nested_attributes_for
with has_and_belongs_to_many
; 不能使用accepts_nested_attributes_for
与has_and_belongs_to_many
; need has_many
with through
option. 需要has_many
with through
选项。 (Thanks @kien_thanh) (谢谢@kien_thanh) accepts_nested_attributes_for
creates a accessor that must be added to attr_accessible
in the form <plural-foreign-model>_attributes
, eg in Recipe
I added attr_accessible :name, :ingredients_attributes
(Thanks @beerlington) 添加accepts_nested_attributes_for
创建一个访问者,必须以<plural-foreign-model>_attributes
的形式添加到attr_accessible
,例如在Recipe
I中添加了attr_accessible :name, :ingredients_attributes
(Thanks @beerlington) new
method of the controller, must call build
on the foreign model after creating a new instance, as in 3.times { @recipe.ingredients.build }
. 在控制器的new
方法中显示表单之前,必须在创建新实例后调用外部模型上的build
,如3.times { @recipe.ingredients.build }
。 This results in HTML having names like recipe[ingredients_attributes][0][name]
(Thanks @bravenewweb) 这导致HTML的名称如recipe[ingredients_attributes][0][name]
(Thanks @bravenewweb) If you inspect the form that is generated, you'll notice that the nested fields have a name like "ingredients_attributes". 如果检查生成的表单,您会注意到嵌套字段的名称类似于“ingredients_attributes”。 The reason you're getting the mass-assignment error is because you need to add these fields to the attr_accessible
declaration. 您获得质量分配错误的原因是您需要将这些字段添加到attr_accessible
声明中。
Something like this should fix it (you'll need to doublecheck the field names): 这样的东西应该修复它(你需要重新检查字段名称):
class Recipe < ActiveRecord::Base
attr_accessible :name, :ingredients_attributes
#...
end
Update : There's a similar answer here 更新 :这里有类似的答案
Leave the call as 保持呼叫为
<%= f.fields_for :ingredients do |i| %>
But before that do 但在此之前
<% @recipe.ingredients.build %>
Im guessing that will allow your form to be created the right way, but there are likely other errors with your models, I can look @ it more in detail when I have more time if its still not working, but: 我猜这将允许你的表单以正确的方式创建,但你的模型可能还有其他错误,如果我有更多的时间,我可以更详细地看看它,如果它还没有工作,但是:
As far as what accepts_nested_attributes_for does, when you pass in a correctly formatted params hash to the Model.new or Model.create or Model.update, it allows those attributes on the related model to be saved if they are in the params hash. 至于accepts_nested_attributes_for的作用,当您将格式正确的params散列传递给Model.new或Model.create或Model.update时,它允许保存相关模型上的那些属性(如果它们在params散列中)。 In addition though, you do need to make the attributes accessible if they are unaccessible in the parent model as stated by beerlington. 此外,如果在beerlington所述的父模型中无法访问属性,则需要使属性可访问。
I think you just need set up a one-to-many association, one recipe has many ingredients and one ingredient belongs to one recipe, so your model look like: 我认为你只需要建立一对多的关联,一个食谱有很多成分,一个成分属于一个食谱,所以你的模型看起来像:
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
You are built right form, so I don't write it again here. 你是正确的形式,所以我不在这里再写。 Now in your new and create controller will be like this: 现在在你的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
How does params[:recipe]
look like? params[:recipe]
看起来怎么样? If you just have one select field, maybe like this: 如果您只有一个选择字段,可能是这样的:
params = { recipe: { name: "Stewed Tomatoes", ingredients_attributes: [ { id: 1 } ] } }
If you have 2 ingredient select field: 如果您有2个成分选择字段:
params = { recipe: { name: "Stewed Tomatoes", ingredients_attributes: [ { id: 1 }, { id: 2 } ] } }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.