【Rails】ユーザーのフォロー機能その2　webインターフェイス【Rails Tutorial 14章まとめ】

##webインターフェイス
フォロー機能が構築できたので、実際にアプリケーション上で使えるようにしていく。

###フォローのサンプルデータ
seedファイルを編集して、サンプルユーザー間にフォロー・被フォロー関係を構築する。

```db/seeds.rb
# ユーザー
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             admin:     true,
             activated: true,
             activated_at: Time.zone.now)

99.times do |n|
  name  = Faker::Name.name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!(name:  name,
               email: email,
               password:              password,
               password_confirmation: password,
               activated: true,
               activated_at: Time.zone.now)
end

# マイクロポスト
users = User.order(:created_at).take(6)
50.times do
  content = Faker::Lorem.sentence(5)
  users.each { |user| user.microposts.create!(content: content) }
end

# リレーションシップ
users = User.all
user  = users.first
following = users[2..50]
followers = users[3..40]
following.each { |followed| user.follow(followed) }
followers.each { |follower| follower.follow(user) }
```

最初のユーザーにユーザー3からユーザー51までをフォローさせ、それから逆にユーザー4からユーザー41に最初のユーザーをフォローさせる。
(11、12章をやってない場合はactivated属性を消しておく)

```console
$ rails db:migrate:reset
$ rails db:seed
```
を実行しておく。

###following/followersページへのルーティング
ユーザーのプロフィールページにフォローユーザーと被フォローユーザーの人数をそれぞれ表示し、その一覧を表示するページへのリンクを付ける。
そこで、usersリソースのルーティングにfollowersページとfollowingアクションへのルーティングを追加する。
:memberメソッドを使って以下のようにする。

```config/routes.rb
  resources :users do
    member do
      get :following, :followers
    end
  end
```

これによって次のルーティングが実装される。
<img width="645" alt="スクリーンショット 2019-12-04 23.29.00.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529443/543694d1-f062-9c54-1a24-ec08d40553bd.jpeg">

###統計情報のパーシャル
フォロー/フォロワー数を表示する統計情報のパーシャルを作成する。

```erb:app/views/shared/_stats.html.erb
<% @user ||= current_user %>
<div class="stats">
  <a href="<%= following_user_path(@user) %>">
    <strong id="following" class="stat">
      <%= @user.following.count %>
    </strong>
    following
  </a>
  <a href="<%= followers_user_path(@user) %>">
    <strong id="followers" class="stat">
      <%= @user.followers.count %>
    </strong>
    followers
  </a>
</div>
```
最初の行では、@user ||= current_userとしている。
このパーシャルはhomeページとユーザープロフィールページの両方で使用する。
homeページではcurrent_userを、プロフィールページではshowアクションで定義された@userをそのまま入れる。

strongタグは、文字を強調するためのもので、強調をスタイルシートで指定するなら使わなくてもよい。

homeページでこのパーシャルを呼び出す。

```erb:app/views/static_pages/home.html.erb
<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="stats">
        <%= render 'shared/stats' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3>Micropost Feed</h3>
      <%= render 'shared/feed' %>
    </div>
  </div>
<% else %>
  .
  .
  .
<% end %>
```

###フォロー/フォロー解除ボタンのパーシャル
プロフィールページに統計情報のパーシャルを貼る前に、フォロー/フォロー解除ボタン（フォーム）用のパーシャルを作成しておく。

```erb:app/views/users/_follow_form.html.erb
<% unless current_user?(@user) %>
  <div id="follow_form">
  <% if current_user.following?(@user) %>
    <%= render 'unfollow' %>
  <% else %>
    <%= render 'follow' %>
  <% end %>
  </div>
<% end %>
```
このパーシャルは自身（ログインユーザー）のプロフィールページには表示されない。
つまり、自身とは別のユーザー用である。
そのユーザーをフォローしていればフォロー解除用の、フォローしていなければフォロー用のパーシャルを表示する。

各パーシャルは以下のようになる。

```erb:app/views/users/_follow.html.erb
<%= form_for(current_user.active_relationships.build) 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 }) do |f| %>
  <%= f.submit "Unfollow", class: "btn" %>
<% end %>
```
これらのフォームは、どちらもform_forを使ってRelationshipオブジェクトを操作している。
前者は新規のオブジェクトをPOSTリクエストでcreateアクションに、後者は既存のオブジェクトをDELETEアクションでdestroyアクションに送信している。

