【Rails】フォロー機能を小５でもわかる様に、やさしく解説します！

#フォロー機能を実装しよう

自分のWEBアプリケーションにフォロー機能を
付けたい人多いのではないでしょうか？

ただ、フォロー機能は中間テーブルを用いて多対多を行っているため
初めての人にとっては分かりづらいのも事実。

特にアソシエーションの部分についてを詳しく説明していくので
初学者でも分かりやすく理解できるよう解説していきます。

##多対多とは？

まず実装する前に多対多についてを説明していこうと思います！
この概念を理解していないと？？？が浮かんでくるので
しっかりとおさえてくださいませ！

通常であれば１対多、多対１のような状態であることが多いです。

例) 1対多(投稿機能)
ー－－－－－－－－－－－－－－ー－－－－－

・ユーザー >> 投稿 = 1対多の関係です。
　１人のユーザーに投稿はたくさんつきます。

・投稿 >> ユーザー = 多対１の関係です
　その一方一つの投稿は一人のユーザーに決まります。

ー－－－－－－－－－－－－－－ー－－－－－

まずここまで理解できますでしょうか？
次に多対多の関係を見ていきます。

・
・
・


例) 多対多(フォロー機能)
ー－－－－－－－－－－－－－－ー－－－－－

・ユーザーA >>  ユーザーB
　　　　　　　　ユーザーc
　　　　　　　　ユーザーd
ユーザーAはたくさんの人をフォローしてます。

ー－－－－－－－－－－－－－－ー－－－－－

・ユーザーB >>  ユーザーA
　　　　　　　  ユーザーC
　　　　　　　  ユーザーd
ユーザーBはたくさんの人をフォローしています。


ー－－－－－－－－－－－－－－ー－－－－－

このように片方が１対多にならない関係を
多対多といいます。

フォロー機能がその良い例ですね！
ここまで簡単に多対多について説明をしました。

それでは次から実装にはいっていきましょう！

##実装

⓵ マイグレーションファイル
⓶ モデル
➂ ルーティング
⓸ コントローラー
⓹ ビューファイル

それでは、いってみよー！

##ステップ➀：マイグレーションファイル

一番名グレーションファイルからコードを見ていきましょう！

まずはコードをお見せしますね♪

```php/db/migrate/...create_user.rb

class CreateFavorites < ActiveRecord::Migration[5.2]
  def change
    create_table :favorites do |t|
      t.integer :follow_id, null: false
      t.integer :follower_id, null: false
      t.timestamps
    end
  end
end

```
※ userテーブルは既に作成済みとして進みます。

ちなみにnull falseというのは空欄である場合に
登録をさせないようにするための記述となりますよ。

空欄で保存させないためにも記載をしておきましょう！

できたらrails db migrateを実行です！


##ステップ➁：モデルファイル

次にモデルファイルにコードを記載していきます。
これが少し難しいので理解をしていく必要があります。


```php/app/models/user.rb
class User < ApplicationRecord

  #フォロー機能のアソシエーション
  #follower_id=自分
  #follow_id=相手

上パート
----------------
　#自分がフォローしたり、アンフォローしたりするための記述
  ➀has_many :favorites, class_name: "Favorite", foreign_key: "follower_id", dependent: :destroy
  #sourceは本当はfollow_idとなっていてカラム名を示している。
　#フォロー一覧を表示するための記述
  ⓶has_many :followings, through: :favorites, source: :follow
----------------

下パート
----------------  
  ⓷has_many :reverse_favorites, class_name: "Favorite", foreign_key: "follow_id", dependent: :destroy
  #フォロワー一覧を表示するための記述
  ⓸has_many :followers, through: :reverse_favorites, source: :follower
----------------

```

分かりやすくするために点線と上パートと下パートと記述をしました。
二つのパートを用意してはいますが、フォローとアンフォローだけであれば
上パートの➀だけあれば実はフォローとアンフォローの機能はできてしまいます。

しかしなぜ、⓶⓷⓸はあるの？というお話になると思いますが
これはフォローとフォロワーの一覧を表示するために必要な機能なんですね。

フォローとアンフォローだけであれば➀だけつくればいいですが
フォロー機能をつくったらフォロー数とフォロワー数や一覧なども
みたくなりますよね？

なので、⓶⓷⓸を記述しているわけです。

さて、本題にもどりますが、これらがどんな役割かというと

➀has_many :favorites, class_name: "Favorite", foreign_key: "follower_id", dependent: :destroy
⓷has_many :reverse_favorites, class_name: "Favorite", foreign_key: "follow_id", dependent: :destroy

➀と➂の最初のfavoritesやreverse_favoritesというのは
仮の名前をつけているわけなんですね。

なぜかというと、1対多であればhas_manyというのは、
上パートと下パートと分ける必要がないので、
一つのカラムに指定をすればいいのですが
多対多という場合は中間テーブルというのを用いて
無理やり？テーブル同士の繋がりを実現することになります。

