简体   繁体   English

Rails 3,多对多形式使用accepts_nested_attributes_for,如何正确设置?

[英]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... 这是非恐怖的细节......

Errors 错误

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

Models (in 3 files, named accordingly) 模型(3个文件,相应命名)

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

Controllers 控制器

As RESTful resources generated by rails generate scaffold rails generate scaffold RESTful资源rails generate scaffold

And, because the plural of "recipe" is irregular, inflections.rb 并且,因为复数的“配方”是不规则的, inflections.rb

ActiveSupport::Inflector.inflections do |inflect|
    inflect.irregular 'recipe', 'recipes'
end

View ( 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 %>

Environment 环境

  • Rails 3.2.9 Rails 3.2.9
  • ruby 1.9.3 红宝石1.9.3

Some things tried 有些事尝试过

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_attributesattr_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: 总结一下:

  • can't use accepts_nested_attributes_for with has_and_belongs_to_many ; 不能使用accepts_nested_attributes_forhas_and_belongs_to_many ; need has_many with through option. 需要has_many with through选项。 (Thanks @kien_thanh) (谢谢@kien_thanh)
  • adding 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)
  • before displaying the form in the 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)
  • join model must be singular, as with all models. 连接模型必须是单一的,与所有模型一样。 (All me :-). (所有我:-)。

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.

相关问题 Rails如何将has_many_and_belongs_to_many与accepts_nested_attributes_for一起使用 - Rails How to use has_many_and_belongs_to_many with accepts_nested_attributes_for Rails,accepts_nested_attributes_for has_many:通过构建 - Rails, accepts_nested_attributes_for has_many: through with build Rails has_many / accepts_nested_attributes_for造成混乱吗? - Rails has_many/accepts_nested_attributes_for Create confusion? Rails 3具有has_many的accepts_nested_attributes_for:through关系 - Rails 3 accepts_nested_attributes_for with has_many :through relationships 在Rails中,如何使用accepts_nested_attributes_for创建嵌套对象? - In Rails, how do I create nested objects using accepts_nested_attributes_for? Rails 6 使用 has_many 关系和 accepts_nested_attributes_for 连接表 - Rails 6 Joins Tables using has_many relationships and accepts_nested_attributes_for Rails:在嵌套 Model 表单中使用 Accepts_nested_attributes_for - Rails: Using accepts_nested_attributes_for in Nested Model Form 在使用accepts_nested_attributes_for时,如何设置将拒绝现有记录的过滤器 - How do I set a filter that will reject existing records when using accepts_nested_attributes_for 使用accepts_nested_attributes_for时如何在联接模型上设置属性? - How do I set an attribute on the join model when using accepts_nested_attributes_for? 以单一形式使用has_many_through和accepts_nested_attributes_for - Using has_many_through and accepts_nested_attributes_for in a single form
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM