Skip to content

Commit

Permalink
新しい技術記事を追加
Browse files Browse the repository at this point in the history
Rails における UserBook.all と UserBook.joins(:user).joins(:book) の違い
  • Loading branch information
noraworld authored Oct 21, 2024
1 parent 657795e commit d821e69
Showing 1 changed file with 139 additions and 0 deletions.
139 changes: 139 additions & 0 deletions articles/join-tables-joins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
title: "Rails における UserBook.all と UserBook.joins(:user).joins(:book) の違い"
emoji: "📚"
type: "tech" # tech: 技術記事 / idea: アイデア
topics: ["rails", "activerecord", "database"]
published: true
---

# はじめに
中間テーブル `user_books` があったとき、Rails (Active Record) における `UserBook.all``UserBook.joins(:user).joins(:book)` の違いについて説明します。



# モデルの定義
以下のようなモデルがあったとします。ユーザは複数の本を所有しており、本は複数のユーザに所有されている、いわゆる多対多の関係になっています。

```ruby
class User < ApplicationRecord
has_many :user_books
has_many :books, through: :user_books
end
```

```ruby
class Book < ApplicationRecord
has_many :user_books
has_many :users, through: :user_books
end
```

```ruby
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 |

上記のテーブルの内容を言語的にまとめると以下のようになります。

* 一郎さんは「こころ」と「三四郎」と「人間失格」を所有している
* 二郎さんは「三四郎」と「人間失格」と「雪国」を所有している
* 三郎さんは「雪国」と「羅生門」を所有している



## ユースケース
「羅生門」は書庫で管理しなくなったなどの理由でレコードを削除したとします。

```ruby
Book.find_by(name: "羅生門").destroy
```

さて、この状態で `user_books` テーブルに存在する、`users``books` に紐づいた全レコードを取得したいとします。

以下の Active Record では正しく **取得できません**

```ruby
UserBook.all
```

なぜなら、`books` テーブルから「羅生門」は削除しましたが、`user_books` テーブルから「三郎さんが羅生門を所有している」という情報は削除されていないからです。

`users` テーブルあるいは `books` テーブルから削除されたレコードが紐づいているものを含まない `user_books` テーブルの全レコードを取得したい場合は以下の Active Record を実行する必要があります。

```ruby
UserBook.joins(:user).joins(:book)
```



# おまけ
逆に `users` テーブルあるいは `books` テーブルから削除されているレコードに紐づく `user_books` テーブルのレコードのみを取得したい場合は以下の Active Record を実行すれば良さそうです。

```ruby
UserBook
.left_outer_joins(:user, :book)
.where(users: { id: nil })
.or(UserBook.where(books: { id: nil }))
```

上記の例でいうとこれは「削除された本『羅生門』を所有しているユーザの情報のみを表示したい」という要件の場合に使用できます。



# 関連するレコードを一緒に削除したい場合
上記の例で `books` テーブルから「羅生門」を削除しましたが、その際に `user_books` テーブルから「三郎さんが羅生門を所有している」というレコードも一緒に消したい場合は該当するアソシエーションに `dependent: :destroy` を付与します。

```diff
class Book < ApplicationRecord
- has_many :user_books
+ has_many :user_books, dependent: :destroy
has_many :users, through: :user_books
end
```

これで、`books` テーブルからレコードが削除された際に、そのレコードと紐づいた `user_books` に含まれるレコードも一緒に削除されます。



# 参考
* [ChatGPT - Active Record 中間テーブル 取得](https://chatgpt.com/share/6715f5be-cd9c-8004-b66b-b1cd3a58c001)
* [中間テーブルを用いた処理(Rails) #Ruby - Qiita](https://qiita.com/ysda/items/87c056aed33280995332)
* [【Rails】ActiveRecordでSQLっぽくテーブル結合する方法 #Ruby - Qiita](https://qiita.com/teddy_bear_eng/items/0f7c7723f5d121a493f9)
* [日本文学作品のおすすめ36選。読まれ続けてきた数々の名作をご紹介](https://sakidori.co/article/1324884)

0 comments on commit d821e69

Please sign in to comment.