Rails における UserBook.all と UserBook.joins(:user).joins(:book) の違い
はじめに
中間テーブル user_books
があったとき、Rails (Active Record) における UserBook.all
と UserBook.joins(:user).joins(:book)
の違いについて説明します。
モデルの定義
以下のようなモデルがあったとします。ユーザは複数の本を所有しており、本は複数のユーザに所有されている、いわゆる多対多の関係になっています。
class User < ApplicationRecord
has_many :user_books
has_many :books, through: :user_books
end
class Book < ApplicationRecord
has_many :user_books
has_many :users, through: :user_books
end
class UserBook < ApplicationRecord
belongs_to :user
belongs_to :book
end
レコード
以下のようなレコードがデータベースに格納されていたとします。
users
テーブル
id | name |
---|---|
1 | 一郎 |
2 | 二郎 |
3 | 三郎 |
books
テーブル
id | name |
---|---|
1 | こころ |
2 | 三四郎 |
3 | 人間失格 |
4 | 雪国 |
5 | 羅生門 |
user_books
テーブル
user_books
自体の主キーである id
は省略します。
user_id | book_id |
---|---|
1 | 1 |
1 | 2 |
1 | 3 |
2 | 2 |
2 | 3 |
2 | 4 |
3 | 4 |
3 | 5 |
上記のテーブルの内容を言語的にまとめると以下のようになります。
- 一郎さんは「こころ」と「三四郎」と「人間失格」を所有している
- 二郎さんは「三四郎」と「人間失格」と「雪国」を所有している
- 三郎さんは「雪国」と「羅生門」を所有している
ユースケース
「羅生門」は書庫で管理しなくなったなどの理由でレコードを削除したとします。
Book.find_by(name: "羅生門").destroy
さて、この状態で user_books
テーブルに存在する、users
と books
に紐づいた全レコードを取得したいとします。
以下の Active Record では正しく 取得できません。
UserBook.all
なぜなら、books
テーブルから「羅生門」は削除しましたが、user_books
テーブルから「三郎さんが羅生門を所有している」という情報は削除されていないからです。
users
テーブルあるいは books
テーブルから削除されたレコードが紐づいているものを含まない user_books
テーブルの全レコードを取得したい場合は以下の Active Record を実行する必要があります。
UserBook.joins(:user).joins(:book)
おまけ
逆に users
テーブルあるいは books
テーブルから削除されているレコードに紐づく user_books
テーブルのレコードのみを取得したい場合は以下の Active Record を実行すれば良さそうです。
UserBook
.left_outer_joins(:user, :book)
.where(users: { id: nil })
.or(UserBook.where(books: { id: nil }))
上記の例でいうとこれは「削除された本『羅生門』を所有しているユーザの情報のみを表示したい」という要件の場合に使用できます。
関連するレコードを一緒に削除したい場合
上記の例で books
テーブルから「羅生門」を削除しましたが、その際に user_books
テーブルから「三郎さんが羅生門を所有している」というレコードも一緒に消したい場合は該当するアソシエーションに dependent: :destroy
を付与します。
class Book < ApplicationRecord
- has_many :user_books
+ has_many :user_books, dependent: :destroy
has_many :users, through: :user_books
end
これで、books
テーブルからレコードが削除された際に、そのレコードと紐づいた user_books
に含まれるレコードも一緒に削除されます。
Discussion