中間テーブル 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 に含まれるレコードも一緒に削除されます。