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

Tom*_*son 4 activerecord ruby-on-rails has-many-through nested-attributes ruby-on-rails-3

我在食谱配料之间有多对多的关系.我正在尝试构建一个允许我在配方中添加成分的表单.

(这个问题的变体已被反复询问,我花了好几个小时,但基本上对于accepts_nested_attributes_for它的含义感到困惑.)

在您对以下所有代码感到害怕之前,我希望您会发现它确实是一个基本问题.这是非恐怖的细节......

错误

当我显示一个表单来创建一个食谱时,我收到错误"未初始化的常量Recipe :: IngredientsRecipe",指向我的表单中的一行

18:   <%= f.fields_for :ingredients do |i| %>
Run Code Online (Sandbox Code Playgroud)

如果我改变这一行,使"成分"单数

<%= f.fields_for :ingredient do |i| %>
Run Code Online (Sandbox Code Playgroud)

然后表单显示,但是当我保存时,我得到一个质量分配错误Can't mass-assign protected attributes: ingredient.

模型(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
Run Code Online (Sandbox Code Playgroud)

控制器

作为RESTful资源生成的 rails generate scaffold

并且,因为复数的"食谱"是不规则的, inflections.rb

ActiveSupport::Inflector.inflections do |inflect|
    inflect.irregular 'recipe', 'recipes'
end
Run Code Online (Sandbox Code Playgroud)

查看(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 %>
Run Code Online (Sandbox Code Playgroud)

环境

  • Rails 3.2.9
  • 红宝石1.9.3

有些事尝试过

如果我更改视图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'
Run Code Online (Sandbox Code Playgroud)

并且控制器中的故障线很简单

@recipe = Recipe.new(params[:recipe])
Run Code Online (Sandbox Code Playgroud)

因此传递的参数(包括嵌套属性)在某种程度上是不正确的.但我已经尝试了许多修复 - 一次破坏 - 另一种.我什么不明白?

Tom*_*son 8

感谢所有人的线索,我发现我的方法出了什么问题.这是我解决它的方式.

我最初尝试使用简单的HABTM多对多关系,其中连接表是按照标准Rails约定命名的:ingredients_recipes.然后我意识到,在某种程度上,accepts_nested_attributes_for它是为一对多的关系而设计的.所以我转换为使用has_many_through,创建了一个模型IngredientsRecipes.

这个名称是核心问题,因为在使用build创建表单元素时,Rails需要能够从复数转换为单数.这导致它寻找不存在的类Recipe::IngredientsRecipe.当我更改我的表单以便使用fields_for :ingredient显示的表单时,但仍然无法使用批量分配错误进行保存.当我加入时它甚至失败:ingredients_attributesattr_accessible.当我加入时,它仍然失败@recipe.ingredients.buildRecipesController#new.

将模型更改为单数形式是解决问题的最终关键. IngredientsRecipe会有用,但我选择了RecipeIngredients,因为它更有意义.

总结一下:

  • 不能使用accepts_nested_attributes_forhas_and_belongs_to_many; 需要has_manythrough选择.(谢谢@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)
  • 连接模型必须是单一的,与所有模型一样.(所有我:-).