前者ではhidden_field_tagを使って、フォローしたいユーザーのIDをfollowed_idに入れて送信している。
このhidden_field_tagは次のようなhtmlを生成している。

```html
<input id="followed_id" name="followed_id" type="hidden" value="3" />
```

必要なパーシャルができたので、プロフィールページに統計情報のパーシャルとフォロー用のパーシャルを表示する。

```erb:app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
    </section>
    <section class="stats">
      <%= render 'shared/stats' %>
    </section>
  </aside>
  <div class="col-md-8">
    <%= render 'follow_form' if logged_in? %>
    <% if @user.microposts.any? %>
      <h3>Microposts (<%= @user.microposts.count %>)</h3>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>
```
フォローボタンはログイン中のみ表示するようにしている。

###following/followerページ
フォロー/フォロワーを表示するページを作成する。
各ページへはログインしていないとアクセスできないようにする。
まずはテストを書く。

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

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
    @other_user = users(:archer)
  end
  .
  .
  .
  test "should redirect following when not logged in" do
    get following_user_path(@user)
    assert_redirected_to login_url
  end

  test "should redirect followers when not logged in" do
    get followers_user_path(@user)
    assert_redirected_to login_url
  end
end
```

following/followedアクションへのルーティングはすでに実装しているので、それらの中身を書いて、beforeフィルターをかける。

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
                                        :following, :followers]
  .
  .
  .
  def following
    @title = "Following"
    @user  = User.find(params[:id])
    @users = @user.following.paginate(page: params[:page])
    render 'show_follow'
  end

  def followers
    @title = "Followers"
    @user  = User.find(params[:id])
    @users = @user.followers.paginate(page: params[:page])
    render 'show_follow'
  end

  private
  .
  .
  .
end
```

ここで、following/followersアクションには対応するビューをそれぞれに用意せず、show_followという共通のビューを使用している。
それは、表示するユーザーの内容とタイトル以外は同じだからである。

show_followビューは以下のようになる。

```erb:app/views/users/show_follow.html.erb
<% provide(:title, @title) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <%= gravatar_for @user %>
      <h1><%= @user.name %></h1>
      <span><%= link_to "view my profile", @user %></span>
      <span><b>Microposts:</b> <%= @user.microposts.count %></span>
    </section>
    <section class="stats">
      <%= render 'shared/stats' %>
      <% if @users.any? %>
        <div class="user_avatars">
          <% @users.each do |user| %>
            <%= link_to gravatar_for(user, size: 30), user %>
          <% end %>
        </div>
      <% end %>
    </section>
  </aside>
  <div class="col-md-8">
    <h3><%= @title %></h3>
    <% if @users.any? %>
      <ul class="users follow">
        <%= render @users %>
      </ul>
      <%= will_paginate %>
    <% end %>
  </div>
</div>
```

###following/followerページのテスト
show_followページが正しく描画されているかの統合テストを作成する。

```console
$ rails generate integration_test following
```

Relationshipのfixtureファイルを書いて、テスト用ユーザーにフォロー/被フォローの関係を設定する。

```test/fixtures/relationships.yml
one:
  follower: michael
  followed: lana

two:
  follower: michael
  followed: malory

three:
  follower: lana
  followed: michael

four:
  follower: archer
  followed: michael
```

前半の2つでMichaelがLanaとMaloryをフォローし、後半の2つでLanaとArcherがMichaelをフォローしている。

統合テストは以下のようになる。

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

class FollowingTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
    log_in_as(@user)
  end

  test "following page" do
    get following_user_path(@user)
    assert_not @user.following.empty?
    assert_match @user.following.count.to_s, response.body
    @user.following.each do |user|
      assert_select "a[href=?]", user_path(user)
    end
  end

  test "followers page" do
    get followers_user_path(@user)
    assert_not @user.followers.empty?
    assert_match @user.followers.count.to_s, response.body
    @user.followers.each do |user|
      assert_select "a[href=?]", user_path(user)
    end
  end
end
```

```rb
assert_not @user.followers.empty?
```
というテストは、これがtrueだった場合に、後の

```rb
@user.followers.each do |user|
      assert_select "a[href=?]", user_path(user)
    end
```
でエラーとならないように入れている。