Railsでフォロー機能を実装した（Ajax通信）

#概要
SNSなどユーザーを扱うアプリケーションではフォロー機能などはよく扱われる。Railsにてフォロー機能を実装する際は、多対多の関連付けをしてユーザー通しを関連付けます。
今回、ユーザー一覧画面(index)とユーザー詳細画面(show)にてフォロー機能を実装した。
また、ユーザビリティを考慮し、Ajax通信にてフォロー機能が使用できるようにした。

#前提
* Userモデルは作成済み
* JQueryはインストール済み

#1.Relationshipモデルの作成
ユーザー通しを多対多の関連付けを行うためRelationshipモデルを作成します。RelationshipモデルはUserモデルの中間テーブルとして作成し、Userモデルのidを扱います。

```terminal:terminal
rails g model Relationship
```

```ruby:create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.2]
  def change
    create_table :relationships do |t|
      t.references :follower, foreign_key: { to_table: :users }
      t.references :following, foreign_key: { to_table: :users }

      t.timestamps
    end
    add_index :relationships, [:follower_id, :following_id], unique: true
  end
end
```

"references :〇〇"と設定することで〇〇モデル(テーブル)のidを〇〇_idというカラム名に自動的に設定することができます。このままではfollowerやfollowingテーブルという存在しないテーブルを参照してしまうため、外部キー"foreign_key: { to_table: :users }"と設定し、Userテーブルを参照するように設定します。
ユーザー通しの関連性を保存するように設定し、同じ組み合わせを保存しないように"unique:true"と設定します。
設定後、db:migrateでRelationshipテーブルを作成します。

#2.モデルの編集
続いてRelationshipモデルとUserモデルを編集します。

```ruby:relationship.rb
belongs_to :follower, class_name: "User"
belongs_to :following, class_name: "User"

validates :follower_id, presence: true
validates :following_id, presence: true
```

"belongs_to :follower"と設定しますが、followerテーブルは存在しないため"class_name: "User""でUserテーブルを参照するようにします。

```ruby:user.rb
has_many :following_relationships, foreign_key: "follower_id", class_name: "Relationship", dependent: :destroy
has_many :followings, through: :following_relationships
has_many :follower_relationships, foreign_key: "following_id", class_name: "Relationship", dependent: :destroy
has_many :followers, through: :follower_relationships
```

"has_many :following_relationships"でidを取得しますが、"following_relationships_id"は存在しないので、"class_name: "Relationship""でrelationshipテーブルを参照、"foreign_key: follower_id"で外部キーを設定し、"follower_id"を取得するよう設定します。"dependent: :destroy"でUserが削除された際、relationshipのidも削除するようになります。
”has_many :followings, through: :following_relationships"でfollowing_relationshipsを通して、ユーザーidを取得します。
逆(follower)も同じ設定です。

```ruby:user.rb
def following?(other_user)
  self.followings.include?(other_user)
end

def follow(other_user)
  self.following_relationships.create(following_id: other_user.id)
end

def unfollow(other_user)
  self.following_relationships.find_by(following_id: other_user.id).destroy
end
```

フォロー機能に関するメソッドを作成します。"following?"はユーザーがフォロー済みであるかを確認します。"follow"と"unfollow"はそれぞれフォロー、アンフォローを行うメソッドです。

#3.コントローラの作成
relationshipのコントローラーを作成します。

```terminal:terminal
rails g controller relationships
```

ルーティングの設定をします。フォローフォロワー一覧はUserとの絡みがあるため、user内にfollowings、followersアクションのルーティングを設定します。

```ruby:routes.rb
resources :users do
  member do
    get :followings, :followers
  end
end
resources :relationships, only: [:create, :destroy]
```

フォローフォロワー一覧はUserとの絡みがあるため、user内にfollowings、followersアクションのルーティングを設定します。
relationshipsへのアクションはcreateとdestroyのみ設定します。

relationshipsコントローラーを作成します。

```ruby:relationships_controller.rb
class RelationshipsController < ApplicationController
  def create
    @user = User.find(params[:relationship][:following_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html {redirect_back(fallback_location: root_path)}
      format.js
    end
  end

  def destroy
    @user = User.find(params[:relationship][:following_id])
    current_user.unfollow(@user)
    respond_to do |format|
      format.html {redirect_back(fallback_location: root_path)}
      format.js
    end
  end
end
```

フォロー、アンフォロー動作を受け取った時の動作を設定します。
Ajax通信にて動作を処理するため、"respond_to do |format|"にてフォーマットを確認し、レンダリング先を指定します。format.jsにて該当するjsリクエストがあれば、〜js.erbにて処理します。htmlでは"redirect_back"にて直前のページを表示し、表示できなければroot_pathに向かいます。

usersコントローラーにフォローフォロワー一覧用メソッドを追加します。

```ruby:users.rb
def followings
  @user = User.find(params[:id])
  @users = @user.followings.page(params[:page]).per(10)
  render "show_followings"
end

def followers
  @user = User.find(params[:id])
  @users = @user.followers.page(params[:page]).per(10)
  render "show_followers"
end
```

注:ユーザー一覧表示にページネーションを使用しています。

#4.ビューの編集
フォローフォロワー一覧へのリンクを作成します。各ユーザー数は表示できるようにしておきます。Ajax通信にて処理するため、該当箇所を-div id="~"-で囲い、idを設定します。

