多対多のアソシエーションの解説をフォローフォロワー機能を例に解説していきます。
中間テーブル、モデルの設定は難しいですが、できるだけ理解できるように解説していきます。
開発環境
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カラムが同一のユーザーのレコードを参照しています。
フォロイーのアソシエーションについても同様に働きます。
まとめ
フォロー、フォロワー機能のアソシエーションがどのように働いているか掴めたでしょうか。
中間テーブルは難しい内容ですが、理解できるまで何回でも見返していただけると嬉しいです!!
参考文献