【Rails】フォロー機能

#フォロー機能
##準備
・userテーブル
・relationshipsテーブル（usersテーブルとusersテーブルの間に入るテーブル）

##relationshipsのマイグレーションファイルを編集&実行
```
$ rails g model Relationship
```
relationshipsモデルを作成し、マイグレーションファイルを編集&実行します。

```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
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597913/53c3f4c8-0521-607f-e667-34654ee24adb.png)
上記がrelationshipsテーブルのカラムになります。

user_idテーブルとfollow_idテーブルを関連づけるため、t.referencesと記載する必要があります。
外部キーを設定するためにforeign_key: trueを追記する。

followキーの参照テーブルとしては`{ to_table: :users }`を追記します。
※`foreign_key: ture`だと無いはずのフォローテーブルを参照してしまいます。

`t.index [:user_id, :follow_id], unique: true`はユーザIDとフォローIDのペアで重複して保存されるのを防いでいる。

`rails db:migrate`でマイグレーションファイルを実行する。

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

```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クラスという本来無いクラスへの参照を防いでいます。
要は「followモデルは存在しないため、userモデルにbelongs_toをする」ということになります。

```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 :followings, through: :relationships, source: :follow
```
`has_many :followings`この部分は`followingクラス（モデル）`を架空で作成しています。
そのあとに`through :relationships`と記載しrelationshipsテーブルを中間に設定しています。

 `source: :follow`は「relationshipsテーブルの`follow_ID`を参考にしてfollowingsモデルにアクセス」を指定しています。

なので、`user.followings`と打つだけでuserが中間テーブル`relationships`を取得し、１つ１つのrelationshipsの`follow_id`から「フォローしているユーザ達」を取得しています。

###次にフォロワー（フォローされているuser達）を取って来るための記述
３行目の`has_many :reverse_of_relationships`は`has_many :relationships`の「逆方向」の意味になります。
`user_id`を入り口に`follow_id`という出口から出てfollowingsテーブルからフォローしてくれる人のデータを取ってくる。

```
foregin_key = 入口
source = 出口
```
この概念を持っておくと理解しやくなります。

`has_many :followers`と命名していますがfollowersクラスはありません。

`through: :reverse_of_relationship`で「中間テーブルはreverses_of_relationshipにしてね」と設定し、`source: :user`で「出口はuser_id、userテーブルから自分をフォローしているuserをとってくる」と設定している。

###userモデルにフォロー機能のメソッドを記載する
userモデルにフォロー機能メソッドを記載する。

```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(other_user)
    unless self == other_user
self.relationships.find_or_create_by(follow_id: other_user.id)
    end
  end
```
`unless self == other_user`によってフォローしようとしているother_userが自分自身では無いかを検証している。selfには`user.follow(other)`を実行したときuserが代入されます。つまり実行したUserのインスタンスがselfになります。

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

`def follow`では`unless self == other_user`によってフォローしようとしている。ohter_userが自分自身では無いかを検証しています。
selfには`user.follow(other)`を実行したときuserが代入されます。実行したUserのインスタンスがselfになります。

`self.relationships.find_or_create_by(follow_id: other_user.id)`は見つかれば`relation`を返し、見つからなければ`self.relationships.find_or_create_by(follow_id: other_user.id)`としてフォロー関係を`self.relationships.create(follow_id: other_user.id) `としてフォロー関係を保存することができる。これによってフォローされている場合にフォローが重複して保存されることがなくなります。

`def unfollowing`ではフォローがあればアンフォローしてくれます。また`relationship.destroy if relationship`はrelationshipが存在すればdestroyする。

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

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

```
$　rails g controller relationships
```
まずはrelationshipsコントローラを作成しています。

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

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

  def destroy
    user = User.find(params[:relationship][:follow_id])
    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[:relationship][:follow_id])
  end

end
```
コントローラに上記を記載します。

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

```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 %>
```

'''
Rails.application.routes.draw do
  resources :relationships, only: [:create, :destroy]
end
'''
あとはルーティングを記載して完了です。

#参考URL
https://qiita.com/MitsuguSueyoshi/items/e41e2ff37f143db81897