【问题标题】:Rails 3, many-to-many form using accepts_nested_attributes_for, how do I set up correctly?Rails 3,多对多形式使用accepts_nested_attributes_for,如何正确设置?
【发布时间】:2012-11-09 02:26:39
【问题描述】:

食谱成分之间存在多对多关系。我正在尝试构建一个表单,让我可以将成分添加到食谱中

(这个问题的变体已经被反复问过,我已经花了几个小时来解决这个问题,但我对 accepts_nested_attributes_for 所做的事情从根本上感到困惑。)

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

错误

当我显示一个创建配方的表单时,我收到错误“未初始化的常量配方::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资源

而且,因为“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 %>

环境

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

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

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 activerecord nested-attributes has-many-through


    【解决方案1】:

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

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

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

    更新:有similar answer here

    【讨论】:

    • 感谢您提供的线索,但也许我的大脑只是因为阅读和阅读文档而被弄糊涂了:-P - 当我检查向食谱添加成分的表单的 HTML 时,选择列表名为recipe[ingredient][id],它被解析为"recipe"=&gt;{"name"=&gt;"Stewed Tomatoes", "ingredient"=&gt;{"ingredient_id"=&gt;"1"}} 的参数。我知道这是错误的,但我不确定什么是正确的。 :ingredients_attributes 是成分模型的属性集合吗?我只是对 Rails 正在寻找的东西没有一个好的心智模型,甚至没有一个例子。谢谢。
    • 这是一个很大的线索,但只是部分答案,我找到了答案,将在下面添加。谢谢!!!
    【解决方案2】:

    留下电话

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

    但在那之前

    <% @recipe.ingredients.build %>
    

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

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

    【讨论】:

    • 谢谢!我现在正在努力创建最简单的案例,它可能从一对多案例开始(而不是我的多对多案例)。一旦我更深入地了解accepts_nested_attributes_for,我就不会像现在这样认为自己是个白痴了;-)
    【解决方案3】:

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

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

    【讨论】:

    • 谢谢!我很确定一对多的关系会起作用,除了我的数据不是这样建模的(例如,“西红柿”是“炖西红柿”和“马力拉酱”食谱中的一种成分;“马力拉酱”有“西红柿”和“大蒜”成分)。根据我在一对多案例中看到的命名,我开始认为 1)accepts_nested_attributes_for 不适用于 M-2-M 关系,或者 2)我还没有弄清楚如何:-)。完成后,我会写一篇博文,给出一个清晰而简单的答案,因为 Google 上没有nothing
    • 我相信accepts_nested_attributes_for只适用于一对一和一对多,就像API一样,它们也没有提到多对多关系。
    • 这样看,现在我正在弄清楚为什么,以及是否有办法让它发挥作用。关于 SO 的其他几个问题表明人们已经让它发挥作用。
    • 我想我不需要为多对多构建嵌套表单。基本上,如果您在两个模型中使用has_many, :through,它仍然是一对多关联。我认为这就是 Rails 没有为多对多关系提供 accepts_nested_attributes_for 的原因。
    • 我已经找到了问题的解决方案,并且对于 many_to_many 来说确实有效。见下文。
    【解决方案4】:

    感谢大家提供的线索,我发现我的方法有什么问题。以下是我的解决方法。

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

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

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

    总结一下:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多