【Rails7】アソシエーションで使用するオプションについて

## はじめに
[HappinessChain](https://happiness-chain.com/) で日々勉強中のAkiraです！
本記事は [HappinessChain Advent Calendar 2023](https://adventar.org/calendars/9414) 14日目の記事になります！
Railsを学習していると自身のアソシエーションに関する理解が浅いために複雑な設定をしようとすると手が止まるということがありました。
本記事では何故極端に浅い理解になってしまったのか、どのように咀嚼することである程度理解できるようになったのかを書いていこうと思います。

## 何故理解が浅くなってしまったのか？
これは各種教材等でインプットする際に `has_many` や `belongs_to` の記述を "呪文" や "おまじない" のように捉えてしまっていたからだと思います。
故に複雑な設定をしなければいけない場合、オプションが何を指して何をしようとしているのか、という思考ができず手が止まったというわけです。
例を挙げると `has_many :books` ←このようなシンプルな記述でも `books:` とは何を指しているのかという所まで理解することで、より汎用的 & 自分のアイデアで活用できるようになるものと考えました。

## `has_many`、 `has_one`  の基礎
それでは実際に `has_many` や `has_one` のシンプルな記述が何をしたくてそうなっているのかについて書いていきます。
先ほども例に挙げたシンプルなこのコード `has_many :books` この引数で渡している `books:` これが何を意味しているのか文章にすると、「Bookモデルは私の情報(レコード)に対して複数の情報(レコード)が紐づいている」ということになります。
そしてこれを設定すると次のように紐づく情報を参照できるわけですね `model.books`
ですがここで頭を固くしてほしくないのが後に説明するオプション等を使用すると `model.sold_books` (売れた本たち) のように参照に使用する名前(関連名)を自由に設定できるということです。
よって、ここで設定している `books:` とは命名規則等に従っていればrailsが良しなに設定してくれることを利用したシンプルな記述というわけです。

## 少し複雑なアソシエーション
ここまではアソシエーションを学ぼうとすると最初に通る基礎中の基礎で非常にシンプルなアソシエーション設定だったと思います。
それでは本題です。次からは少々複雑なアソシエーション設定を考えてみます。
下記のようなER図を想定します。

![ブログ用ER図(1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474917/55f4ca12-bd76-f9b6-5a79-b2638a0c4e73.png)

この図は従業員(employee)は上司と部下の関係(hierarchicals)を持っているということになります。
それではこの図を元にアソシエーションを設定してみましょう。
従来のシンプルな書き方だと以下のようになりそうです。
```employee.rb
class Employee < ApplicationRecord
  has_many :hierarchicals
end
```
```hierarchical.rb
class Hierarchical < ApplicationRecord
  belongs_to :employee
end
```
ですがこれでirbを叩き情報を取ろうとすると "employee_id" が存在しないという旨のエラーが出ます。
ここでER図のhierarchicalsテーブルの外部キーを確認すると

![ブログ用ER図(2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474917/dc35e0b2-44ba-32f7-ef0b-54661bad70ab.png)

manager_employee_id と assistant_employee_id となっていることが確認できます。
このことから、上記の書き方のように設定するとRailsは hierarchicalsテーブルの `employee_id` を自動で探しに行くということが分かります。
ここまでの情報を元にすると上記エラーは明示的に外部キーを指定することで解決しそうです。
そのために一旦次のオプションを使ってみます。
```employee.rb
class Employee < ApplicationRecord
  has_many :hierarchicals, foreign_key: :manager_employee_id
end
```
こうして再度irbを叩いてみると無事に情報を取れるということが確認できます。

### foreign_keyオプション
追加したオプションは `foreign_key` というオプションです。
このオプションは名の通り、"外部キーを明示的に指定する" というオプションになります。
今回はこのオプションを使い、外部キーを明示的に指定してあげたことで「employeeとhierarchicalがmanager_employee_idで結びつく」という結果になったわけです。
実際に今回のアソシエーションを元にインスタンスを生成するとmanager_employee_idにemployee.idが設定されることが確認できると思います。

### class_name オプション
これで従業員の "部下" に対するアソシエーションは設定できましたね。
それでは "上司" に対するアソシエーションも設定していきたい...と思うのですがここで問題がでてきます。
下記のコードは先述のことも踏まえ、上司に対するアソシエーション設定を追記したものです。
```employee.rb
class Employee < ApplicationRecord
  # 部下に対するアソシエーション
  has_many :hierarchicals, foreign_key: :manager_employee_id
  
  # 上司に対するアソシエーション
  has_many :hierarchicals, foreign_key: :assistant_employee_id
end
```
気づきましたでしょうか。そう、関連名が同じなのです。
この状態では関連名を呼び出す際、部下なんだか上司なんだかわからないという状況になります。
これを解決するのが `class_name` オプションです。`class_name` を適用したコードは下記になります。
```employee.rb
class Employee < ApplicationRecord
  # 部下に対するアソシエーション
  has_many :assistants, foreign_key: :manager_employee_id, class_name: 'Hierarchical'
  
  # 上司に対するアソシエーション
  has_many :managers, foreign_key: :assistant_employee_id, class_name: 'Hierarchical'
end
```
これで上司と部下の関連名がそれぞれ assistants と managers に変更され `class_name: 'Hierarchical'` という記述が追加されました。
これは何をしているのかというと変更前までは `:hierarchicals` とすることでRailsが関連名を追えていたのですが関連名を変更したことによりRailsは関連先を追うことができなくなります。その際に自身で関連先を明示的に指定するのが `class_name` オプションと言うわけです。
この設定で 部下に対するアソシエーションを `従業員A.assistants` で、
上司に対するアソシエーションを `従業員B.managers` で取得できるようになりました。

### throughオプション
ここまでで部下と上司に対するアソシエーションは設定できました。
しかし実際に帰ってくるデータとしては関係性を表したレコードのみです。本来なら「従業員Aの部下一覧」のようにしたいはず。
ということで `through` オプションを使い実現していきます。
throughオプションは中間テーブルを "通して" 関連テーブルの情報を取得するといった際に使います。
※下記画像のようなイメージ。

![ブログ用ER図(3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474917/bdf84cdc-ea8e-ae3d-c01d-8c58408957f6.png)

それでは実際にどう書くのかコードをみていきます。
```employee.rb
class Employee < ApplicationRecord
  # 部下に対するアソシエーション
  has_many :assistants, foreign_key: :manager_employee_id, class_name: 'Hierarchical'
  # 上記中間テーブル(部下)を通して関連先の情報を取得
  has_many :assistant_employees, through: :assistants, source: :assistant_employee
  
  # 上司に対するアソシエーション
  has_many :managers, foreign_key: :assistant_employee_id, class_name: 'Hierarchical'
  # 上記中間テーブル(上司)を通して関連先の情報を取得
  has_many :manager_employees, through: :managers, source: :manager_employee
end
```
同時に今回はHierarchicalも記述します。
```hierarchical.rb
class Hierarchical < ApplicationRecord
  belongs_to :assistant_employee, class_name: 'Employee'
  belongs_to :manager_employee, class_name: 'Employee'
end
```
これで設定は以上です。
詳しく見ていきましょう。
まず、各関連名は `assistant or manager_employees(部下or上司_従業員)` という形になっています。
そして肝心の `through: assistants or managers` の部分ですが、先ほど設定した関連名を指定しています。このことから、先ほど設定した記述が中間テーブルの役割を果たしているということが理解できると思います。
そして新しいオプション `source` が登場していますが、これは `class_name` のように関連名が参照と一致しないため明示的に指定するためのオプションです。
今回部下の方では、`source: :assistant_employee`、上司の方では、`source: :manager_employee` と設定していますね。
これで部下の情報を取得すると `assistant_employee_id` を見に行くといった形になるのです。
また、`belongs_to` で使用している `class_name` オプションも先ほど解説した内容で理解できると思います。

### おわりに
以上がアソシエーションで設定で使用する基本的なオプションです。
自分が理解するのに苦戦した内容をなるべくわかりやすく解説したつもりですが間違っている点などあればご指摘頂ければ幸いです。