つまり疑似的にuserテーブルを二つあるかのような設定を作り上げます。

一覧表示を表示する際は、
自分がフォローしている人の一覧を表示するためと
自分のことをフォローしてくれている人の一覧を表示するために
上パートと下パートを用意することでそれを実現できます。

しかし現実的にはfavoriteテーブルというのは
一つしかないので、仮の名前を１つ目で作成しているわけなんですね。

次にclass_nameというのは、これが本当のテーブルの名前です。

foreign_keyというのは、外部キー指定というもので
このカラムに保存してくださいとアソシエーションで指定できます。

dependent destroyというのは、
userが削除されたらそれに紐づいているアソシエーションもすべて削除されます。

次にこちらの解説をします。
これらは一覧表示をするために必要になります。
⓶has_many :followings, through: :favorites, source: :follow
⓷has_many :reverse_favorites, class_name: "Favorite", foreign_key: "follow_id", dependent: :destroy

ここでのポイントはthroughとsourceという記載があることです。
throughはfavoritesテーブルを通してという意味になります。
sourceというのは実際のカラムになります。
sourceであればfollowカラムを参照するということなので
favoritesテーブルを通じて自分がフォローしている人すべての人を取得できます。

ちなみにですが、sourceは出口と覚えていただくと分かりやすいと思います。
➀has_many :favorites, class_name: "Favorite", foreign_key: "follower_id", dependent: :destroy
⓶has_many :followings, through: :favorites, source: :follow

この例で行くと、foreign_keyがfollower_idで入口で、source: :followが出口です。
従いまして、自分がフォローしている人一覧を表示する際は
まず自分のidなので、follower_idを探してきて、それに紐づくsourceのfollowキーを
railsが引っ張ってくれるわけですね。

自分のことをフォローしてくれている人を表示したい時は
逆の場合も同じように考えればいいわけです！

```php/app/models/favorite.rb

class Favorite < ApplicationRecord
  # userへのアソシエーション
  # class_nameがuserモデルとリンクしている(class_nameは大文字でなければNG）
  belongs_to :follow, class_name: "User"
  belongs_to :follower, class_name: "User"

```

1対多であれば、上記の場合であれば
belongs_to: userという記述になりますが
favoriteモデルの場合は多対多になるため
belongs_toが二つ必要になります。

なぜかというと、userモデルでsourceを指定しているので
書かないと繋がりをつくれなくなってしまうからですね。

class_nameはユーザーモデルを示しています。

さて、フォロー機能で一番難しいといっても過言ではない
モデル間のアソシエーション機能については完了となります。

次はルーティングをみていくとしましょう。

##ステップ⓷：ルーティングファイル

それでは、ステップ⓷はルーティングファイルを記載していきます。

```php/config/routes.rb
Rails.application.routes.draw do

    # users用のURL用に設定
    resources :users, only: [:edit, :update] do
      # フォロー機能はuserにネストさせている
      resource :favorites, only: [:create, :destroy]
      get 'followings' => 'favorites#followings', as: 'followings'
      get 'followers' => 'favorites#followers', as: 'followers'
    end

```

resource :favorites, only: [:create, :destroy]は、
フォローとアンフォローをするだけあればOKなのでcreateとdestroyだけを記述しています。

そしてその下は、一覧表示するための記述です。

 get 'followings' => 'favorites#followings', as: 'followings'
 get 'followers' => 'favorites#followers', as: 'followers'

user_idに基づいて、フォローしたりアンフォローしたりするので
user_idネストをすることでurlにidを含めることができます。

##ステップ⓸：コントローラー

さて、マイグレーションファイル、モデル、ルーティングが完了したら
次はコントローラーを記述していきます。


```php/app/controllers/favorites_controller.rb
class FavoritesController < ApplicationController

  # フォローアンフォロー処理
  def create
    # params[:user_id]これはリンクから送られてきたuser_idをparamsで受け取っている
    # そして受け取った値をモデルのメソッドに受け渡している
    @user = User.find(params[:user_id])
    current_user.follow(params[:user_id])
    #フォローの通知機能
    @user.create_notification_follow!(current_user)
    redirect_to request.referer
  end

  def destroy
    current_user.unfollow(params[:user_id])
    redirect_to request.referer
  end

  # フォローフォロワー一覧処理
  def followings
    user = User.find(params[:user_id])
    @users = user.followings
  end

  def followers
    user = User.find(params[:user_id])
    @users = user.followers
  end
end

```

まずは、フォローアンフォローの機能からみていきましょう。

  def create
    current_user.follow(params[:user_id])
  end

  def destroy
    current_user.unfollow(params[:user_id])
    redirect_to request.referer
  end

フォロー機能はこれだけでできます。
crateとdestroyで何をやっているかというと
current_userでfollowメソッドを走らせてと行っています。
そしてfollowにはuser_idをひきわたしてあげます。

このfollowというのは何かというと
モデルに自作することができるメソッドのことです。

