簡體   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