4

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

(这个问题的变体已经被反复问过,我已经花了几个小时在这个问题上,但从根本上被什么弄糊涂了accepts_nested_attributes_for。)

在你被下面的所有代码吓到之前,我希望你会明白这确实是一个基本问题。以下是不可怕的细节......

错误

当我显示一个创建配方的表单时,我收到错误“未初始化的常量配方::IngredientsRecipe”,指向我的表单部分中的一行

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

如果我更改此行以使“成分”单数

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

然后显示表单,但是当我保存时,我得到一个 mass assignment error 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

控制器

作为 RESTful 资源生成的rails generate scaffold

而且,因为“recipe”的复数形式不规则,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 %>

环境

  • 导轨 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])

所以传递的参数,包括嵌套属性,在某种程度上是不正确的。但是我已经尝试了很多变体,它们可以修复一个又一个。我不明白什么?

4

4 回答 4

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调用外部模型,如. 这导致 HTML 的名称类似于(感谢@bravenewweb)build3.times { @recipe.ingredients.build }recipe[ingredients_attributes][0][name]
  • 与所有模型一样,连接模型必须是单数的。(全是我:-)。
于 2012-11-21T16:10:14.157 回答
4

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

像这样的东西应该可以解决它(您需要仔细检查字段名称):

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

更新:这里有一个类似的答案

于 2012-11-20T22:21:53.617 回答
1

将通话保留为

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

但在那之前做

<% @recipe.ingredients.build %>

我猜这将允许以正确的方式创建您的表单,但是您的模型可能存在其他错误,如果它仍然无法正常工作,我可以在有更多时间时更详细地查看@它,但是:

就 accept_nested_attributes_for 所做的而言,当您将格式正确的 params 哈希传递给 Model.new 或 Model.create 或 Model.update 时,如果相关模型上的这些属性位于 params 哈希中,则它允许保存这些属性。此外,如果如 beerlington 所述,如果它们在父模型中不可访问,则您确实需要使属性可访问。

于 2012-11-21T02:34:49.987 回答
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

你是正确的形式,所以我不再在这里写了。现在在您的新和创建控制器中将是这样的:

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 } ] } }
于 2012-11-21T04:41:06.187 回答