【Rails】ユーザーのフォロー機能その3　フォローボタンの実装【Rails Tutorial 14章まとめ】

##フォローボタンの実装
###Relationshipsコントローラ
フォロー/フォロー解除ボタンが機能するように、Relationshipsコントローラを作成する。

```console
$ rails generate controller Relationships
```
Relationshipsコントローラに必要なのは、フォロー用のcreateアクションと、フォロー解除用のdestroyアクションだけである。
よって、ルーティングは以下のようになる。

```config/routes.rb
Rails.application.routes.draw do
  .
  .
  .
  resources :users do
    member do
      get :following, :followers
    end
  end
  resources :microposts, only: [:create, :destroy]
  resources :relationships, only: [:create, :destroy]
end
```

###create/destroyアクション
createアクションとdestroyアクションはやはりログインしていなければアクセスできないので、テストを書いてフィルターをつける。

```test/controllers/relationships_controller_test.rb
require 'test_helper'

class RelationshipsControllerTest < ActionDispatch::IntegrationTest

  test "create should require logged-in user" do
    assert_no_difference 'Relationship.count' do
      post relationships_path
    end
    assert_redirected_to login_url
  end

  test "destroy should require logged-in user" do
    assert_no_difference 'Relationship.count' do
      delete relationship_path(relationships(:one))
    end
    assert_redirected_to login_url
  end
end
```

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

  def create
    user = User.find(params[:followed_id])
    current_user.follow(user)
    redirect_to user
  end

  def destroy
    user = Relationship.find(params[:id]).followed
    current_user.unfollow(user)
    redirect_to user
  end
end
```

create/destroyアクションでは、フォロー/フォロー解除フォームから送信された値から、followed_idに対応するユーザーを見つけ、それにfollow/unfollowメソッドを使っている。

##Ajax
###Ajaxを使った非同期リクエスト
現在の仕様では、フォローボタンを押した後はプロフィールページにリダイレクトされるようになっている。
ここで、ページを維持したままリクエストを送るために、Ajaxを使用する。

form_forにremote: trueを与える。

```erb:app/views/users/_follow.html.erb
<%= form_for(current_user.active_relationships.build, remote: true) do |f| %>
  <div><%= hidden_field_tag :followed_id, @user.id %></div>
  <%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>
```

```erb:app/views/users/_unfollow.html.erb
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
             html: { method: :delete },
             remote: true) do |f| %>
  <%= f.submit "Unfollow", class: "btn" %>
<% end %>
```

次に、create/destroyアクションにrespond_toメソッドを使う。

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

  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end
end
```
ここではインスタンス変数を使うことに注意する。

また、ブラウザ側でJavaScriptが無効になっていた場合 (Ajaxリクエストが送れない場合) でもうまく動くようにする。

```config/application.rb
require File.expand_path('../boot', __FILE__)
.
.
.
module SampleApp
  class Application < Rails::Application
    .
    .
    .
    # 認証トークンをremoteフォームに埋め込む
    config.action_view.embed_authenticity_token_in_remote_forms = true
  end
end
```

次に、JavaScriptと埋め込みRubyを使って、プロフィールページを更新する。

```js:app/views/relationships/create.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>");
$("#followers").html('<%= @user.followers.count %>');
```

```js:app/views/relationships/destroy.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>");
$("#followers").html('<%= @user.followers.count %>');
```

このあたりの詳細がよく分からないので、とりあえず今はスキップすることにする。

##フォロー機能のテスト
###フォロー/フォロー解除
Relationshipsコントローラのcreate/destroyアクションにそれぞれPOST/DELETEリクエストを送り、フォローされたユーザーの数が増減することを確認する。

```test/integration/following_test.rb
require 'test_helper'

class FollowingTest < ActionDispatch::IntegrationTest

  def setup
    @user  = users(:michael)
    @other = users(:archer)
    log_in_as(@user)
  end
  .
  .
  .
  test "should follow a user the standard way" do
    assert_difference '@user.following.count', 1 do
      post relationships_path, params: { followed_id: @other.id }
    end
  end

  test "should follow a user with Ajax" do
    assert_difference '@user.following.count', 1 do
      post relationships_path, xhr: true, params: { followed_id: @other.id }
    end
  end

  test "should unfollow a user the standard way" do
    @user.follow(@other)
    relationship = @user.active_relationships.find_by(followed_id: @other.id)
    assert_difference '@user.following.count', -1 do
      delete relationship_path(relationship)
    end
  end

  test "should unfollow a user with Ajax" do
    @user.follow(@other)
    relationship = @user.active_relationships.find_by(followed_id: @other.id)
    assert_difference '@user.following.count', -1 do
      delete relationship_path(relationship), xhr: true
    end
  end
end
```

Ajax使用する場合はリクエストの後にxhr: trueを追加する。