モデルにメソッドを書くことでコントローラー内の
記述がスッキリするのでモデルにメソッドをかけるのであれば
モデルにメソッドを記述していきましょう！

モデル内に書いたメソッドはこれです。


```php/app/controllers/favorites_controller.rb
class User < ApplicationRecord

  def follow(user_id)
    favorites.create(follow_id: user_id)
  end

  def unfollow(user_id)
    favorites.find_by(follow_id: user_id).destroy
  end

```

さて、コントローラーの記述に戻りますが
下記のfollowというのと、モデルのfollowが一致していますね。

current_user.follow(params[:user_id])
  
def follow(user_id)
    favorites.create(follow_id: user_id)
end

つまり、コントローラーのfollowはモデルの自作したfollowメソッドを
引っ張ってきているわけなんですね。

通常であればコントローラーに.updateや.createと
記載をすると思うのですがモデルにモデルにかけるので
モデルに.createを記載していきます。

コントローラーでcurrent_userという記述をしているので
現在ログインしているユーザーの情報をもったまま
モデルに書いたメソッドにログインユーザーの情報を引き渡して
モデルに書いた処理を実行することができます。

フォローボタンを押した際にフォローした相手のuser_idが
follow_idカラムに保存され、follower_idにはcurrent_userのidが
自動で入るようにrailsがおこなってくれるわけですね。

favorites.create(follow_id: user_id)

逆も同じような考えて削除するときも同じように削除を行います。


  # フォローフォロワー一覧処理
  def followings
    user = User.find(params[:user_id])
    @users = user.followings
  end

  def followers
    user = User.find(params[:user_id])
    @users = user.followers
  end

フォロー一覧をする際にはUser.findをまずして
誰がuser_idなのかを突き止めてから
user.followingsでアソシエーションをして
user_idがフォローしている人一覧を取得します。

これにてコントローラーの解説は完了となります。
最後はviewファイルをみていきますよ！

##ステップ⑤：viewファイル

最後はいよいよビューファイルをみていきます！

まずは、フォロー機能とフォロー一覧機能で
分けてみていきましょう！

```php/app/views/users/friends.html.erb

<% @friends.each do |friend| %>
    <tr>
        <td class="text-center"><%= link_to friend_posts_path(friend) do %>
          <%= attachment_image_tag friend, :profile_image, fallback: "no_image.jpg", size: '70x70', class:"mt-3 rounded-circle"; %>
        <% end %>
        </td>
        <td class="text-center align-middle"><%= friend.nickname %></td>
        <td class="text-center align-middle"><%= friend.height %></td>
        <td class="text-center align-middle"><%= friend.weight %></td>                
        <td class="text-center align-middle"><%= friend.introduction %></td>  
        <!--ログインユーザーでない場合にフォローボタン表示-->
        <% if friend != current_user %>
            <td class="text-center align-middle"><% if current_user.following?(friend) %>
            <%= link_to "Unfollow", user_favorites_path(friend), method: :delete %>
        <% else %>
            <%= link_to "Follow", user_favorites_path(friend), method: :post %>
        <% end %>
            </td>
        <% end %>
        </td>
    </tr>
<% end %>

```

eachの@friendsは、userコントローラー内のfriendsコントローラーで
index(または自作)アクション内に指定をしたインスタンス変数を指定してあげます。

```php/app/controllers/users_controller.rb

  def friends
    @friends = User.all
  end

```

eachではUserの情報が入っているのでブロック変数のfriendを
link_toで当ててあげましょう。


<% if friend != current_user %>
  <td class="text-center align-middle"><% if current_user.following?(friend) %>
  <%= link_to "Unfollow", user_favorites_path(friend), method: :delete %>
<% else %>
<%= link_to "Follow", user_favorites_path(friend), method: :post %>
<% end %>

フォローアンフォロー機能を作成するには、link_toを記載して
methodでdeleteかpostを指定してあげることで、
favoritesコントローラーのfollow、unfollowアクションを
走らせることができます。

そして、もう一つの肝となるのが、
フォロー機能を判定するメソッドです。

<% if current_user.following?(friend) %>

これはuserモデルにこのメソッドを記載しています。
followingsは、アソシエーションになるので
followingsはfollowカラムを参照するので
自分がフォローしているユーザーを探すことができます。

(user)の引数には相手のidが入ってくるため
<% if current_user.following?(friend) %>
でcurrent_userと指定していることで
自分のidがフォローしているかincludeで判定をします。

  # 引数そのものを検索する
  # フォローを判定しているメソッド
  def following?(user)
    followings.include?(user)
  end

自分が相手をフォロしていればunfollowを表示できるようになり
そうでなければfollowと表示をviewの分岐で行います。

これにて、フォロー機能は完成です！

##まとめ

いかがでしたでしょうか？

フォロー機能は少し複雑にはなりますが
ポイントはアソシエーションの理解になります。

ここさえ掴めれば自分でも理解に落とし込めるはずです。

何度も復習してみてくださいね！

それでは、ちゃお！