Railsでフォロー機能を作成する方法【改】

#前提
☆必要なテーブル
・usersテーブル
・relationshipsテーブル（中間テーブルです）
☆ポイント
・アソシエーションが普通の「多対多」とは違う事（ちゃんと解説します！！）
・<b>Userモデル、usersテーブルは作っている前提で説明します！！</b>
#流れ
⓵relationshipsモデルを作る

⓶relationshipsのマイグレーションファイルを編集&実行

⓷userモデルとrelationshipsモデルにアソシエーションを書く

⓸userモデルにフォロー機能のメソッドを書く

⓹relationshipsコントローラを作成&編集

⓺フォローボタン（form_for）をviewに設置

⓻ルーティングを書く！終了！

#⓵relationshipsモデルを作る
今回はuserとtweetsの関係性とは違い、<b>userテーブル同士で「多対多」の関係を作ります。何故ならフォロワーもまたuserだからです。イメージとしてはuserテーブル同士をrelationshipsという中間テーブルでアソシエーションを組むイメージです！</b>
まずは、realtionshipsモデルを作っていきます。

```ruby:ターミナル
$ rails g model Relationship
```
#⓶relationshipsのマイグレーションファイルを編集&実行
下記のように編集してください。

```ruby:db/migrate/年月日時_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.0]
  def change
    create_table :relationships do |t|
      t.references :user, foreign_key: true
      t.references :follow, foreign_key: { to_table: :users }

      t.timestamps

      t.index [:user_id, :follow_id], unique: true
    end
  end
end
```
しっかり解説します。まず、relationshipsテーブルのカラムは

| カラム | タイプ | オプション |
|:-----------|------------:|:------------:|
| user_id     |   integer |    foreign_key: true    |
| follow_id   |       integer |   foreign_key:{to_table: users}   |

となります。
そもそも、relationshipsテーブルは中間テーブルなので、```user_id```と```follow_id```は「```t.references```」で作ってあげる必要があります。
そして、外部キーとしての設定をするためにオプションは「```foreign_key: true```」とします。

でも！注意したいのが```follow_id```の参照先のテーブルはusersテーブルにしてあげたいので、```{to_table: :users}```としてあげてます。
```foreign_key: true```にすると存在しないfollowsテーブルを参照してしまうからです。

```t.index [:user_id, :follow_id], unique: true``` は、 ```user_id``` と ```follow_id``` のペアで重複するものが保存されないようにするデータベースの設定です！
これは、あるユーザがあるユーザをフォローしたとき、フォローを解除せずに、重複して何度もフォローできてしまうような事態を防いでいるだけです。

終わったら、マイグレーションファイルを実行してください！

```ruby:ターミナル
$ rails db:migrate
```

#⓷relationshipsモデルとuserモデルにアソシエーションを書く
まずは、relationshipsモデルにアソシエーションを書いていきます！

```ruby:app/models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :user
  belongs_to :follow, class_name: 'User'

  validates :user_id, presence: true
  validates :follow_id, presence: true
end
```

```class_name: ‘User’``` と補足設定することで、Followクラスという存在しないクラスを参照することを防ぎ、User クラスであることを明示しています。
要は「followモデルなんて存在しないので、userモデルにbelongs_toしてね！」って事です。
さらに、バリデーションも追加してどちらか一つでも無かった場合保存されないようにします！

次にuserモデルにアソシエーションを書いていくのですが、、、

<b>ここが山場です。理解しにくい部分なのでしっかり解説します。
</b>

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :relationships
  has_many :followings, through: :relationships, source: :follow
  has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: 'follow_id'
  has_many :followers, through: :reverse_of_relationships, source: :user
