【Rails】フォロー/フォロワー機能の実装※見返し用

:::note info
フォロー・フォロワー機能を初心者向け(自分用)にまとめたもの
:::

# 1. フォロー・フォロワー機能の基本的な仕組み

フォロー機能は、ユーザー同士が相互に関係を持つ「多対多」の関係を管理します。これを実現するために、「中間テーブル」を使います。

・ユーザーは他のユーザーをフォローできる。	
・フォロワーは自分をフォローしているユーザーのこと。

この関係をRailsで実現するために、次の3つを作成します：

・Userモデル: ユーザーの情報を管理します。
・Relationshipモデル: フォロー・フォロワーの関係を管理する中間テーブルです。
・コントローラー: フォロー・フォロワーの操作を実行するためのアクションを定義します。

# ２. データベース設計

まず、relationshipsテーブルを作成して、フォローとフォロワーを管理します。

```bush
'rails g migration CreateRelationships follower_id:integer followed_id:integer'
```
マイグレーションファイルの中身は次のようになります。
```ruby
class CreateRelationships < ActiveRecord::Migration[6.0]
  def change
    create_table :relationships do |t|
      t.integer :follower_id
      t.integer :followed_id

      t.timestamps
    end
    add_index :relationships, :follower_id
    add_index :relationships, :followed_id
    add_index :relationships, [:follower_id, :followed_id], unique: true
  end
end
```
<details open><summary>add_indexとは</summary>

###### インデックスとは何か？

インデックスはデータベースの「検索を高速化」するための仕組みです。データベース内の特定のカラムにインデックスを設定することで、そのカラムを使った検索（クエリ）がより早く行えるようになります。

(例)たとえば、usersテーブルに1,000,000人のユーザーがいるとします。もしnameカラムで検索するときにインデックスがない場合、データベースは1,000,000件のすべてのレコードを1つずつ確認して、該当する名前のユーザーを探さなければなりません。これは非常に時間がかかる処理です。

しかし、nameカラムにインデックスを追加すると、データベースはそのインデックスを参照して効率よく検索を行えるようになります。これにより、検索速度が大幅に向上します。

###### add_indexの仕組み

add_indexは、指定されたカラムにインデックスを追加するコマンドです。これにより、そのカラムを使った検索クエリの処理が速くなります。
これにより、ユーザー同士のフォロー関係を管理するテーブルが作成されます。
###### 例1: 単一のカラムにインデックスを追加する
```ruby 
add_index :relationships, :follower_id
```
・:relationships: インデックスを追加するテーブル名（ここではrelationshipsテーブル）。
・:follower_id: インデックスを追加するカラム名。relationshipsテーブルのfollower_idカラムにインデックスを追加しています。

この場合、follower_idで検索するクエリが効率化されます。たとえば、「あるユーザーがフォローしている人」を探すとき、follower_idを使ってクエリを実行する場合、検索速度が速くなります。
###### 例2: 複数のカラムにインデックスを追加する（複合インデックス）
```ruby 
add_index :relationships, [:follower_id, :followed_id], unique: true
```
・複合インデックス: 2つ以上のカラムを組み合わせたインデックスのことを複合インデックスと呼びます。この場合、follower_idとfollowed_idのペアに対してインデックスを追加しています。
・unique: true: このオプションをつけることで、follower_idとfollowed_idの組み合わせが重複しないことをデータベースに強制します。つまり、同じユーザーが同じ人を2回以上フォローできなくなります。

###### インデックスを追加する理由

インデックスを追加する理由は、主に次の2つです

・パフォーマンス向上: 大量のデータがあるテーブルで、特定のカラムを使った検索やクエリが多い場合、そのカラムにインデックスを追加することでクエリ処理を速くします。
・一意性の保証: add_indexでunique: trueを指定することで、特定のカラムやカラムの組み合わせが重複しないように制約をかけることができます。これにより、データの整合性を保つことができます。

###### ※validatesとの違い
```Relationship.rb
class Relationship < ApplicationRecord
validates :relationships, { scope: :followed_id }
```
・validates :follower_id, uniqueness: { scope: :followed_id }は、Railsアプリケーションレベルで同じ組み合わせを重複して保存できないようにするバリデーションです。

