【Rails】中間テーブルの一括保存方法

はじめに

こんにちは、株式会社TOKOSの石津です!
今回はRailsで中間テーブルを保存する方法について説明していきます。

Railsでアプリ開発をしていると親要素のみの保存や子要素の保存以外に多対多の中間テーブルの一括を保存する必要がでてくる場面があると思います。
初心者の方は複数のテーブルを一括で保存する方法が分からずループ文を書いてしまう事もあるかと思います。
しかし、今回の方法を使えば複雑なコードを書く必要がなく中間テーブルの一括方法を簡単に実装できます。
中間テーブルの一括保存はアプリ開発においてはよく出てくるのでその実装方法をしらない初心者から中級者の方に参考になると嬉しいです。

対象読者

  • railsの中間テーブルの一括保存方法を知りたい人
  • controllerでのparamsの書き方が分からない人

実装するモデル間のER図

今回は分かりやすいようにproductテーブル、categoryテーブル、products_categoryテーブル(中間テーブル)のモデルを用いて説明しています。

productは複数のcategoryを持ち、categoryもまた複数のproductを持つように実装しています。
多対多の実装ですので中間テーブルであるproducts_categoryにこの情報を保存していきます。
もちろん一括保存されるテーブルはもちろんproducts_category テーブルです。

productテーブルとcategoryテーブルのnameカラムは必要ないですが、なんとなく書いておきます。
products_categoryテーブルは中間テーブルなので親テーブルとなるproductテーブルのidであるproduct_idとcategoryテーブルのidであるcategory_idが必要になってきます。

ここまで準備できたら次はモデルファイルにリレーションを書いていきます。

リレーションをモデルファイルに書く

中間テーブルの一括保存にはモデルファイルにリレーションを書いていく必要があります。
まずはproduct.rbにcategoryテーブルとproducts_categoryテーブルとの関係性を書きます。
以下のように書いてください。

class Product < ApplicationRecord
  has_many :product_categories
  has_many :categories, through: :product_categories
end

productは複数のproducts_categoryを持つのでhas_many: product_categoriesと書きます。

次にproductは複数のcategoryを持つのですがproducts_categoryを介して持つことになるので has_many :categories, through: :product_categoriesと書きます。
このように書くことでRails側でproductがproducts_categoryを中間テーブルとしてcategoryを複数持つことを認識します。

products_category.rbにもリレーションを書いていきます。
こちらのリレーションは以下の通りです。

class ProductsCategory < ApplicationRecord
  belongs_to :product
  belongs_to :category
end

このリレーションの書き方は通常の書き方と変わらないと思うので説明は省略します。

category.rbにもproductと同様にリレーションを記述していきます。

class Category < ApplicationRecord
  has_many :product_categories
  has_many :products, through: :product_categories
end

URI Patternを見るとmemberで作った場合はplayerとcsv_exportの間にidがあるのが分かると思います。

中間テーブルの保存ロジックをコントローラーに書く

モデルにリレーションを記述した後は、実際にデータが送られてくるコントローラーに中間テーブルを一括保存するコードを書いていきます。

今回はcreate関数で中間テーブルの一括保存を実装していきます。
以下がcreate関数のコードになります。

class ProductsController < ApplicationController
  def create
    @product = Product.new(product_params)
    if @product.save
      redirect_to product_path(@product), notice: 'Product create!!!'
    else
      render :new
    end
  end
end

お気づきの通りcreate関数の記述方法は通常の保存方法と変わりません。
変更していくのはproduct_paramsの中身になります。
ほとんどの方は保存するデータはprivateメソッドに切り出していると思います。
中間テーブルの一括保存ではこのコードを変更していきます。

今回はcategoryのidと該当productのidとの中間テーブルをそれぞれ作る必要があります。
大変そうですが、product_paramsの中身を少し変えるだけで自動的に全ての中間テーブルを作成してくれます。product_paramsを以下のように変更してください。

class ProductsController < ApplicationController
  def create
    @product = Product.new(product_params)
    if @product.save
      redirect_to product_path(@product), notice: 'Product create!!!'
    else
      render :new
    end
  end

 private

 def 

  def product_params
    params.require(:product).permit(:name, category_ids: [])
  end
end

上記のように, category_ids: []をproduct_paramsに追加するだけでコントローラーに渡ってくるidの配列との中間テーブルを全て作成してくれます。

非常に便利ですね!

おわりに

今回の記事ではRailsで中間テーブルを一括で保存する方法を説明しました。

Viewでは、f.collectionを用いてidの配列をコントローラー側に渡しました。
railsのAPIモードを使って開発をしている方は、コントローラーにidの配列が渡るように調整してください。

皆さんの参考になったら嬉しいです!