end
```
１行目の```has_many :relationships```は大丈夫ですね。

2行目の```has_many :followings```とありますが、これはいまこのタイミングで命名したものです！followingクラス（モデル）を架空で作り出しました。
勿論、followingクラス（モデル）なんて存在しません。
なので、補足を付け足す必要があります。
```through: :relationships``` は「中間テーブルはrelationshipsだよ」って設定してあげてるだけです。
```source: :follow```とありますが、これは
「relationshipsテーブルの```follow_id```を参考にして、followingsモデルにアクセスしてね」って事です。
結果として、user.followings と打つだけで、user が中間テーブル relationships を取得し、その1つ1つの relationship の```follow_id```から、「フォローしている User 達」を取得しています。

次にフォロワー（フォローされているuser達）をとってくるための記述をします。

<b>結論から言うとフォローの逆をしてあげればいいのです。</b>

3行目の```has_many :reverse_of_relationships```は
```has_many :relaitonships```の<b>「逆方向」</b>って意味です。
これはこのタイミングで命名したものです。勿論reverse_of_relationshipsなんて中間テーブルは存在しません。なので、これも補足を付け足してやります。
```class_name: 'Relationship'```で「relationsipモデルの事だよ〜」と設定してあげます。

次の```foreign_key: 'follow_id'```ですが、これ何のこっちゃ分からないと思います。
これ、「relaitonshipsテーブルにアクセスする時、```follow_id```を入口として来てね！」っていう事です。

ちょっと1行目の```has_many :relationships```を思い出してください。実はこれ

```ruby
has_many :relationships, foreign_key: 'user_id'
```
って意味なんです！
要はこれも```user_id```を入り口にしてね、っていうだけです！
```user_id```を入口として、relationshipsテーブルという家に「おじゃましま〜す」と入って、```follow_id```という出口（＝source: :follow）から出て、followingsテーブルからフォローしている人のデータをとってくるイメージです！
<b>
foregin_key = 入口
source = 出口
</b>
というのを念頭においてください。
<b>これで100％理解出来るはずです。</b>




４行目に行きます。```has_many :followers```もこのタイミングで命名してます。勿論、followersなんてクラス存在しません。
```through: :reverses_of_relationship```で「中間テーブルはreverses_of_relationshipにしてね」と設定し、
```source: :user```で「出口は```user_id```ね！それでuserテーブルから自分をフォローしているuserをとってきてね！」と設定してます。

#⓸userモデルにフォロー機能のメソッドを書く
userモデルにフォロー機能のメソッドを書いておきます。
これやった方が後々めちゃくちゃ楽です。

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :relationships
  has_many :followings, through: :relationships, source: :follow
  has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: 'follow_id'
  has_many :followers, through: :reverse_of_relationships, source: :user

  def follow(other_user)
    unless self == other_user
      self.relationships.find_or_create_by(follow_id: other_user.id)
    end
  end

  def unfollow(other_user)
    relationship = self.relationships.find_by(follow_id: other_user.id)
    relationship.destroy if relationship
  end

  def following?(other_user)
    self.followings.include?(other_user)
  end

end
```

注意すべき点は、フォローが自分自身ではないか？とすでにフォローしていないか？の２点です！！！！

```def follow``` では、```unless self == other_user``` によって、フォローしようとしている other_user が自分自身ではないかを検証しています。self には ```user.follow(other)``` を実行したとき user が代入されます。つまり、実行した User のインスタンスが self です！
更に、```self.relationships.find_or_create_by(follow_id: other_user.id)``` は、見つかれば Relation を返し、見つからなければ self.relationships.create(follow_id: other_user.id) としてフォロー関係を保存(create = new + save)することができます。これにより、既にフォローされている場合にフォローが重複して保存されることがなくなります！

```def unfollow``` では、フォローがあればアンフォローしています。また、```relationship.destroy if relationship ```は、relationship が存在すれば destroy します！if文はこのように書けます！

```def following?``` では、```self.followings``` によりフォローしている User 達を取得し、```include?(other_user)``` によって other_user が含まれていないかを確認しています！含まれている場合には、true を返し、含まれていない場合には、false を返します！



#⓹relationshipsコントローラを作成&編集
relationshipsコントローラ作ってください。

```ruby:ターミナル
$　rails g controller relationships
```

下記のように書いていきます。

```ruby:app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :set_user

  def create
    following = current_user.follow(@user)
    if following.save
      flash[:success] = 'ユーザーをフォローしました'
      redirect_to @user
    else
      flash.now[:alert] = 'ユーザーのフォローに失敗しました'
      redirect_to @user
    end
  end

  def destroy
    following = current_user.unfollow(@user)
    if following.destroy
      flash[:success] = 'ユーザーのフォローを解除しました'
      redirect_to @user
    else
      flash.now[:alert] = 'ユーザーのフォロー解除に失敗しました'
      redirect_to @user
    end
  end

  private
  def set_user
    @user = User.find(params[:follow_id])
  end

end

```

#⓺フォローボタン（form_for）をviewに設置


```html:app/views/relationships/_follow_button.html.erb
<% unless current_user == user %>
  <% if current_user.following?(user) %>
    <%= form_for(current_user.relationships.find_by(follow_id: user.id), html: { method: :delete }) do |f| %>
      <%= hidden_field_tag :follow_id, user.id %>
      <%= f.submit 'Unfollow', class: 'btn btn-danger btn-block' %>
    <% end %>
  <% else %>
    <%= form_for(current_user.relationships.build) do |f| %>
      <%= hidden_field_tag :follow_id, user.id %>
      <%= f.submit 'Follow', class: 'btn btn-primary btn-block' %>
    <% end %>
  <% end %>
<% end %>
```

※hamlで書くと下のような感じになります！

```ruby
- unless current_user == user
  - if current_user.following?(user)
    = form_for(current_user.relationships.find_by(follow_id: user.id), html: { method: :delete }) do |f|
      = f.hidden_field :follow_id, value: user.id
      = f.submit 'フォロー中', class: 'follow-now'
  - else
    = form_for(current_user.relationships.build) do |f|
      = f.hidden_field :follow_id, value: user.id
      = f.submit 'フォロー', class: 'follows'


```

あとは部分テンプレートでご自身の好きなところに置いちゃって下さい！！！

```html:app/views/users/show.html.erb
<%= render 'relationships/follow_button', user: @user %>
```

みたいな感じで！
#⓻ルーティングを書く！終了！


```ruby:config/routes.rb
Rails.application.routes.draw do
  resources :relationships, only: [:create, :destroy]
end
```


ありがとうございました！