・add_index :relationships, [:follower_id, :followed_id], unique: trueは、データベースレベルでfollower_idとfollowed_idの組み合わせが一意であることを保証します。

・通常、データベースインデックスとRailsのバリデーションを両方組み合わせて使用することが推奨されます。なぜなら、それぞれが異なるレベルでの保証を提供するからです。

1. Railsのバリデーション:
・フォームでエラーメッセージを表示するために、アプリケーションレベルのバリデーションが必要です。
・データがデータベースに送信される前に、重複チェックを行います。
2. データベースのインデックス:
・万が一、並列リクエストや競合が発生した場合にも、データベース側での一意性を保証します。
・アプリケーションの外からデータが挿入されたり、複雑な操作が行われた際に、データの一意性がデータベースで守られます。

</details>

# ３. モデルの設定

次に、UserモデルとRelationshipモデルを設定します。
#### Userモデル
```ruby
class User < ApplicationRecord
  # フォローしているユーザーとの関係
  has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
  has_many :following, through: :active_relationships, source: :followed

  # フォロワーとの関係
  has_many :passive_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy
  has_many :followers, through: :passive_relationships, source: :follower

  # ユーザーをフォローするメソッド
  def follow(other_user)
    active_relationships.create(followed_id: other_user.id)
  end

  # フォローを解除するメソッド
  def unfollow(other_user)
    active_relationships.find_by(followed_id: other_user.id).destroy
  end

  # すでにフォローしているかを確認するメソッド
  def following?(other_user)
    following.include?(other_user)
  end
end
```
<details open><summary>解説</summary>

```ruby
has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
```

###### 1.1 has_many :active_relationships

・**has_many**は、1対多の関係を表すRailsのアソシエーションです。この場合、「あるユーザーが複数のフォロー関係（active_relationships）を持っている」ことを示しています。

・**active_relationships**は、このアソシエーションの名前です。これは、「ユーザーがフォローしている関係」を指します。通常、フォローしているユーザーとの関係は「アクティブ」な関係と呼ばれるため、active_relationshipsという名前を使っています。

###### 1.2 class_name: "Relationship"

・このオプションは、active_relationshipsがどのモデルを参照しているかを指定しています。

・class_name: "Relationship"により、このアソシエーションがRelationshipモデルを参照することをRailsに伝えています。

・デフォルトでは、active_relationshipsという名前から、ActiveRelationshipモデルを探してしまいますが、ここではRelationshipモデルを使いたいので、このようにclass_nameオプションで指定します。

###### 1.3 foreign_key: "follower_id"

・foreign_keyオプションは、このアソシエーションがどのカラムを外部キーとして使用するかを指定します。

・follower_idを外部キーとして使用することで、このactive_relationshipsが「フォローしているユーザー」を意味することを示しています。つまり、follower_idが「フォローする側のユーザーのID」を表しています。

###### 1.4 dependent: :destroy

・dependent: :destroy**オプションは、あるユーザーが削除されたときに、そのユーザーが持つ関連するactive_relationships（つまり、そのユーザーがフォローしている全ての関係）も一緒に削除されることを指定しています。

・これにより、データの整合性が保たれ、ユーザーが削除されたときにそのユーザーに関連するフォロー情報も削除されます。

```ruby
has_many :following, through: :active_relationships, source: :followed
```

###### 2.1 has_many :following

・followingは、「ユーザーがフォローしている他のユーザー」を表します。followingという名前を使って、フォローしている人を一覧で取得できるようにしています。

###### 2.2 through: :active_relationships

・throughオプションは、中間テーブルを介したアソシエーションを定義するために使用されます。ここでは、active_relationships（フォロー関係）を通じて、ユーザーがフォローしている他のユーザー（following）を取得します。

・簡単に言うと、ユーザーはactive_relationshipsテーブルを通じて「フォローしている人」を参照しています。

###### 2.3 source: :followed

・sourceオプションは、followingアソシエーションがactive_relationshipsテーブルのどのカラムを参照するかを指定します。

