Railsでユーザーフォロー機能を実装する（Ajax使うよ）②

## 何をしたか
この記事は、こちらの記事の続きです。

▼前回の記事
[Railsでユーザーフォロー機能を実装する（Ajax使うよ）①](https://qiita.com/tanutanu/items/a4a33554cb0e873720e9)

Railsの課題を実施しています。`User`のフォロー機能を実装することになりました。
前回までの記事では、マイグレーションファイルの作成と、モデルファイルへのアソシエーションの定義が終わったので、今回は`controller`と`view`、そして**モデルメソッド**を追加していきます。

なお、実行環境は以下の通りです。

- `Rails 5.2.3`
- `Ruby 2.6.0`

## ゴール
今回のゴールはこんな感じです。ボタンを押すとサクサクと非同期でフォロー／フォロー解除のボタンが現れます。

[![Image from Gyazo](https://i.gyazo.com/3bc3c8d87d9eb1c460d1894f6ba32783.gif)](https://gyazo.com/3bc3c8d87d9eb1c460d1894f6ba32783)

## 仕様
この機能の使用は以下の通りです。

- ユーザーは他のユーザーをフォローできる
- 同じユーザーを2回フォローはできない
- ユーザーは自分をフォローできない

## データ構造
また、DBの設計は下記の通りです。
[![Image from Gyazo](https://i.gyazo.com/e6915dc3624821fd7b502bbbef8b8a71.png)](https://gyazo.com/e6915dc3624821fd7b502bbbef8b8a71)

なぜこうなるのかは、[前回の記事]()を読んでくださいね:relaxed:

## 非同期ではない実装
まずは、非同期ではない形で実装していきます。
`controller`、`model`ともにさらっと書いていきますので、詳しく知りたい方はこの辺の記事をご覧になると良いかもしれません。

- [Railsで「いいね！」機能を作る - ②「いいね！」のcreateアクション](https://qiita.com/tanutanu/items/bff95ce3a91dab1eb2c6)
- [Railsで「いいね！」機能を作る - ③「いいね！」を解除できるようにする](https://qiita.com/tanutanu/items/89cd52ed57b487fed84d)
- [RailsでAjaxで「いいね！」機能を実装する。](https://qiita.com/tanutanu/items/b435af08aff706e01f2a)

### controller

`controller`の記載は以下の通りです。

```ruby:relationships_controller.rb
class RelationshipsController < ApplicationController
  def create
    @other_user = User.find(params[:follower])
    current_user.follow(@other_user)
  end

  def destroy
    @user = current_user.relationships.find(params[:id]).follower
    current_user.unfollow(params[:id])
  end
end
```

`follow`、`unfollow`はモデルメソッドです。後ほど解説します。

### view
ビューファイルの記述は以下の通りです。なお、装飾のためのクラスは省いています。なお、`user`（それぞれのユーザー）を表す変数がここに表示しているコードの外から渡っているものとします。

```ruby:view
- if logged_in? && current_user != user
  - if current_user.following?(user)
    = button_to 'フォロー解除', relationship_path(current_user.relationships.find_by(follower: user)), method: :delete
  - else
    = button_to 'フォロー', relationships_path(follower: user)
```
`following?`もモデルメソットです。

### model

`user`モデルのモデルメソッドとして、`follow`、`unfollow`、`following?`メソッドを、それぞれ定義します。

```ruby:
class User < ApplicationRecord
  # 前回の記事で定義した部分
  has_many :relationships, dependent: :destroy
  has_many :followings, through: :relationships, source: :follower

  has_many :passive_relationships, class_name: 'Relationship', foreign_key: 'follower_id', dependent: :destroy
  has_many :followers, through: :passive_relationships, source: :user

  # 今回追記したモデルメソッド
  def follow(other_user)
    return if self == other_user

    relationships.find_or_create_by!(follower: other_user)
  end

  def following?(user)
    followings.include?(user)
  end

  def unfollow(relathinoship_id)
    relationships.find(relathinoship_id).destroy!
  end
end
```

なお、このクラスの上方にあるのは、[前回の記事で適宜した複雑なアソシエーション](https://qiita.com/tanutanu/items/a4a33554cb0e873720e9)です。

### 非同期ではない通信での実装完了

ここまでで、Ajaxではないフォロー機能は完成しています。デモ用に`redirect_to`で画面を遷移させているので、若干非同期っぽくも見えますが、「フォロー」ボタンを押して画面をリロード後「フォロー解除」ボタンが現れます。

[![Image from Gyazo](https://i.gyazo.com/e32fdc2223b3a7ec603bccfe8b0a5aed.gif)](https://gyazo.com/e32fdc2223b3a7ec603bccfe8b0a5aed)

## Ajaxでの実装
それでは、これを非同期の通信にしていきます。こちらもさらっと書いていますので、どうしてそうなるのかが気になる方は、こちらの記事をご覧ください。

[remote: trueでajaxの投稿をPOSTをするよ。](https://qiita.com/tanutanu/items/61e837c1cf1efc618779)

### html部分

まずは、ボタン部分は`remote: true`オプションをつけて、パーシャルに切り出します。

```ruby:views/relationships/_follow_button.html.slim
= button_to 'フォロー', relationships_path(follower: user), remote: true
```

```ruby:views/relationships/_unfollow_button.html.slim
= button_to 'フォロー解除', relationship_path(current_user.relationships.find_by(follower: user)), method: :delete, remote: true
```
```Ruby
# もともとボタンのあったビューファイル
- if logged_in? && current_user != user
 div id="follow-button-#{user.id}"
   - if current_user.following?(user)
      = render 'relationships/unfollow_button', user: user
   - else
      = render 'relationships/follow_button', user: user
```

### `**.js.erb` ファイルを作る

動的に呼び出す部分のファイルは、`*＊.js.erb`ファイルに書き出します。

```erb:views/relationships/create.js.erb
$("#follow-button-<%= @other_user.id %>").html("<%= j(render 'unfollow_button', user: @other_user) %>")
```

```erb:views/relationships/destroy.js.erb
$("#follow-button-<%= @user.id %>").html("<%= j(render 'follow_button', user: @user) %>")
```

## 完成！

こちらで完成です＾＾Ajaxにするのはビューファイルを書き換えるだけで終わりました:relaxed:
シンプルー:sparkles:

[![Image from Gyazo](https://i.gyazo.com/3bc3c8d87d9eb1c460d1894f6ba32783.gif)](https://gyazo.com/3bc3c8d87d9eb1c460d1894f6ba32783)

## 感想など

フォロー・フォロー解除機能は、データ構造８割！といった感じで、DBとモデルの設計がやたら複雑なんですね〜
しっかり読み解けてよかったです＾＾