概要
Railsにて、単語帳アプリを作成しました。
ログイン機能を有しており、Userモデルと、登録される単語をWordモデル、お気に入り機能もあるため、Favoriteモデルが存在します。
FavoriteモデルをUserモデルとWordモデルの中間テーブルとし、それぞれのアソシエーションを組もうとした際に調べたことを、備忘録としてまとめました。
アソシエーションを考える
アソシエーションとは、モデルを利用したテーブル同士の関連付けのことで、これをモデルに定義することで、そのモデルに紐づく別のモデルの情報へアクセスできるようになります。
ではまず、UserとWordの関係性について考えます。
1人のユーザーは複数の単語を登録でき、反対に、単語は1人のユーザーに所属します。
この場合、ユーザーと単語の間には「一対多」の関係が成り立ち、以下のようなアソシエーションを組むことができます。
class User < ApplicationRecord
has_many :words
end
class Word < ApplicationRecord
belongs_to :user
end
アソシエーションを定義するとできること
ここで、アソシエーションを組むことによって、何ができるかを確認します。
例えば、ユーザーのマイページがあって、そこに自分が登録した単語を一覧表示させたい場合、コントローラーでは、そのユーザーが登録した単語情報を取得する必要があります。
もしアソシエーションを組まずに、ある特定のユーザーの登録した単語の情報を取得しようとすると、次のような記述になります。(今回はusersコントローラーのshowアクション内に記述します)
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
続いてアソシエーションを組んだ場合、以下のように記述することができます。
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モデルでのアソシエーションの定義の仕方を確認します。
class Word < ApplicationRecord
belongs_to :user
has_many :favorites
has_many :users, through: :favorites
end
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を定義します。
class Favorite < ApplicationRecord
belongs_to :user
end
一方、Userモデルにおいて、先程のWordモデルでの書き方とほぼ一緒ですが、少し工夫が必要になります。
ポイントは、既にhas_many :words
が定義されているという点です。
例えば以下のような記述をした場合、
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を以下のよう書き直します。
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のお気に入りした単語を取得
以上。