多対多のアソシエーションの解説をフォローフォロワー機能を例に解説していきます。
中間テーブル、モデルの設定は難しいですが、できるだけ理解できるように解説していきます。
開発環境
ruby 2.6.3
rails 6.1.4
前提
- Userモデル同士でフォローフォロワー機能を実装する
- Userモデルを実装済みである
- 1:多のアソシエーションは理解っている前提で話を進めます
作成イメージ
初めに、今回作成するフォローフォロワー機能について、イメージを掴んでおきましょう!
この図にある、Followerはフォローする人、Followedはフォローされる人を指しています。
フォローフォロワー機能の複雑なところは、図のように1:nのアソシエーションが同一モデルに2つ存在しているという点にあります。1つずつ考えるとそこまで難しくないので、それぞれのアソシエーションを分けて、解説していこうと思います。
実装
DB構築
rails g model Relationship follower:references followed:references
references型に関しては、以下の記事を参考にしてください。
上記のコマンドを実行したらマイグレーションファイルが作成されるので、
以下のコマンドを忘れないように実行しましょう!!
rails db:migrate
モデルの記述
relationshipモデルには、以下のように記述します。
class Relationship < ApplicationRecord
# 1
belongs_to :follower, class_name: "User"
# 2
belongs_to :followed, class_name: "User"
end
ここで行っている設定は、
-
follower
というオブジェクト名でUserモデル
とのリレーションを行う設定 -
followed
というオブジェクト名でUserモデル
とのリレーションを行う設定
この2つになります。
belongs_toについて
ここでは、belongs_toについて補足説明します。 以下のようにモデルの設定がされているとします。class Xxx < ApplicationRecord
belongs_to :yyy
end
belongs_to
の設定をすると、XXXテーブルにあるYYYモデルの外部キーYYY_id
を参考に、YYYモデルのレコードを参照することができます。
ここで、アソシエーションによって動作する内容としては以下になります。
def self.yyy
return Yyy.find(self.yyy_id)
end
この処理によって、親モデルのレコードを取得することができます。
どの外部キーを参照するかについては、belongs_to :
の後ろに設定されているものに_id
を付けたものを外部キーとして扱います。
例えば、belongs_to :user
の場合だと、user_id
が外部キーとなります。
一方で、belongs_to :users
だと、users_id
となるため、単数形、複数形の設定に注意しましょう。
userモデルには、以下のように記述します。
class User < ApplicationRecord
# あるユーザーをフォローしている人(フォロワー)の一覧を取得するアソシエーション##########
# 1
has_many :reverse_of_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
# 2
has_many :followers, through: :reverse_of_relationships, source: :follower
################################################################################
# あるユーザーがフォローしている人(フォロイー)の一覧を取得するアソシエーション##########
# 3
has_many :relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
# 4
has_many :followings, through: :relationships, source: :followed
################################################################################
end
ここで行っている設定は、
-
reverse_of_relationships
というオブジェクト名で、Relationshipモデル
とfollowed_id
を外部キーとしてアソシエーションを行う設定 -
followers
というオブジェクト名で、1で設定したreverse_of_relationships
というアソシエーションを利用してRelationshipモデル
を参照し、参照先のRelationshipモデル
のfollower
というアソシエーションを行う設定 -
relationships
というオブジェクト名で、Relationship
モデルとfollower_id
を外部キーとしてアソシエーションを行う設定 -
followings
というオブジェクト名で、3で設定したrelationships
というアソシエーションを利用してRelationshipモデル
を参照し、参照先のRelationshipモデル
のfollowed
というアソシエーションを行う設定
この4つの設定になります。
以上でアソシエーションの実装は終わりです。
オプションについて
今回使用されているhas_many
のオプションについてそれぞれ解説していきます。
class_name
このオプションは、関連付け相手のモデル名を直接指定できるオプションとなります。
これを設定することによって、任意のオブジェクト名にモデルを関連付けることができます。
今回は、Relationshipモデル
との関連付けを行いたいため、Relationship
と設定しています。
foreign_key
このオプションは、外部キーの名前を直接指定することができます。
今回は、Relationshipモデルに2つの外部キーがあるため、これを使用して外部キーを識別します。
dependent
このオプションは、オブジェクトのオーナー(1:nの1の方)のレコードが削除されたとき、それに関連付けられたオブジェクトを制御することができます。
よく指定するのは、:destroy
オプションで、これを指定すると、関連付けられたオブジェクトも同時に削除することができます。
through
このオプションは、3つのモデル間でアソシエーションする際に使用します。
これは、2つのモデルの中間にモデルが介在し、中間のモデルを経由し、2つのモデルにおいてアソシエーションの関係を作ります。(親 - 子 - 孫の関係を作成)
class Xxx < ApplicationRecord
has_many :yyy
has_many :zzz, through: :yyy
end
class Yyy < ApplicationRecord
belongs_to :xxx
has_many :zzz
end
class Zzz < ApplicationRecord
belongs_to :yyy
end
このように設定し、以下のコードを実行します。
Xxx.find(1).zzz
これを実行すると、図の赤枠のレコードを取得することができます。
Xxxモデル
に紐づいているYyyモデル
を参照し、それぞれのYyyモデル
に紐づいているZzzモデル
のレコードを参照し、親モデル(Xxxモデル)の情報から孫モデル(Zzzモデル)の情報すべてを取得できます。
source
throughオプション
を適用したオブジェクト名から、中間のモデル内のアソシエーションのうち、どれを使用して孫モデルを参照するのか推測できない場合に、中間のモデル内のアソシエーションを直接指定する際に使用します。
throughオプションで紹介したコードを、source
を使用して丁寧に書くと以下のようになります。
class Xxx < ApplicationRecord
has_many :yyy
- has_many :zzz, through: :yyy
+ has_many :zzz, through: :yyy, source: :zzz
end
class Yyy < ApplicationRecord
belongs_to :xxx
has_many :zzz
end
class Zzz < ApplicationRecord
belongs_to :yyy
end
今回は動作自体は同じですが、このように設定することによって、Yyyモデル(子モデル)からZzzモデル(孫モデル)を参照する際に使用するアソシエーションを指定することができます。
解説
ここから、フォローフォロワーのアソシエーションについて詳細に解説していきます。
フォロワーを取得するアソシエーションについて
フォロワーを取得するアソシエーションは2つに分かれます。
それぞれ解説していきます!
1
has_many :reverse_of_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
まず、こちらのアソシエーションについてです。
このようなイメージとなります。
フォロワーを取得するため、フォローされているユーザーの情報(followed_id)を元に、Relationshipモデル
の情報を参照します。
つまり、このアソシエーションを使用すると、Relationshipモデル
を扱っている状態となります。
2
次に、最もややこしい部分になります。
has_many :followers, through: :reverse_of_relationships, source: :follower
こちらのアソシエーションに関しては、一つ目のアソシエーションをthroughオプション
で利用しています。
この状態では、Relationshipモデルのレコードを参照するため、フォロワーの情報を取得することができません。
そこで、sourceオプション
によって、Relationshipモデル
に定義されている、
belongs_to :follower, class_name: "User"
このアソシエーションを使用して、参照しているrelationshipモデルのレコード内のfollower_idカラム
とUserモデルのidカラム
が同一のユーザーのレコードを参照しています。
フォロイーのアソシエーションについても同様に働きます。
まとめ
フォロー、フォロワー機能のアソシエーションがどのように働いているか掴めたでしょうか。
中間テーブルは難しい内容ですが、理解できるまで何回でも見返していただけると嬉しいです!!
参考文献