22
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Ruby on Rails】モデルの関連付け(アソシエーション)をざっと理解してみる 〈図解あり〉

Last updated at Posted at 2023-11-30

はじめに

初めまして!この記事では、RailsのActiveRecordの関連付け(アソシエーション)について解説します。
私自身もRailsの初学者で、関連付けを学ぶ際にモデル同士の関係が把握しにくかった経験があります。そこで、基本的な内容ではありますが、この記事では一対一、一対多、多対多の関連付けに焦点を当て、解説していきます。もし記事の中に不正確な情報や改善点があれば、ご指摘いただけると助かります🙇‍♀️

アソシエーションとは

3.jpg

データベースの異なるテーブルやモデル同士を関連づけ、効果的にデータを結びつける仕組みのことです。
これにより、アプリケーション内で違うデータ同士を組み合わせて取り出したり、更新したりすることが簡単にできるようになります!

メジャーな関連付けのパターン3つ

まずはよく使う基本的な関連付けを紹介します。

①一対一
②一対多
③多対多

一対一

POINT

  • has_one
  • belongs_to

7.jpg
一番わかりやすい例は、「Userモデル」と「Profileモデル」の関係です。
この場合、ユーザーとプロフィールの関係は

  • 「ある1人のユーザーは、1つのプロフィールを持っている
  • 「プロフィールは、ユーザーに所属している」
    といえます。

それぞれのテーブルには以下のようなカラムが設定されているとします。(一部省略)
スクリーンショット 2023-11-28 14.16.23.png
※user_idについては後ほど解説します。

id⇨1のユーザーが、id⇨1のプロフィールを持っている。
◼︎ ユーザーに対してプロフィールは一つなので、「has_one :profile」となります。
◼︎ プロフィールはユーザーに属する(belongs to)ことになるので、「belongs_to :user」となります。
モデルに記載する際は以下の通りになります。

app/models/user.rb
class User < ApplicationRecord
  has_one :profile
end
app/models/profile.rb
class Profile < ApplicationRecord
  belongs_to :user
end

一対多

POINT

  • has_many
  • belongs_to

8.jpg

一対多の関係は、「Userモデル」「Post(投稿)モデル」 の関係で説明します。
この場合、ユーザーと投稿の関係は

  • 「1人のユーザーは、複数の投稿を持っている
  • 「1つの投稿は、1人のユーザーが作成している
    といえます。

それぞれのテーブルには以下のようなカラムが設定されているとします。(一部省略)
スクリーンショット 2023-11-28 15.10.57.png

(例)id⇨1のユーザーが、id⇨1、5、10、24の投稿を持っている。
◼︎ ユーザーに対して投稿は複数なので、「has_many :posts」となります。(たくさん持つのでsがつく)
◼︎ 投稿はユーザーに属する(belongs to)ことになるので、「belongs_to :user」となります。
モデルに記載する際は以下の通りになります。

app/models/user.rb
class User < ApplicationRecord
  has_many :posts
end
app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end

多対多

POINT

  • has_many
  • belongs_to
  • 中間テーブル

tataiat.jpg

多対多の関係はお気に入り機能が一般的です。
この場合、ユーザーと投稿の関係は

  • 「1人のユーザーは、お気に入りしている投稿を複数持っている
  • 「1つの投稿は、お気に入りされた複数のユーザーの情報を持っている

といえます。

お気に入りを持っているので、「ユーザーとお気に入り、投稿とお気に入りの関係が多対多なのでは??」とうっかりわからなくなってしまいそうですが、
お気に入り(Favoriteモデル)を通してユーザーと投稿の関係は多対多であるということになります。

中間テーブル

Favoritesテーブルは、ユーザーがどの投稿をお気に入りにしたのか、投稿がどのユーザーにお気に入りされたのかを管理していることになります。(その機能を使ってお気に入り機能を実装できる。)
Favoriteモデルのように、多対多の関係を取り持つテーブルのことを「中間テーブル」と呼びます。

それぞれのテーブルには以下のようなカラムが設定されているとします。(一部省略)
スクリーンショット 2023-11-28 16.30.08.png

多対多の関係のテーブルは中間テーブルなしでは複雑になってしまいます。
中間テーブルを使用することで、クエリが簡素化されたり、将来的な変更や拡張が容易になり、新しい要件に対応しやすくなったりするというメリットがあります。


こんな時はどう??

▶︎ 1つの投稿にカテゴリーが1つ付けられる

10.jpg
これはわかりやすいですね。
この場合、カテゴリーと投稿の関係は

  • 「1つのカテゴリーは、複数の投稿を持っている
  • 「1つの投稿は、1つのカテゴリーを持っている
    ので、一対多の関係です。

▶︎ 1つの投稿に複数のカテゴリーが付けられる場合

11.jpg
この場合、カテゴリーと投稿の関係は

  • 「1つのカテゴリーは、複数の投稿を持っている
  • 「1つの投稿は、複数のカテゴリーを持っている

ので、多対多の関係です。
カテゴリーと投稿の組み合わせを管理するために、post_categoriesという中間テーブルを作成します。

この場合のコードは以下のようになります。

models/post.rb
class Post < ApplicationRecord
  has_many :post_categories
  has_many :categories, through: :post_categories
end
models/category.rb
class Category < ApplicationRecord
  has_many :post_categories
  has_many :posts, through: :post_categories
end
models/post_category.rb
# 中間テーブル
class PostCategory < ApplicationRecord
  belongs_to :post
  belongs_to :category
end

もう少し複雑になる

話は先ほどのお気に入り機能に戻ります。

この多対多の説明では、「お気に入りのテーブル(Favoriteモデル)を通して、ユーザー(Userモデル)と投稿(Postモデル)は多対多の関係である」としましたが、

一対多の説明では、「ユーザー(Userモデル)と投稿(Postモデル)は一対多の関係である」 としました。

「ユーザーと投稿の関係は一対多、多対多のどっちなんだ??」と疑問を持った方もいらっしゃるかもしれませんが(私がそうでした)、これはどっちも正解です。

中間テーブル(お気に入り).jpg

一対多

  • 「1人のユーザーは、複数の投稿を持っている
  • 「1つの投稿は、1人のユーザーが作成している

多対多

  • 「1人のユーザーは、お気に入りしている投稿を複数持っている
  • 「1つの投稿は、お気に入りされた複数のユーザーの情報を持っている

この2つは同時に成り立ちます。

この場合のコードは以下のようになります。

app/models/user.rb
class User < ApplicationRecord
  has_many :posts
  has_many :favorites
  has_many :favorite_posts, through: :favorites, source: :post
end
app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :favorites
  has_many :favorited_users, through: :favorites, source: :user
end
app/models/favorite.rb
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を使用しない場合、親モデルが削除されても関連する子モデルのレコードは削除されません。

12.jpg
※親モデルに:dependentオプションを追加

app/models/user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :destroy #親モデルに追加
end
app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end

コントローラーでこのようにすると、ユーザーが削除された時にそのユーザーに関連する全ての投稿も削除されます。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  def destroy
    @user = User.find(params[:id])
    @user.destroy

    redirect_to users_path, notice: 'ユーザーが削除されました。'
  end
end

おわりに

ここまでActive Record の関連付け機能(アソシエーション)について、簡単にですが解説させていただきました。他にも、単一テーブル継承 (STI)やポリモーフィック関連付けなどもあるので調べてみてください。最後までご覧いただきありがとうございます。

22
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?