Railsで多対多の関連のモデルを表現する方法
概要
Railsでウェブアプリケーションを作っていると、例えば記事に複数のユーザが属していて、さらにユーザにも複数の記事が属しているような、多対多の関係をデータベースで定義したいというときがあると思います。
このような多対多の関係を、Railsで表現する方法を紹介します。
環境
- Rails 5.1
前提
ここでは、記事を表すArticleモデルと、ユーザを表すUserモデルのふたつを多対多の関係で表現する方法を紹介します。
そのため、実際に利用する場合は、適切にモデル名などを書き換えてください。
ArticleモデルとUserモデルは次のコマンドで作成します。
rails generate model Article
rails generate model User
中間テーブル
多対多の関係を表す場合、中間テーブルというものを使います。
中間テーブルでは、記事とユーザのデータのIDが保存され、記事からユーザの情報をみるときには、中間テーブルに保存されている記事IDとユーザIDの情報を元にユーザの情報を見つけ出します。
ユーザから記事の情報をみるときも同様に、中間テーブルに保存されている記事IDとユーザIDの情報を元に記事の情報を見つけ出します。
では、中間テーブルを作りましょう。
中間テーブルとして、ArticleUserモデルを作成します。
rails generate model ArticleUser article:references user:references
Railsで、一般的に中間テーブルの名前は多対多の関連を作りたい2つのモデルの名前を繋げたものを使います。
ArticleUserという中間テーブルのモデルには、article:references
とuser:references
という多対多の関係を作るモデルの名前をカラム名とした参照を定義します。
referencesというものは見慣れないかもしれませんが、Railsが別のテーブルのデータを参照するために使用し、rails db:migrate
などを使うと、テーブルにはカラム名としてarticle_id
やuser_id
というように_id
をつけて定義されます。
ArticleUserモデルのソースコードを見ると、次のようになっていると思います。
class ArticleUser < ApplicationRecord
belongs_to :article
belongs_to :user
end
belongs_to
は、どのモデルに属するかを指定します。
ArticleモデルとUserモデルの2つの関連を表すために、belongs_to
をふたつ書きます。
モデルを用意したら、rails db:migrate
などを実行してデータベースへ反映させます。
モデルの変更
次に、既存のArticleモデルやUserモデルに設定を追加して、それぞれのモデルを参照できるようにします。
Articleモデルは次のようにします。
class Article < ApplicationRecord
has_many :article_users, dependent: :destroy
has_many :users, through: :article_users
end
Userモデルは次のようにします。
class User < ApplicationRecord
has_many :article_users, dependent: :destroy
has_many :articles, through: :article_users
end
has_manyというものをモデルのなかで使って、モデル同士の関連を表現します。
まずどちらのモデルでも、has_many :article_users, dependent: :destroy
というものを書いています。
これは、Articleモデル、UserモデルともにArticleUserモデルの情報を複数持っているということを表します。複数持っているということから、:article_users
のように複数形で書きます。
dependent:
は、ArticleモデルやUserモデルのデータを削除したときに、もしArticleUserモデルのデータを持っているときの挙動を制御するもので、:destroy
を書いた場合、その中間テーブルのデータも削除します。デフォルトの挙動では、中間テーブルのデータを削除しないため、ゴミデータが溜まっていくことになるため、ここでは:destory
を指定しました。
さらに同じように、has_many
を書いていますが、Articleモデルの方では、:users
と書き、Userモデルのデータを複数持つということを定義し、Userモデルの方でも、:articles
と書き、Articleモデルのデータを複数持つということを定義しています。
このとき、ArticleモデルやUserモデルが、それぞれuser_idカラムやarticle_idカラムを持っている場合はこのままでもいいのですが、そのようにすると、ArticleモデルのデータがひとつのUserモデルのデータとしか属せず、UserモデルのデータもひとつのArticleモデルデータとしか属することができません。
そのため、through
を使って、中間テーブルを介してそれぞれのモデルに、アクセスできるようにします。ここでは中間テーブルはArticleUserというモデルとしてアクセスするので、throguh
には、:article_users
を指定します。
これで、多対多の関連を表現できました。
使い方
has_manyを使うことで、そのモデルのデータに別な複数のモデルのデータが属することを表現できます。
さらに、いくつかの便利なメソッドが追加され、操作が楽にできるようになります。
例えば、Articleモデルのデータに複数のUserモデルのデータを属するようにするには、次のように代入するだけです。
article = Article.last
users = User.all
article.users = users
このように、Articleモデルのインスタンスにメソッドが追加されるので、それを使って楽にUserモデルのデータを関連づけることができます。
まとめ
多対多の関連を表現するときには、中間テーブルを使いましょう。
そして、モデルのhas_manyにthroughオプションを追加して、中間テーブルを介して目標のモデルへアクセスできるようにしましょう。