LoginSignup
4
4

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-04-18

概要

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

アソシエーションを考える

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

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

app/model/user.rb
  class User < ApplicationRecord
    has_many :words
  end
app/model/word.rb
  class Word < ApplicationRecord
    belongs_to :user
  end

アソシエーションを定義するとできること

ここで、アソシエーションを組むことによって、何ができるかを確認します。
例えば、ユーザーのマイページがあって、そこに自分が登録した単語を一覧表示させたい場合、コントローラーでは、そのユーザーが登録した単語情報を取得する必要があります。
もしアソシエーションを組まずに、ある特定のユーザーの登録した単語の情報を取得しようとすると、次のような記述になります。(今回はusersコントローラーのshowアクション内に記述します)

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

  
続いてアソシエーションを組んだ場合、以下のように記述することができます。

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モデルでのアソシエーションの定義の仕方を確認します。

app/model/word.rb
  class Word < ApplicationRecord
    belongs_to :user
    has_many :favorites
    has_many :users, through: :favorites
  end
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は、「単語は、お気に入りを介して複数のユーザーを抱えている」といった意味合いです。
例えば、

  @word = Word.find(1)  # 単語1
  @users = @word.users

とすることで、単語1をお気に入りしているユーザーの情報を取得できます。

またFavoriteモデルでは、お気に入りは単語に属するので、belongs_to :wordを定義しています。

UserモデルとFavoriteモデル

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

app/model/favorite.rb
  class Favorite < ApplicationRecord
    belongs_to :user
  end

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

app/model/user.rb
  class User < ApplicationRecord
    has_many :words
    has_many :favorites
    has_many :words, through: :favorites
  end

2行目と4行目でhas_many :wordsが被ってしまっています。
これだと、2行目のhas_many :wordsが4行目のhas_many :words, through: :favoritesによって上書きされることになります。

  @user = User.find(1)  # ユーザー1
  @words = @user.words

上記のような記述をしたとき、本来であれば「ユーザー1が登録した単語の情報を取得」したかったところが、アソシエーションが上書きされた状態では、「ユーザー1がお気に入りした単語の情報を取得する」になってしまいます。

こうならないために、User.rbを以下のよう書き直します。

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オプションに関連付け元の名前を指定する必要があります。

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

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

以上。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4