LoginSignup
11
8

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

Last updated at Posted at 2023-08-17

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

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

開発環境

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型に関しては、以下の記事を参考にしてください。

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

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

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

まとめ

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

参考文献

11
8
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
11
8