繁体   English   中英

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

[英]Rails 3, many-to-many form using accepts_nested_attributes_for, how do I set up correctly?

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

(这个问题的变体已被反复询问过,我已经花了几个小时的时间,但基本上对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

模型(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

控制器

rails generate scaffold RESTful资源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 %>

环境

  • 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'

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

@recipe = Recipe.new(params[:recipe])

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

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

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

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

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

总结一下:

  • 不能使用accepts_nested_attributes_forhas_and_belongs_to_many ; 需要has_many with through选项。 (谢谢@kien_thanh)
  • 添加accepts_nested_attributes_for创建一个访问者,必须以<plural-foreign-model>_attributes的形式添加到attr_accessible ,例如在Recipe I中添加了attr_accessible :name, :ingredients_attributes (Thanks @beerlington)
  • 在控制器的new方法中显示表单之前,必须在创建新实例后调用外部模型上的build ,如3.times { @recipe.ingredients.build } 这导致HTML的名称如recipe[ingredients_attributes][0][name] (Thanks @bravenewweb)
  • 连接模型必须是单一的,与所有模型一样。 (所有我:-)。

如果检查生成的表单,您会注意到嵌套字段的名称类似于“ingredients_attributes”。 您获得质量分配错误的原因是您需要将这些字段添加到attr_accessible声明中。

这样的东西应该修复它(你需要重新检查字段名称):

class Recipe < ActiveRecord::Base
  attr_accessible :name, :ingredients_attributes
  #...
end

更新 :这里有类似的答案

保持呼叫为

<%= f.fields_for :ingredients do |i| %>

但在此之前

<% @recipe.ingredients.build %>

我猜这将允许你的表单以正确的方式创建,但你的模型可能还有其他错误,如果我有更多的时间,我可以更详细地看看它,如果它还没有工作,但是:

至于accepts_nested_attributes_for的作用,当您将格式正确的params散列传递给Model.new或Model.create或Model.update时,它允许保存相关模型上的那些属性(如果它们在params散列中)。 此外,如果在beerlington所述的父模型中无法访问属性,则需要使属性可访问。

我认为你只需要建立一对多的关联,一个食谱有很多成分,一个成分属于一个食谱,所以你的模型看起来像:

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 } ] } }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM