4

RecipesIngredientsの間に多対多の関係があります。レシピに材料を追加できるフォームを作成しようとしています。

(この質問のバリエーションが繰り返し尋ねられました。私はこれに何時間も費やしましたが、根本的に混乱してaccepts_nested_attributes_forいます。)

以下のすべてのコードに怯える前に、これが本当に基本的な質問であることを理解していただければ幸いです。ここに怖くない詳細があります...

エラー

レシピを作成するフォームを表示すると、「uninitialized constant 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

コントローラー

によって生成された 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 %>

環境

  • レール 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は 1 対多の関係用に設計されていることに気付きました。だから私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_forでは使用できませんhas_and_belongs_to_many。オプションhas_manyで必要です。through(ありがとう@kien_thanh)
  • 追加すると、フォームaccepts_nested_attributes_forに追加する必要があるアクセサーが作成されます。たとえば、追加しました(@beerlingtonに感謝します)attr_accessible<plural-foreign-model>_attributesRecipeattr_accessible :name, :ingredients_attributes
  • コントローラのメソッドでフォームを表示する前に、 のように、新しいインスタンスを作成した後に外部モデルを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 %>

フォームを正しい方法で作成できると思いますが、モデルに他のエラーがある可能性があります。まだ機能しない場合は、時間があれば、@を詳しく調べることができますが、次のようになります。

accepts_nested_attributes_forが行う限り、正しくフォーマットされたparamsハッシュをModel.new、Model.create、またはModel.updateに渡すと、関連するモデルの属性がparamsハッシュに含まれている場合にそれらを保存できます。さらに、beerlingtonが述べているように、親モデルで属性にアクセスできない場合は、属性にアクセスできるようにする必要があります。

于 2012-11-21T02:34:49.987 に答える
1

1 対多の関連付けを設定するだけでよいと思います。1 つのレシピには多くの材料があり、1 つの材料は 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]見えますか?選択フィールドが 1 つしかない場合は、次のようになります。

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 に答える