・followedは、active_relationshipsのfollowed_idカラムを指しており、これは「フォローされている人」のIDを意味します。

・つまり、このアソシエーションでは「フォローしているユーザー」を取得するために、followed_idを使っています。

##### 3. 全体の仕組みの解説

これらのアソシエーションを使うと、Railsで以下のことが可能になります。

1. active_relationships: ユーザーがフォローしている関係（フォロワー側）をRelationshipモデルを通じて管理します。
2. following: active_relationshipsを介して、ユーザーがフォローしているユーザー（フォローされる側）を取得します。

例えば、あるユーザーが他のユーザーをフォローしている場合、その関係はactive_relationshipsに保存されます。そして、そのユーザーがフォローしているユーザーの一覧を取得したい場合は、followingを使って簡単に取得できます。

```example.ruby
user = User.find(1)
user.following # ユーザーがフォローしている他のユーザーの一覧を取得
```
##### 4.具体例

ユーザーA（ID: 1）がユーザーB（ID: 2）をフォローしている場合、relationshipsテーブルには次のようなレコードが保存されます。
| id | follower_id | followed_id |
|:-:|:-:|:-:|
| 1 | 1 | 2 |

ここで、follower_idはフォローしているユーザー、つまりユーザーAを指し、followed_idはフォローされているユーザー、つまりユーザーBを指しています。

この状態でuser.followingを呼び出すと、ユーザーAがフォローしているユーザー（この場合はユーザーB）を取得することができます。

</details>

#### Relationshipモデル
```ruby
class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
end
```
# 4. フォロー機能の実装（コントローラー）

RelationshipsControllerを作成して、フォロー・フォロワーの処理を行います。

#### RelationshipsController
```ruby
class RelationshipsController < ApplicationController
  before_action :authenticate_user!

  def create
    user = User.find(params[:followed_id])
    current_user.follow(user)
    redirect_to user
  end

  def destroy
    relationship = Relationship.find(params[:id])
    user = relationship.followed
    current_user.unfollow(user)
    redirect_to user
  end
end
```
# 5. ビューの実装

フォロー・フォロワーの一覧ページを作成します。

#### show_follow.html.erb（フォロー・フォロワーの一覧）
```erb
<h3><%= @title %></h3>

<ul class="users-list">
  <% @users.each do |user| %>
    <!-- ユーザーの画像 -->
    <%= image_tag user.profile_image.url, alt: user.name, size: "50x50" if user.profile_image.present? %>

    <!-- ユーザーの名前 -->
    <strong><%= user.name %></strong>

    <!-- フォロー数とフォロワー数 -->
    <p>
      <strong>フォロー数:</strong> <%= user.following.count %>
      <strong>フォロワー数:</strong> <%= user.followers.count %>
    </p>

    <!-- ユーザーのプロフィールページへのリンク -->
    <%= link_to "プロフィールを見る", user_path(user), class: "btn btn-secondary" %>

    <!-- フォローボタン -->
    <% if current_user.following?(user) %>
      <%= link_to "フォロー解除", relationship_path(current_user.active_relationships.find_by(followed_id: user.id)), method: :delete, class: "btn btn-danger" %>
    <% else %>
      <%= link_to "フォローする", relationships_path(followed_id: user.id), method: :post, class: "btn btn-primary" %>
    <% end %>
  </li>
  <% end %>
</ul>
```
# 6. リンクの追加

プロフィールページやユーザー一覧ページにフォロー数・フォロワー数を表示し、それぞれの一覧ページにリンクを追加します。

#### show.html.erb（ユーザープロフィール）
```erb
<p>
  <strong>フォロー数:</strong> <%= link_to @user.following.count, following_user_path(@user) %>
</p>
<p>
  <strong>フォロワー数:</strong> <%= link_to @user.followers.count, followers_user_path(@user) %>
</p>
```
まとめ

1.	モデルでユーザー間のフォロー・フォロワーの関係を定義します。
2.	コントローラーでフォローとフォロー解除の処理を実装します。
3.	ビューでフォロー・フォロワーの一覧やフォローボタンを表示します。