【Rails】Ajaxを用いた非同期フォロー機能の実装

#目標

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/1c198f23-9b62-3a1d-302d-d9689ba47d79.gif)

# 開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

# 前提

下記実装済み。

・[Slim導入](https://qiita.com/matsubishi5/items/b639ffddecc626856039)
・[Bootstrap3導入](https://qiita.com/matsubishi5/items/b117c95f4dd71340cc80)
・[ログイン機能実装](https://qiita.com/matsubishi5/items/5bd8fdd45af955cf137d)

# フォロー機能を実装

### 1.モデル

```terminal:ターミナル
$ rails g model Relationship follower_id:integer followed_id:integer
```

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

```ruby:schema.rb
ActiveRecord::Schema.define(version: 2020_04_05_115005) do
  create_table "relationships", force: :cascade do |t|
    t.integer "follower_id"
    t.integer "followed_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end  
end
```

```ruby:user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable,:validatable

  has_many :books, dependent: :destroy
  has_many :follower, class_name: 'Relationship', foreign_key: 'follower_id', dependent: :destroy
  has_many :followed, class_name: 'Relationship', foreign_key: 'followed_id', dependent: :destroy
  has_many :following_user, through: :follower, source: :followed
  has_many :follower_user, through: :followed, source: :follower

  # ユーザーをフォローする
  def follow(user_id)
    follower.create(followed_id: user_id)
  end

  # ユーザーをアンフォローする
  def unfollow(user_id)
    follower.find_by(followed_id: user_id).destroy
  end

  # フォローしているかを確認する
  def following?(user)
    following_user.include?(user)
  end
end
```
① `has_many :follower, class_name: 'Relationship', foreign_key: 'follower_id', dependent: :destroy`
➡︎ Relationshipモデルのfollower_idにuser_idを格納

②`has_many :followed, class_name: 'Relationship', foreign_key: 'followed_id', dependent: :destroy`
➡︎ Relationshipモデルのfollower_idにuser_idを格納

③ `has_many :following_user, through: :follower, source: :followed`
➡︎ 自分がフォローしているユーザー

④ `has_many :follower_user, through: :followed, source: :follower`
➡︎ 自分をフォローしているユーザー

```ruby:relationship.rb
class Relationship < ApplicationRecord
  belongs_to :follower, class_name: 'User'
  belongs_to :followed, class_name: 'User'
end
```

### 2.コントローラー
```terminal:ターミナル
$ rails g controller relationships
```

```ruby:relationships_controller.rb
class RelationshipsController < ApplicationController
  # フォローする
  def follow
    current_user.follow(params[:id])
    redirect_to users_path
  end

  # アンフォローする
  def unfollow
    current_user.unfollow(params[:id])
    redirect_to users_path
  end
end
```

```ruby:users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end

  # 自分がフォローしているユーザー一覧
  def following
    @user = User.find(params[:user_id])
    @followings = @user.following_user.where.not(id: current_user.id)
  end

  # 自分をフォローしているユーザー一覧
  def follower
    @user = User.find(params[:user_id])
    @followers = @user.follower_user.where.not(id: current_user.id)
  end
end
```

### 3.ルーティング

```ruby:routes.rb
Rails.application.routes.draw do
  post 'follow/:id', to: 'relationships#follow', as: 'follow'
  post 'unfollow/:id', to: 'relationships#unfollow', as: 'unfollow'
  get 'users/following/:user_id', to: 'users#following', as:'users_following'
  get 'users/follower/:user_id', to: 'users#follower', as:'users_follower'
end
```

### 4.ビュー

```slim:users/index.html.slim
.row
  .col-xs-2

  .col-xs-8
    table.table
      thead
        tr
          th
            | 氏名
          th
            | 自己紹介
          th
          th
          th

      tbody
        - @users.each do |user|
          tr
            td
              = link_to user do
                = user.name
            td
              = user.introduction
            td
              / 自分がフォローしているユーザー一覧
              = link_to "#{ user.follower.count }フォロー中", users_following_path(user)
            td
              / 自分をフォローしているユーザー一覧
              = link_to "#{ user.followed.count }フォロワー", users_follower_path(user)
            td
              - unless user == current_user
                - if current_user.following?(user)
                  = link_to 'フォロー解除', unfollow_path(user), method: :post, class: 'btn btn-info'

                - else
                  = link_to 'フォローする', follow_path(user), method: :post, class: 'btn btn-default'


  .col-xs-2
```

# 非同期機能を実装

### 1.jQueryを導入

```ruby:Gemfile
    gem 'jquery-rails'
```

```terminal:ターミナル
    $ bundle
```

```js:application.js
    //= require rails-ujs
    //= require activestorage
    //= require turbolinks
    //= require jquery
    //= require_tree .
```

### 2.ビューを編集

```slim:users/index.html.slim
/ 各フォロー中一覧のリンクにIDを付与
td id='following-count_#{ user.id }'
  = link_to "#{ user.follower.count }フォロー中", users_following_path(user)
/ 各フォロワー一覧のリンクにIDを付与
td id='follower-count_#{ user.id }'
  = link_to "#{ user.followed.count }フォロワー", users_follower_path(user)
td
  - unless user == current_user
    / 各フォローボタンにIDを付与
    span id='follow-button_#{ user.id }'
      / フォローボタンをパーシャル化
      = render 'users/follow-button', user: user
```

```slim:users/_follow-button.html.slim
/ フォローボタンに「remote: true」を付与
- if current_user.following?(user)
  = link_to 'フォロー解除', unfollow_path(user), method: :post, class: 'btn btn-info', remote: true

- else
  = link_to 'フォローする', follow_path(user), method: :post, class: 'btn btn-default', remote: true
```

`「remote: true」`を付与する事で、JavaScriptファイルを呼び出せる様になる。

### 2.JavaScriptファイルを作成

```js:users/follow.js.erb
$("#follow-button_<%= @user.id %>").html("<%= j(render 'users/follow-button', user: @user) %>");
$("#follower-count_<%= @user.id %>").html('<%= link_to "#{ @user.followed.count }フォロワー", users_follower_path(@user) %>');
$("#following-count_<%= current_user.id %>").html('<%= link_to "#{ current_user.follower.count }フォロー中", users_following_path(@user) %>');
```

```js:users/unfollow.js.erb
$("#follow-button_<%= @user.id %>").html("<%= j(render 'users/follow-button', user: @user) %>");
$("#follower-count_<%= @user.id %>").html('<%= link_to "#{ @user.followed.count }フォロワー", users_follower_path(@user) %>');
$("#following-count_<%= current_user.id %>").html('<%= link_to "#{ current_user.follower.count }フォロー中", users_following_path(@user) %>');
```

`$("#follow-button_<%= @user.id %>").html("<%= j(render 'users/follow-button', user: @user) %>");`
➡︎ フォローボタンを非同期で処理

`$("#follower-count_<%= @user.id %>").html('<%= link_to "#{ @user.followed.count }フォロワー", users_follower_path(@user) %>');`
➡︎ フォロワーカウント数を非同期で処理

`$("#following-count_<%= current_user.id %>").html('<%= link_to "#{ current_user.follower.count }フォロー中", users_following_path(@user) %>');`
➡︎ フォロワー中カウント数を非同期で処理