```:index.html
<% @users.each do |user| %>
~省略~
  <div id="following_count_<%= user.id %>">
    <%= link_to "フォロー中(#{user.followings.count})", followings_user_path(user),class:"~" %>
  </div>
  <div id="follower_count_<%= user.id %>">
    <%= link_to "フォロワー(#{user.followers.count})", followers_user_path(user),class:"~" %>
  </div>
<% end %>
```

```:show.html
<div id="following_count">
  <%= link_to "フォロー中(#{@user.followings.count})", followings_user_path(@user), class:"~" %>
</div>
<div id="follower_count">
  <%= link_to "フォロワー(#{@user.followers.count})", followers_user_path(@user), class:"~" %>
</div>
```

indexでは各ユーザーに対してidを振り分けるため、
"-div id="following_count_<%= user.id %>"-"
"-div id="follower_count_<%= user.id %>"-"
としてid名を設定します。

続いてフォローボタンも作成します。

```:index.html.erb
<% @users.each do |user| %>
~省略~
  <% if current_user && user != current_user %>
    <div id="follow_form_<%= user.id %>">
      <% if current_user.following?(user) %>
        <%= render "unfollow", user: user %>
      <% else %>
        <%= render "follow", user: user %>
      <% end %>
    </div>
  <% end %>
<% end %>
```

```:show.html.erb
<% if current_user && @user != current_user %>
  <div id="follow_form">
    <% if current_user.following?(@user) %>
      <%= render "unfollow", user: @user %>
    <% else %>
      <%= render "follow", user: @user %>
    <% end %>
  </div>
<% end %>
```

一覧表示と同様、indexでは各ユーザーに対してidを振り分けるため、"-div id="follow_form_<%= user.id %>"-"としてid名を設定します。
"user: @user"にてレンダリング先のuserへ@userの値を渡します。

フォロー、フォロワーボタンを作成します。

```follow.html.erb
<%= form_with(model: current_user.following_relationships.build) do |f| %>
  <%= f.hidden_field :following_id, value: user.id %>
  <%= f.submit "フォローする", class: "~" %>
<% end %>
```

```unfollow.html.erb
<%= form_with(model: current_user.following_relationships.find_by(following_id: user.id), method: :delete) do |f| %>
  <%= f.hidden_field :following_id %>
  <%= f.submit "フォローをやめる", class: "~" %>
<% end %>
```

form_withを使用してリクエストを送信します。form_withはデフォルトでAjax通信をするよう設定されています。もしform_forを使う場合は、オプションに"remote: true"を設定します。
"f.hidden_field :following_id"にてユーザーidを入れるようにします。

#5.jsファイルの作成
フォームから送信されたリクエストはコントローラのアクションにて処理を行い、js.erbへ向かいます。

```javascript:create.js.erb
$("#follow_form_<%= @user.id %>").html("<%= j(render partial: 'users/unfollow', locals: { user: @user }) %>");
$("#follow_form").html("<%= j(render partial: 'users/unfollow', locals: { user: @user }) %>");

$("#following_count_<%= @user.id %>").html('<%= link_to "フォロー中(#{@user.followings.count})", followings_user_path(@user), class:"~" %>');
$("#follower_count_<%= @user.id %>").html('<%= link_to "フォロワー(#{@user.followers.count})", followers_user_path(@user), class:"~" %>');
$("#following_count").html('<%= link_to "フォロー中(#{@user.followings.count})", followings_user_path(@user), class:"~" %>');
$("#follower_count").html('<%= link_to "フォロワー(#{@user.followers.count})", followers_user_path(@user), class:"~" %>');
```

```javascript:destroy.js.erb
$("#follow_form_<%= @user.id %>").html("<%= j(render partial: 'users/follow', locals: { user: @user }) %>");
$("#follow_form").html("<%= j(render partial: 'users/follow', locals: { user: @user}) %>");

$("#following_count_<%= @user.id %>").html('<%= link_to "フォロー中(#{@user.followings.count})", followings_user_path(@user), class:"~" %>');
$("#follower_count_<%= @user.id %>").html('<%= link_to "フォロワー(#{@user.followers.count})", followers_user_path(@user), class:"~" %>');
$("#following_count").html('<%= link_to "フォロー中(#{@user.followings.count})", followings_user_path(@user), class:"~" %>');
$("#follower_count").html('<%= link_to "フォロワー(#{@user.followers.count})", followers_user_path(@user), class:"~" %>');
```

"$("設定したid").html('レンダリングしたいhtml')"という記載でAjax通信にて部分的にレンダリングすることができます。
注: "や'の差で動作がうまくいかない箇所があったため、設定には注意が必要です。私は上記の記述で動作しました。

#まとめ
以上で、Ajax通信にてフォロー機能を実装することができました。
ユーザー詳細(show)でのフォロー機能は参考文献があり、簡単にできましたが、一覧(index)にてフォロー機能を設定するには参考文献がなく、かなり苦戦しました。試行錯誤して無事達成できたので、良い勉強と経験になりました。

#参考文献
[[Ruby on Rails] フォロー機能を実装しよう](https://qiita.com/E6YOteYPzmFGfOD/items/ec0492b509962c3b7ae4)
[【Rails】Ajaxを用いた非同期フォロー機能の実装](https://qiita.com/matsubishi5/items/3a70689b42dd98b4d99b)

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