はじめに
初めまして!この記事では、RailsのActiveRecordの関連付け(アソシエーション)について解説します。
私自身もRailsの初学者で、関連付けを学ぶ際にモデル同士の関係が把握しにくかった経験があります。そこで、基本的な内容ではありますが、この記事では一対一、一対多、多対多の関連付けに焦点を当て、解説していきます。もし記事の中に不正確な情報や改善点があれば、ご指摘いただけると助かります🙇♀️
アソシエーションとは
データベースの異なるテーブルやモデル同士を関連づけ、効果的にデータを結びつける仕組みのことです。
これにより、アプリケーション内で違うデータ同士を組み合わせて取り出したり、更新したりすることが簡単にできるようになります!
メジャーな関連付けのパターン3つ
まずはよく使う基本的な関連付けを紹介します。
①一対一
②一対多
③多対多
一対一
POINT
- has_one
- belongs_to
一番わかりやすい例は、「Userモデル」と「Profileモデル」の関係です。
この場合、ユーザーとプロフィールの関係は
- 「ある1人のユーザーは、1つのプロフィールを持っている」
- 「プロフィールは、ユーザーに所属している」
といえます。
それぞれのテーブルには以下のようなカラムが設定されているとします。(一部省略)
※user_idについては後ほど解説します。
id⇨1のユーザーが、id⇨1のプロフィールを持っている。
◼︎ ユーザーに対してプロフィールは一つなので、「has_one :profile」となります。
◼︎ プロフィールはユーザーに属する(belongs to)ことになるので、「belongs_to :user」となります。
モデルに記載する際は以下の通りになります。
class User < ApplicationRecord
has_one :profile
end
class Profile < ApplicationRecord
belongs_to :user
end
一対多
POINT
- has_many
- belongs_to
一対多の関係は、「Userモデル」 と 「Post(投稿)モデル」 の関係で説明します。
この場合、ユーザーと投稿の関係は
- 「1人のユーザーは、複数の投稿を持っている」
- 「1つの投稿は、1人のユーザーが作成している」
といえます。
それぞれのテーブルには以下のようなカラムが設定されているとします。(一部省略)
(例)id⇨1のユーザーが、id⇨1、5、10、24の投稿を持っている。
◼︎ ユーザーに対して投稿は複数なので、「has_many :posts」となります。(たくさん持つのでsがつく)
◼︎ 投稿はユーザーに属する(belongs to)ことになるので、「belongs_to :user」となります。
モデルに記載する際は以下の通りになります。
class User < ApplicationRecord
has_many :posts
end
class Post < ApplicationRecord
belongs_to :user
end
多対多
POINT
- has_many
- belongs_to
- 中間テーブル
多対多の関係はお気に入り機能が一般的です。
この場合、ユーザーと投稿の関係は
- 「1人のユーザーは、お気に入りしている投稿を複数持っている」
- 「1つの投稿は、お気に入りされた複数のユーザーの情報を持っている」
といえます。
お気に入りを持っているので、「ユーザーとお気に入り、投稿とお気に入りの関係が多対多なのでは??」とうっかりわからなくなってしまいそうですが、
お気に入り(Favoriteモデル)を通してユーザーと投稿の関係は多対多であるということになります。
中間テーブル
Favoritesテーブルは、ユーザーがどの投稿をお気に入りにしたのか、投稿がどのユーザーにお気に入りされたのかを管理していることになります。(その機能を使ってお気に入り機能を実装できる。)
Favoriteモデルのように、多対多の関係を取り持つテーブルのことを「中間テーブル」と呼びます。
それぞれのテーブルには以下のようなカラムが設定されているとします。(一部省略)
多対多の関係のテーブルは中間テーブルなしでは複雑になってしまいます。
中間テーブルを使用することで、クエリが簡素化されたり、将来的な変更や拡張が容易になり、新しい要件に対応しやすくなったりするというメリットがあります。
こんな時はどう??
▶︎ 1つの投稿にカテゴリーが1つ付けられる
これはわかりやすいですね。
この場合、カテゴリーと投稿の関係は
- 「1つのカテゴリーは、複数の投稿を持っている」
- 「1つの投稿は、1つのカテゴリーを持っている」
ので、一対多の関係です。
▶︎ 1つの投稿に複数のカテゴリーが付けられる場合
- 「1つのカテゴリーは、複数の投稿を持っている」
- 「1つの投稿は、複数のカテゴリーを持っている」
ので、多対多の関係です。
カテゴリーと投稿の組み合わせを管理するために、post_categoriesという中間テーブルを作成します。
この場合のコードは以下のようになります。
class Post < ApplicationRecord
has_many :post_categories
has_many :categories, through: :post_categories
end
class Category < ApplicationRecord
has_many :post_categories
has_many :posts, through: :post_categories
end
# 中間テーブル
class PostCategory < ApplicationRecord
belongs_to :post
belongs_to :category
end
もう少し複雑になる
話は先ほどのお気に入り機能に戻ります。
この多対多の説明では、「お気に入りのテーブル(Favoriteモデル)を通して、ユーザー(Userモデル)と投稿(Postモデル)は多対多の関係である」としましたが、
一対多の説明では、「ユーザー(Userモデル)と投稿(Postモデル)は一対多の関係である」 としました。
「ユーザーと投稿の関係は一対多、多対多のどっちなんだ??」と疑問を持った方もいらっしゃるかもしれませんが(私がそうでした)、これはどっちも正解です。
一対多
- 「1人のユーザーは、複数の投稿を持っている」
- 「1つの投稿は、1人のユーザーが作成している」
多対多
- 「1人のユーザーは、お気に入りしている投稿を複数持っている」
- 「1つの投稿は、お気に入りされた複数のユーザーの情報を持っている」
この2つは同時に成り立ちます。
この場合のコードは以下のようになります。
class User < ApplicationRecord
has_many :posts
has_many :favorites
has_many :favorite_posts, through: :favorites, source: :post
end
class Post < ApplicationRecord
belongs_to :user
has_many :favorites
has_many :favorited_users, through: :favorites, source: :user
end
class Favorite < ApplicationRecord
belongs_to :user
belongs_to :post
end
sourceオプション
has_many :favorited_usersのように書いただけでは、Railsは自動的に:favorited_usersなどの名前を探しに行くため、sourceオプションを使って明示的に関連するモデルを指定することで、名前の重複を避けつつ正しい関連付けを行います。
dependent: :destroy
dependent: :destroyを使用すると、「ユーザーを削除すると、作成した投稿も消える」 といったように、親モデルのレコードが削除されたときに、それに関連する子モデルのレコードも同時に削除されます。
逆に、dependent: :destroyを使用しない場合、親モデルが削除されても関連する子モデルのレコードは削除されません。
class User < ApplicationRecord
has_many :posts, dependent: :destroy #親モデルに追加
end
class Post < ApplicationRecord
belongs_to :user
end
コントローラーでこのようにすると、ユーザーが削除された時にそのユーザーに関連する全ての投稿も削除されます。
class UsersController < ApplicationController
def destroy
@user = User.find(params[:id])
@user.destroy
redirect_to users_path, notice: 'ユーザーが削除されました。'
end
end
おわりに
ここまでActive Record の関連付け機能(アソシエーション)について、簡単にですが解説させていただきました。他にも、単一テーブル継承 (STI)やポリモーフィック関連付けなどもあるので調べてみてください。最後までご覧いただきありがとうございます。