【Rails】フォロー/フォロワー機能のアソシエーションについて【初学者の疑問点を詳しめに解説】

## はじめに
　本記事は、Railsの学習を始めて1ヶ月の初学者が、学習を進めていて疑問に思った点について調べた結果を備忘録も兼ねてまとめたものです。
　そのため、記事の内容に誤りが含まれている可能性があります。ご容赦ください。
　間違いを見つけた方は、お手数ですが、ご指摘いただけますと幸いです。

## 今回の疑問点
　今回の疑問点は、

　　_フォロー/フォロワー機能のアソシエーションについて_
　
　です。　

 　現在、Twitterクローンを作成しており、フォロー/フォロワー機能の実装の際に上記について疑問を抱きました。

## 疑問点についての解説
### いいね機能実装時との比較
おそらく、多くの方がフォロー/フォロワー機能実装の前にいいね機能の実装に取り組んでいることと思いますので、いいね機能と比較して確認していきます。
#### いいね機能のアソシエーションの記述
##### Favoriteモデルの記述

```ruby:app/models/favorite.rb
class Favorite < ApplicationRecord
    belongs_to :user   # FaviriteモデルとUserモデルの1対1の関係
    belongs_to :tweet   # FavoriteモデルとTweetモデルの1対1の関係

end
```

##### Userモデルの記述

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :tweets   # UserモデルとTweetモデルの1対多の関係
  has_many :favorites   # UserモデルとFavoriteモデルの1対多の関係
  has_many :favorite_tweets, through: :favorites, source: :tweet  # UserモデルとTwitterモデルの多対多の関係（Favoriteモデル経由）

end
```

##### Tweetモデルの記述

```ruby:app/models/tweet.rb
class Tweet < ApplicationRecord
    belongs_to :user   # TweetモデルとUserモデルとの1対1の関係
    has_many :favorites   # TweetモデルとUserモデルとの1対多の関係

end
```

##### 各モデルの関係

上記の通り記述することで各モデルを以下の通り関連付けています。


〜〜〜Userモデル〜〜〜　　⇄　　多対多　　⇄　　〜〜〜Tweetモデル〜〜〜

　　　　   ⇅1対1及び1対多　　　　　　　　　　　　　　　　 ⇅1対1及び1対多

　〜〜〜〜〜〜〜〜〜〜〜〜〜〜Favoriteモデル〜〜〜〜〜〜〜〜〜〜〜〜〜〜

各モデルを頂点とした三角形の相互関係となっています。

#### フォロー/フォロワー機能のアソシエーションの記述
記述を見る前に各モデルの関係性から確認します。
フォロー/フォロワー機能の場合には以下の通りとなります。

〜〜〜〜〜Userモデル〜〜〜〜〜

　　　　　　 ⇅　1対多の関係

〜〜〜〜Relationshipモデル〜〜〜〜

このまま、実装しようとしてもフォローする側のユーザーとフォローされる側のユーザーを区別することができないため、うまくいきません（フォロー数とフォロワー数を表示したいのにフォロー数とフォロワー数の合計しか出せない等）。
うまく実装するためには、先程のいいね機能の時のように三角形の関係を作ってあげる必要があります。
そこで以下のように記述します。

##### Relationshipモデルの記述

```ruby:relationship.rb
class Relationship < ApplicationRecord
    belongs_to :following, class_name: "User"   # RelationshipモデルとUserモデル（folloeing)との1対1の関係
    belongs_to :follower, class_name: "User"   # RelationshipモデルとUserモデル（follower)との1対1の関係
end
```

上記のように記述することで、Userモデルを便宜上following（いいね機能での「いいね」するUser）とfollower（いいね機能での「いいね」されるTweet）の２つに分けることができました。
ここで注目していただきたいのが、`class_name: "User"`という記述です。
`class_name: "User"`は、参照先のモデルクラス名を指定するために記述しています。
なぜ指定しなければならないかというと、関連名に基づいて参照先のモデルクラス名をRailsが推測しているからです。
今回は関連名にUserモデルのクラス名`user`を使わず、`following`及び`follower`としているため、Railsによる関連名からの参照先モデルの推測ができないため、明示してあげる必要があります。

##### Userモデルの記述

```ruby:app/models/user.rb
class User < ApplicationRecord

  #フォローする側のUserから見て、フォローされる側のUserを(Relationshipを経由して)集める。なのでfollowing_id(フォローする側)
  has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id
  # Relationship（active_relationshipsを経由して「follower」モデルのUser(フォローされた側)を集めることを「followings」と定義
  has_many :followings, through: :active_relationships, source: :follower
  

  #フォローされる側のUserから見て、フォローしてくる側のUserを(Relationship経由で)集める。なのでfollower_id(フォローされる側)
  has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id
  # Relationship（active_relationshipsを経由して「following」モデルのUser(フォローする側)を集めることを「followers」と定義
  has_many :followers, through: :passive_relationships, source: :following
  
