Rails【初心者向け】フォロー機能のモデル同士の関連付けについて理解を深掘ってみた！

####【フォロー機能のモデル同士の関連付けについての理解】

SNS系WEBアプリの制作中に、**フォロー機能のモデル同士の関連付け**について理解できていないまま実装していた部分があったので、復習も兼ねて理解を深めていきたいと思い、実装の部分を一から学習し直しました。
今回はフォロー機能の関連付けを行う際の理解を掘り下げることで学んだ内容をまとめてみようと思います。

・フォロー機能の実装に関しては[こちら](https://qiita.com/kazukimatsumoto/items/14bdff681ec5ddac26d1#%E8%87%AA%E5%B7%B1%E7%B5%90%E5%90%88%E5%A4%9A%E5%AF%BE%E5%A4%9A%E3%81%BE%E3%81%A8%E3%82%81)の記事を参考にさせていただきました。

注）この記事では主にモデル同士の関連付けの理解について深掘りを行っていくため、実装の部分は割愛させていただきます。


## はじめに
~~~ruby:app/models/user.rb
class User < ApplicationRecord
  #
  #
  has_many :active_relationships, class_name: "Relationship", foreign_key: :follower_id
  has_many :followings, through: :active_relationships, source: :following
 
  has_many :passive_relationships, class_name: "Relationship", foreign_key: :following_id
  has_many :followers, through: :passive_relationships, source: :follower
~~~

突然ですが、この部分。
**Userモデル**に記述する、**Relationshipsモデル**との**関連性**を持たせるこの部分のコードの理解がなかなかできずに困っていました（class_name? through? source?といった感じです）

そこでフォローする側の関連付けの部分と、フォローされる側の関連付けの部分に分けた上で、一行づつに丁寧にメソッドの意味も踏まえて考えていきたいと思います。**そこで先程のコードの上２行**

~~~ruby
  has_many :active_relationships, class_name: "Relationship", foreign_key: :follower_id
  has_many :followings, through: :active_relationships, source: :following
~~~
**active_relationships**(能動的)、つまり**自分がフォローしているユーザー**との関連を持たせるための記述の部分だけに着目して理解の深掘りを行っていきたいと思います。


・**前提**としてRelationshipsのマイグレーションファイルには、フォローしているユーザーとフォローされているユーザーのidが保存できるカラムを作成してあることとしています。

~~~ruby:db/migrate/[timestamp]_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[6.0]
  def change
    create_table :relationships do |t|
      #フォローしているユーザー（自分）のid
      t.integer :follower_id
      #フォローされているユーザー（相手）のid
      t.integer :following_id

      t.timestamps
  end
end
~~~

##has_many,  belongs_to

~~~ruby
has_many :active_relationships, class_name: "Relationship", foreign_key: :follower_id
~~~
まずはこの行からいきたいと思います。
そもそも**has_many**なのですが、これは今までもモデルとの関連性を持たせる場合に使用してきました。
UserモデルとContentモデル(投稿)を紐付けるような場合には、ユーザーはたくさんの投稿を持っていて、その投稿は特定のユーザーのものだから１対多の関係、だからhas_manyを使うんだ。
という認識でいました。この認識は間違っていないのですが、実は**has_manyには「メソッドを生成するためのメソッド」**という役割もあります。

どういうことかと言いますと

~~~ruby
has_many :active_relationships
~~~
これで`active_relationships`というメソッドを使えるようにした！ということです。
もちろんUserクラス内で定義されているのでUserクラスのインスタンスでしか使用できませんが

~~~ruby
@user = User.first
@user.active_relationships
~~~
例えば上記`@user.active_relationships`のようなメソッドが利用できるようになります(**belongs_to**においても同様です）この事を頭の片隅に置いていて下さい。

##class_name, foreign_key
次に**class_name**と**foreign_key**についてです。まずは**class_name**ですが

~~~ruby
has_many :active_relationships, class_name: "Relationship"
~~~
前述のように`@user.active_relationships`でactive_relationshipsメソッドを呼び出した際に、**class_nameで何も指定していないデフォルトの状態**だとrailsは
**"ActiveRelationship"テーブル**にある、user_idを参照しに行こうとします。しかし、そのようなテーブルは作成していません。

なので、**参照先は"Relationship"テーブルにしてネ。**といった指示をclass_nameでrailsに伝えているような形です。

この後passive_relationship（フォローされる側の関連）も作成する予定です。
has_many :relationshipとしてしまうと名前が重複してしまうので、分かりやすい名前のメソッド(active_relationships)を作成して、参照先はRelationshipテーブルだヨ。と示している訳です。
※（正しくはActiveRelationship model(class)ですが、テーブルと書く方が分かりやすいと思ったのでここではテーブルとしています）
***
次は**foreign_key**です

先程class_nameの説明で、class_nameで何も指定していないデフォルトの状態だと
"Active_relationship"テーブルにある、**user_id**を参照しに行く
と書きました。class_nameで参照先はRelationshipテーブルと示せたものの、**user_id**というカラムは用意していません。

ここでなぜrailsは**user_id**を参照しに行くのか？なのですが、今回のRelationshipモデルとの関連付けのように、Userクラスでhas_manyとしていると**デフォルトではforeign_keyがuser_id（モデル名_id）となっているためです。**

例えば投稿モデル（content）と関連付けを行う場合

~~~ruby
has_many :contents
~~~
とすると思います。これはデフォルトでは下記のようになっています。

~~~ruby
has_many :contents
# class_name  :"Content"
# foreign_key :user_id
~~~
つまり、**Contentテーブルのuser_idを参照して下さいネ。**ということです
Contentテーブルでは外部キーをuser_idとしてカラムを用意し、class_nameもそのままのContentでよいため、デフォルトの挙動に従って何も追加で記述する必要はありませんでした。

今回に関してはRelationshipテーブルを参照しに行く際に、user_idではなく、**follower_id**を代わりに参照してヨ。ということをforeign_keyでrailsに指示している、ということになります。
***

以上のことから１行目のコードをまとめます。

~~~ruby
has_many :active_relationships, class_name: "Relationship", foreign_key: :follower_id
~~~
・Relatinoshipモデルと関連付けをするヨ
・active_relationshipsというメソッドを使えるようにしたけど、呼び出す時の参照先はRelationshipテーブルのfollower_idを参照しに行ってネ。

***

##through, source
次に２行目です

~~~ruby
has_many :followings, through: :active_relationships, source: :following
~~~
前述した内容と同様に、has_many :followingsでfollowingsというメソッドが使えるようになりました。
**throughとsource**ですが**あまり直訳した意味は考えずに、このメソッドがどういう動きをするのか？**を理解した方が簡単ですので、メソッドの動き方で理解していきましょう。

まず目標としては、has_manyで作成したfollowingsというメソッドを使って、コントローラーなどで@user.followingsとしただけで、そのユーザーがフォローしているユーザーのデータが手に入る。というような状況を実現したいです。

しかし、現時点でも上記のようなデータを取得することは可能です。
・rails consoleで下記のようなコードを入力してみます

~~~ruby
user = User.first
user.active_relationships #　> そのユーザーがフォローしているユーザーidの集合を取得
user.active_relationships.first.following # > そのユーザーがフォローした1人目のユーザーのデータを取得
# ↓ user.followingsと同じ動き
user.active_relationships.map(&:following) # > そのユーザーがフォローしたユーザーのデータの集合を取得

# mapメソッドは 配列の要素それぞれに、与えられた処理を実行して新しい配列を作り出す
# &(アンパサンド）は mapで渡された配列一つ一つにメソッドを実行する 

# 例えば user.contents.map($:body)などとすると、ユーザーが投稿したbodyカラムの内容だけを集めた配列が新しく作られる
~~~

結果`user.active_relationships.map(&:following)`でもフォローしているユーザーのデータは取り出せるのですが、これだとコードが長くなり、今後何度も呼び出すとなるとコードが見づらくなってしまいます。

そこで、話を戻しましてhas_manyで作成したfollowingsメソッドに戻ります。
**through**も**source**も上記のrails consoleで実行した内容が理解できると分かり易いのですが

~~~ruby
through: :active_relationships
~~~
１行目のhas_manyで作成した、**active_relationshipsメソッドを呼び出し**

~~~ruby
source: :following
~~~
`map(&:following)`と同じように、**それぞれの要素にfollowingメソッドを呼び出しているのです。**
※（**followingメソッドとは何か**は、後述するRelationshipモデル側の関連付けの部分で説明します）

これでfollowingsメソッドでそのユーザーがフォローしているユーザーのデータの集合が取得できるようになり、`user.followings`で簡単にデータの取得が可能になりました。
（名前が似ているのでややこしいですが、**followingsメソッドとfollowingメソッドは別物です**）

***

２行目をまとめます。

~~~ruby
has_many :followings, through: :active_relationships, source: :following
~~~
・**through**でactive_relationshipメソッドを呼び出して、Relationshipテーブルから自分がフォローしているユーザーのidの集合を取得するヨ
・**source**で、throughで取得したデータにそれぞれfollowingメソッドをかけて具体的なユーザーの情報を持った配列にして、followingsメソッドにそのデータが返すヨ
・**followings**メソッドを呼び出すことで自分がフォローしているユーザーの情報を集合で取得できるようになったヨ


***

##Relationshipモデル

**Relationshipモデル側の関連付け**

~~~ruby:app/models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :following, class_name: "User"
  belongs_to :follower,  class_name: "User"
end
~~~
先程まではUserモデル側の関連付けを行なってきましたが、考え方としては前述した内容とさほど変わりありません。
`belongs_to :following`という記述で**following**メソッドが使えるようになり、`class_name :"User"`でUserモデルと１対多の関連付けを行っています。

**※ここで使えるようになったfollowingメソッドが、先程後述説明するとしました、followingメソッドです。**
つまり先程のfollowingsメソッドの動きのイメージとしましては
**①** throughの部分でactive_relationshipsメソッドが呼び出されて、UserモデルからRelationsihpモデルへ（foreign_key :follower_idで）参照を行い、フォローしているユーザーのidの集合を取得します。
**②** 今度はsourceの部分で、①で取得したidの集合（配列）にfollowingメソッドがそれぞれ実行されて、RelationshipモデルからUserモデルへ（foreign_key :user_idで）参照を行います。

最後の部分はなかなかイメージが難しいかもしれませんが、rails consoleでコマンドを打って確認すると、それぞれの動き方や取得できるデータの内容が分かって理解し易くなると思います。

***

##まとめ
フォロー機能の関連付けだけをまとめると
・Userクラスのhas_manyで、Userモデルのuser_idとRelationshipモデルのfollower_idが紐づいている
・Relationshipクラスのbelongs_toで、Relationshipモデルのfollower_idとUserモデルのuser_idが紐付いている
ということになります。理解するためのコツは、has_manyやbelongs_toはメソッドを生成するということを頭に置いて考えることだと思いました。

（フォローされているユーザーとの関連付けは、答えのコードもありますし、この内容と逆のことをすればいいので、片方理解ができればそこまで難しくないと思うので省略させていただきます。）

***

Rails初学者がまとめた物なので至らない点もあるかと思います、気づいた点などあればコメントいただけると幸いです。ありがとうございました！