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:referencesuser:referencesという多対多の関係を作るモデルの名前をカラム名とした参照を定義します。

referencesというものは見慣れないかもしれませんが、Railsが別のテーブルのデータを参照するために使用し、rails db:migrateなどを使うと、テーブルにはカラム名としてarticle_iduser_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オプションを追加して、中間テーブルを介して目標のモデルへアクセスできるようにしましょう。