end
```

続いてuser.rbファイルに上記の通り記述します。
`has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id`
`has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id`
の2つの記述は、いいね機能のuser.rb及びtweet.rbファイルの
`has_many :favorites` 
に当たります。
いいね機能の際にはFavoriteモデルに対してUserモデルとTweetモデルからひとつずつ矢印が伸びていましたが、今回は、Userモデルのfollowing（いいね機能での「いいね」するUser）とfollower（いいね機能での「いいね」されるTweet）からひとつずつ矢印が伸びることになるのでUserモデルに２通り記述します。
2通り書く際に両方の関連名を`relationships`と同名にすることはできませんので、relationship.rbの時と同様に関連名を変えていきます。
今回は
`has_many :active_relationships, class_name: "Relationship"`
`has_many :passive_relationships, class_name: "Relationship"`
としています。
文の最後に
`foreign_key: :following_id`
`foreign_key: :follower_id`
とありますが、これは外部キー（foreign key)を指定するための記述です。
指定する理由は、`class_name`による参照先モデル名の指定同様、関連名がモデル名の複数形（relationships)ではないため、Railsが推測をすることができないからです。
本来、関連名→モデル名→外部キーと関連付けられているものが、関連名を変更したことにより、関連性がなくなっています。
また、外部キーの名前は原則、`親モデル名_id`とされており、この通り命名することで親モデルと外部キーが関連付けられます。しかし、今回はfollowing_id、follower_idであり同名のモデルは存在しませんので親モデルを推測することはできません。（※relationship.rbにおいて便宜上Userモデルをfollowingとfollowerの2つに分けていますが、実際にUserモデルが２つになったわけではないことにご注意ください。）
ここで、なぜ同様に関連名を変更したrelationship.rbにおいて外部キーを指定していないのか疑問に思う方もいらっしゃると思います。
これは、Relationshipモデルを親モデルとする外部キーが他のモデルに設定されていないからです。
（もし、外部キーが設定されていれば、同様に外部キーを指定する必要があるでしょう。）

続いて以下の記述について解説します。
`has_many :followings, through: :active_relationships, source: :follower`
`has_many :followers, through: :passive_relationships, source: :following`
上記の記述は、いいね機能のuser.ebの
`has_many :favorite_tweets, through: :favorites, source: :tweet`
に当たります。
`has_many, through`は`through`の後に記述したモデルを経由してデータを取得できるようにします。今回の場合は、Userモデル(following又はfollowers)からRelationshipモデル(active_relationships又はpassive_relationships）を経由してUserモデル(following又はfollowers)のデータを取得することができます。（※Relationshipモデルは便宜上、active_relationshipsとpassive_relationshipsに分かれていますが、実際に２つに分かれたわけではないことにご注意ください。Relationshipモデル自体は１つのままです。）
本記述は、フォロー/フォロワー一覧画面を作成する際に必要になります。
Relationshipモデルを経由しますが、実質的にはUser（following)モデルとUser（follower)モデルとの多対多の関連付けをしています。
また、今回も関連名が変更されていますので、参照先モデルを指定しなければなりません。
指定のための記述が、
`source: :follower`
`source: :following`
になります。
先ほどまでは、`class_name`で指定していましたが、ここでは`source`を使います。
`class_name`と`source`はどちらも参照先モデルを指定するために使用しますが、`class_name`は1対多の関係の際に`source`は多対多の関係の際に使用します。
`has_many`は1対多の関係ですが、`has_many, through`は多対多の関係になりますので、ここでは`source`を使います。
上記の記述により、Relationshipモデルを経由したデータの取得が可能になるため、


本来は、user.rbファイルにfollowed_by?の定義をする必要がありますが、ここでは割愛いたします。

##### 各モデルの関係

上述の通り設定をすると各モデルの関係性は以下の通りとなります。


　Userモデル{〜〜〜(following)〜〜〜　　⇄　　多対多　　⇄　　〜〜〜（follower)〜〜〜}

　　　　   　　   　　　　　⇅1対1及び1対多　　　　　　　　　　　　　　　　⇅1対1及び1対多

　　　　　〜〜〜〜〜〜〜〜〜〜〜〜〜〜Relationshipモデル〜〜〜〜〜〜〜〜〜〜〜〜〜〜


いいね機能と同様に三角形の関係となっております。
上記のようになれば、フォローする側のユーザーとフォローされる側のユーザーが区別できるため、うまく実装できるのではないでしょうか。



## まとめ
長くなってしましましたので最後にポイントをまとめます。

- User同士を関連付ける必要があり、いいね機能と同じやり方では、フォローする側とされる側を区別できない。区別するためには、擬似的にUserモデルとRelationshipモデルを２つに分ける必要がある。

- 関連名を変更する場合には、`class_name`又は`source`で参照先モデルを指定する必要がある。また、必要があれば、`foreign_key`で外部キーも指定する必要がある。
- フォロー/フォロワー一覧画面を作成するためには、`has_many, through`を使ってUser（following)とUser（follower)の多対多の関連付けをする。