1つのフォームから複数のテーブルへ複数のレコード保存の実装


目的

Railsで1回のフォーム送信で2つのテーブルにデータを登録しつつ、片方のテーブルには複数のレコードにデータを保存したい。

自身が行った実装方法

  1. フォームオブジェクトパターンの実装で2つのテーブルへ保存できるようにする
  2. 複数のレコードを作成したいフォームのパラメーターを配列で送られてくるようにする
  3. 2で送られてきたパラメーターを変形して要素の数だけcreateメソッドで保存

※2と3について記録致します。

フォームのある.html
      <div class="ingredient">
        <div class="ingredient-name">
          <%= f.label :name, "材料名" %>
          <%= f.text_field :name, name:"[recipe_ingredient][name][]" %>
        </div>

        <div class="ingredient-amount">
          <%= f.label :amount, "分量" %>
          <%= f.text_field :amount, name:"[recipe_ingredient][amount][]" %>
        </div>

        <div class="ingredient-unit">
          <%= f.label :unit_id, "単位" %>
          <%= f.collection_select(:unit_id, Unit.all, :id, :name, {}, {name:"[recipe_ingredient][unit_id][]"}) %>
        </div>

フォームの中に
name:"[recipe_ingredient][name][]
name:"[recipe_ingredient][amount][]
name:"[recipe_ingredient][unit_id][]
と記述することで

"recipe_ingredient"=>{"name"=>["豚バラ", "卵"], "amount"=>["300", "2"], "unit_id"=>["2", "3"]}

と配列に入って送られてきます。(上記は一部切り取ったパラメーターです)

フォームオブジェクトパターン用のmodel.rb
class RecipeIngredient
  include ActiveModel::Model
  attr_accessor :title, :source, :user_id, :name, :amount, :unit_id, :recipe

  with_options presence: true do
    validates :title
    validates :user_id
  end

  def save
    recipe  = Recipe.create(title: title, source: source, user_id: user_id)

    ingredients = [name, amount, unit_id].transpose
    
    ingredients.each do |ingredient|
      header = ["name", "amount", "unit_id"]
      ingredient_parameter = Hash[header.zip(ingredient)]
      ingredient_parameter["recipe_id"] = recipe.id
      Ingredient.create(ingredient_parameter)
    end
  end
end

送られてきた

"recipe_ingredient"=>{"name"=>["豚バラ", "卵"], "amount"=>["300", "2"], "unit_id"=>["2", "3"]}

をsaveメソッドの2行目の
ingredients = [name, amount, unit_id].transposeで

[["豚バラ", "300", "2"], ["卵", "2", "3"]]

に変形させeach文で配列の回数繰り返し処理を行い
header = ["name", "amount", "unit_id"]で保存したいカラム名を入れ
ingredient_parameter = Hash[header.zip(ingredient)]で

{"name"=>"豚バラ", "amount"=>"300", "unit_id"=>"2"}{"name"=>"卵", "amount"=>"2", "unit_id"=>"3"}

に変形してそれぞれcreateの引数に渡して保存しています。
こうすることで一度の送信で複数のレコードを保存する事ができました。

終わりに

思い描く実装の記事には出会えなかったので試行錯誤の中、とりあえず形にはなりましたが、正しい実装方法なのかは分かりません。
改善点等あれば指摘していただければ幸いです。
初めての投稿で読みにくい箇所があるかもしれませんがお許し下さい・・・汗
もしこの投稿が誰かのためになれば幸いです。