🦔

[Rails]フォロー、フォロワー機能のアソシエーションについて

に公開

多対多のアソシエーションの解説をフォローフォロワー機能を例に解説していきます。

中間テーブル、モデルの設定は難しいですが、できるだけ理解できるように解説していきます。

開発環境

ruby 2.6.3
rails 6.1.4

前提

  • Userモデル同士でフォローフォロワー機能を実装する
  • Userモデルを実装済みである
  • 1:多のアソシエーションは理解っている前提で話を進めます

作成イメージ

初めに、今回作成するフォローフォロワー機能について、イメージを掴んでおきましょう!
画像1.png
この図にある、Followerはフォローする人、Followedはフォローされる人を指しています。
フォローフォロワー機能の複雑なところは、図のように1:nのアソシエーションが同一モデルに2つ存在しているという点にあります。1つずつ考えるとそこまで難しくないので、それぞれのアソシエーションを分けて、解説していこうと思います。

実装

DB構築

rails g model Relationship follower:references followed:references

references型に関しては、以下の記事を参考にしてください。

https://qiita.com/ryouzi/items/2682e7e8a86fd2b1ae47

上記のコマンドを実行したらマイグレーションファイルが作成されるので、
以下のコマンドを忘れないように実行しましょう!!

rails db:migrate

モデルの記述

relationshipモデルには、以下のように記述します。

app/models/relationship.rb
class Relationship < ApplicationRecord
# 1
  belongs_to :follower, class_name: "User"
# 2
  belongs_to :followed, class_name: "User"
end

ここで行っている設定は、

  1. followerというオブジェクト名でUserモデルとのリレーションを行う設定
  2. followedというオブジェクト名でUserモデルとのリレーションを行う設定

この2つになります。

belongs_toについて

ここでは、belongs_toについて補足説明します。
以下のようにモデルの設定がされているとします。

Xxx.rb
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モデルには、以下のように記述します。

app/models/user.rb
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

ここで行っている設定は、

  1. reverse_of_relationshipsというオブジェクト名で、Relationshipモデルfollowed_idを外部キーとしてアソシエーションを行う設定
  2. followersというオブジェクト名で、1で設定したreverse_of_relationshipsというアソシエーションを利用してRelationshipモデルを参照し、参照先のRelationshipモデルfollowerというアソシエーションを行う設定
  3. relationshipsというオブジェクト名で、Relationshipモデルとfollower_idを外部キーとしてアソシエーションを行う設定
  4. 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

これを実行すると、図の赤枠のレコードを取得することができます。
image.png
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

まず、こちらのアソシエーションについてです。
画像3.png
このようなイメージとなります。
フォロワーを取得するため、フォローされているユーザーの情報(followed_id)を元に、Relationshipモデルの情報を参照します。
つまり、このアソシエーションを使用すると、Relationshipモデルを扱っている状態となります。

2

次に、最もややこしい部分になります。

user.rb
has_many :followers, through: :reverse_of_relationships, source: :follower

こちらのアソシエーションに関しては、一つ目のアソシエーションをthroughオプションで利用しています。
この状態では、Relationshipモデルのレコードを参照するため、フォロワーの情報を取得することができません。
そこで、sourceオプションによって、Relationshipモデルに定義されている、

relationship.rb
belongs_to :follower, class_name: "User"

このアソシエーションを使用して、参照しているrelationshipモデルのレコード内のfollower_idカラムとUserモデルのidカラムが同一のユーザーのレコードを参照しています。画像7.png

フォロイーのアソシエーションについても同様に働きます。

まとめ

フォロー、フォロワー機能のアソシエーションがどのように働いているか掴めたでしょうか。
中間テーブルは難しい内容ですが、理解できるまで何回でも見返していただけると嬉しいです!!

参考文献

https://railsguides.jp/association_basics.html

https://api.rubyonrails.org/v7.0/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to

Discussion