中間テーブルのアソシエーション

# 概要

Railsにて、単語帳アプリを作成しました。
ログイン機能を有しており、Userモデルと、登録される単語をWordモデル、お気に入り機能もあるため、Favoriteモデルが存在します。
FavoriteモデルをUserモデルとWordモデルの中間テーブルとし、それぞれのアソシエーションを組もうとした際に調べたことを、備忘録としてまとめました。

# アソシエーションを考える
アソシエーションとは、モデルを利用したテーブル同士の関連付けのことで、これをモデルに定義することで、そのモデルに紐づく別のモデルの情報へアクセスできるようになります。

ではまず、UserとWordの関係性について考えます。
**1人のユーザーは複数の単語を登録でき、反対に、単語は1人のユーザーに所属します。**
この場合、**ユーザーと単語の間には「一対多」の関係が成り立ち**、以下のようなアソシエーションを組むことができます。

  ```Ruby:app/model/user.rb
  class User < ApplicationRecord
    has_many :words
  end
  ```

  ```Ruby:app/model/word.rb
  class Word < ApplicationRecord
    belongs_to :user
  end
  ```
# アソシエーションを定義するとできること
ここで、アソシエーションを組むことによって、何ができるかを確認します。
例えば、ユーザーのマイページがあって、そこに自分が登録した単語を一覧表示させたい場合、コントローラーでは、そのユーザーが登録した単語情報を取得する必要があります。
もしアソシエーションを組まずに、ある特定のユーザーの登録した単語の情報を取得しようとすると、次のような記述になります。（今回はusersコントローラーのshowアクション内に記述します）

  ```Ruby:app/controllers/users_controller.rb
  class UsersController < ApplicationController
    def show
      @user = User.find(1)  # usersテーブルからidが1のレコードを取得して、@userに代入
      @words = Word.where(user_id: @user.id)  # wordsテーブルからuser_idが@userのidと一致する全てのレコードを取得して@wordsに代入
    end
  end
  ```
　　
続いてアソシエーションを組んだ場合、以下のように記述することができます。

  ```Ruby:app/controllers/users_controller.rb
  class UsersController < ApplicationController
    def show
      @user = User.find(1)
      @words = @user.words
    end
  end
  ```
先程Wordモデルで定義した、`has_many :words`の`:words`の部分が、`@user.words`のような形で、Userクラス（モデル）のインスタンスメソッドとして使用できるようになった、という感じです。
このように、アソシエーションを組めばコードを簡潔に、直感的に記述することができます。

# 中間テーブルのアソシエーション
次に、お気に入りを含めたアソシエーションについて考えていきます。
**ユーザーは複数の単語をお気に入りすることができます。反対に、単語は複数のユーザーからお気に入りされる可能性があります。つまり、ここではユーザーと単語の間に「多対多」の関係性が存在します。**
**中間テーブルは、このような多対多の関係にある2つのテーブル間の組み合わせだけをレコードとして保存する役割を持ち、「多対多」の関係を定義します。**今回は中間テーブルとして、Favoriteモデルを用意し、お気に入り機能を実装します。

## WordモデルとFavoritesモデル
ではまず、Wordモデル、Favoriteモデルでのアソシエーションの定義の仕方を確認します。

  ```Ruby:app/model/word.rb
  class Word < ApplicationRecord
    belongs_to :user
    has_many :favorites
    has_many :users, through: :favorites
  end
  ```

  ```Ruby:app/model/favorite.rb
  class Favorite < ApplicationRecord
    belongs_to :word
  end
  ```
Wordモデルでは、先程定義した`belongs_to :user`に加え、新たに次の2つを定義しました。
まず、1つの単語は複数お気に入りされる可能性があるため、`has_many :favorites`を記述します。
続いて、**`has_many :users, through: :favorites`**についてですが、has_manyメソッドの**throughオプション**は**「〜を経由する」**という意味で、モデルに多対多の関連を定義するときに利用します。
上記の`has_many :users, through: :favorites`は、「単語は、お気に入りを介して複数のユーザーを抱えている」といった意味合いです。
例えば、

  ```Ruby
  @word = Word.find(1)  # 単語1
  @users = @word.users
  ```
とすることで、単語1をお気に入りしているユーザーの情報を取得できます。  
またFavoriteモデルでは、お気に入りは単語に属するので、`belongs_to :word`を定義しています。

## UserモデルとFavoriteモデル
次に、UserモデルとFavoriteモデルでの定義の仕方です。
Favoriteモデルは先ほどと同じように、belongs_toを定義します。

  ```Ruby:app/model/favorite.rb
  class Favorite < ApplicationRecord
    belongs_to :user
  end
  ```
  
一方、Userモデルにおいて、先程のWordモデルでの書き方とほぼ一緒ですが、少し工夫が必要になります。
ポイントは、既に`has_many :words`が定義されているという点です。
例えば以下のような記述をした場合、

  ```Ruby:app/model/user.rb
  class User < ApplicationRecord
    has_many :words
    has_many :favorites
    has_many :words, through: :favorites
  end
  ```
2行目と4行目で`has_many :words`が被ってしまっています。
これだと、２行目の`has_many :words`が４行目の`has_many :words, through: :favorites`によって上書きされることになります。

  ```Ruby
  @user = User.find(1)  # ユーザー１
  @words = @user.words
  ```
上記のような記述をしたとき、本来であれば「ユーザー1が**登録した**単語の情報を取得」したかったところが、アソシエーションが上書きされた状態では、「ユーザー1が**お気に入りした**単語の情報を取得する」になってしまいます。
  
こうならないために、User.rbを以下のよう書き直します。

  ```Ruby:app/model/user.rb
  class User < ApplicationRecord
    has_many :words
    has_many :favorites
    has_many :fav_words, through: :favorites, source: :word
  end
  ```
**`has_many :fav_words, through: :favorites, source: :word`**について、  
**`has_many`の後には、`:fav_words`という仮の名前を定義してあげて、
`through: :favorites`で、favoritesテーブルを経由して、
`source: :word`のように、`source`の後に参照元になるモデルを指定してあげる**  
といった具合で、アソシエーションを組んでいます。
このように`:fav_words`と定義することで、`インスタンス.fav_words`のようにメソッド化して使うことができます。

Railsは、アソシエーションを機能させる際、外部キーの名前などでテーブルの判断をしているそうですが、関連付け名（`has_many :words`の`:words`の部分のこと）を外部キーの名前から外れた名前にする場合（今回の例で言うと、`has_many :words`ではなく、`has_many :fav_words`と定義すること）は、sourceオプションに関連付け元の名前を指定する必要があります。


これらのアソシエーションが組まれた上で、以下のように記述をすると、それぞれの単語情報を取得することができます。

  ```Ruby
  @user = User.find(1)  # ユーザー１
  @words_1 = @user.words  # ユーザー1の登録した単語を取得
  @words_2 = @user.fav_words  # ユーザー１のお気に入りした単語を取得
  ```

以上。