Railsチュートリアル 第10章 ユーザーの更新・表示・削除 - すべてのユーザーを表示する

# 何をするか

Usersコントローラーに`index`アクションを実装していきます。これにより、「すべてのユーザーの一覧表示」という操作が可能になります。

追加機能として、以下の機能も実装していきます。

* RDBへのサンプルユーザーの追加
* ページネーション機能
    * 英語ではpagination=ページ分割
    * ユーザー一覧の出力を複数ページに分割する機能のこと

Railsチュートリアル本文においては、ユーザー一覧ページのモックアップとして、[図 10.8](https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#fig-user_index_mockup)が示されています。

# ユーザー情報のセキュリティモデル

Railsチュートリアル本文においては、現在開発中のサンプルアプリケーションにおいて、ユーザー情報のセキュリティモデルは以下のように設計することとしています。

* ユーザーの`show`ページは、非ログインユーザーも含め、すべての利用者に表示を許可する
* ユーザーの`index`ページは、ログイン済のユーザーのみに表示を許可する

# ユーザーの一覧ページ

## 「ユーザーの`index`ページは、ログイン済のユーザーのみに表示を許可する」という動作に対するテスト

表題内容の対偶をとると、「非ログインユーザーがユーザーの`index`ページを表示しようとした場合、表示を許可しない」という内容になります。「表示を許可しない」という場合の動作は、「ログインページにリダイレクトする」というのが最も自然ですね。

ということで、「非ログインユーザーがユーザーの`index`ページを表示しようとした場合」に対応するテストの実装は、以下のようになります。名前は「should redirect index when not logged in」とします。

```ruby:
test "should redirect index when not logged in" do
  get users_path
  assert_redirected_to login_url
end
```

実装箇所は`test/controllers/users_controller_test.rb`内です。

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

  class UsersControllerTest < ActionDispatch::IntegrationTest

    def setup
      @user       = users(:rhakurei)
      @other_user = users(:mkirisame)
    end
+
+   test "should redirect index when not logged in" do
+     get users_path
+     assert_redirected_to login_url
+   end

    ...略
  end
```

現時点で、このテストは失敗します。

```ruby:test/controllers/users_controller_test.rb（10行目）
test "should redirect index when not logged in" do
```

```
# rails test test/controllers/users_controller_test.rb:10
Running via Spring preloader in process 1376
Started with run options --seed 60339

ERROR["test_should_redirect_index_when_not_logged_in", UsersControllerTest, 0.728221700002905]
 test_should_redirect_index_when_not_logged_in#UsersControllerTest (0.73s)
AbstractController::ActionNotFound:         AbstractController::ActionNotFound: The action 'index' could not be found for UsersController
            test/controllers/users_controller_test.rb:11:in `block in <class:UsersControllerTest>'

  6/6: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.73096s
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
```

現時点でUsersコントローラーに`index`アクションは実装されていないため、「Usersコントローラーに`index`アクションが実装されていない」旨のエラーメッセージが出力されます。

## Usersコントローラーに`index`アクションを実装し、beforeフィルターの対象とする

続いて`index`アクションを実装していきます。`index`アクションの動作は、現状ではRailsデフォルトの動作とするため、実装は何も記述せずにメソッド定義だけ記述することとします。

「ユーザーの`index`ページは、ログイン済のユーザーのみに表示を許可する」という動作は、「`index`アクションを、beforeフィルターの`logged_in_user`に追加し、同メソッドによる保護対象とする」ことにより実現できます。早速`app/controllers/users_controller.rb`の内容を修正しましょう。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController
-   before_action :logged_in_user, only: [:edit, :update]
+   before_action :logged_in_user, only: [:index, :edit, :update]
    before_action :correct_user,   only: [:edit, :update]
+
+   def index
+   end

    ...略
  end
```

ここまでの実装が完了すると、テスト「should redirect index when not logged in」が成功するようになります。

```
# rails test test/controllers/users_controller_test.rb:10
Running via Spring preloader in process 1402
Started with run options --seed 22324

  6/6: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.54567s
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```

## indexビューを実装する

Usersコントローラーで`index`アクションの実装が済んだら、今度はindexビューを実装します。中身は「全ユーザーが格納された変数を作成し、順々に表示する」というものになります。

### indexビューを実装するために、Usersコントローラーの`index`アクションに必要となる新たな実装

以下の動作をUsersコントローラーの`index`アクションにに実装する必要があります。

1. `User.all`を用い、RDBに保存された全ユーザーの情報を取得する
2. 取得したユーザー情報を、ビューで使えるユーザー変数`@users`に代入する

コードとしては以下のようになります。

```ruby:
@users = User.all
```

早速Usersコントローラーの`index`アクションに反映しましょう。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController
    ...略

    def index
+     @users = User.all
    end

    ...略
    
  end
```

### indexビューを実際に実装する

indexビューのファイル名は`app/views/users/index.html.erb`となります。現時点で当該ファイルは存在しないため、まず当該ファイルを新規に作成するところから始まります。

```
>>> touch app/views/users/index.html.erb
```

空の`app/views/users/index.html.erb`が作成できたら、埋め込みRubyを記述していきます。

```erb:app/views/users/index.html.erb
<% provide(:title, 'All Users') %>
<h1>All Users</h1>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>
```

上述埋め込みRubyコードで、特筆すべき点は以下です。

* ユーザーを列挙する領域全体は`ul`要素としている
* `each`メソッドでユーザーを列挙している
* 各ユーザーのプロフィール画像と名前を`li`タグで囲っている
* `UsersHelper#gravatar_for`の`:size`オプションに、デフォルト以外の値を与えている
    * デフォルトは80、今回与えた値は50

### （参考）`UsersHelper#gravatar_for`の現状の実装

なお、私の環境では、現状の`UsersHelper#gravatar_for`の実装は以下のようになっています。

```ruby:UsersHelper#gravatar_for
def gravatar_for(user, options = { size:80 })
  gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
  size = options[:size]
  gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
  image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
```

### ユーザーのindexページの表示を整えるために、CSSに手を加える

```diff:app/assets/stylesheets/custom.scss
  ...略
+
+ .users {
+     list-style: none;
+     margin: 0;
+     li {
+         overflow: auto;
+         padding: 10px 0;
+         border-bottom: 1px solid $gray-lighter;
+     }
+ }
 
  ...略
```

### ビューのヘッダー部に、ユーザー一覧ページへのリンクを追加する

ビューのヘッダー部には、すでにユーザー一覧ページへのリンクが存在します。リンク先は、現時点まで仮に`#`としてきましたが、ここまでの実装でユーザー一覧ページが表示できるようになったので、実際のユーザー一覧ページにリンクするようにします。

```diff:app/views/layouts/_header.html.erb
  <header class="navbar navbar-fixed-top navbar-inverse">
    <div class="container">
      ...略
      <nav>
        <ul class="nav navbar-nav navbar-right">
          ...略
          <% if logged_in? %>
-           <li><%= link_to "Users", '#' %></li>
+           <li><%= link_to "Users", users_path %></li>
            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                Account<b class="caret"></b>
              </a>
              <ul class="dropdown-menu">
                <li><%= link_to "Profile", current_user %></li>
                <li><%= link_to "Settings", edit_user_path(current_user) %></li>
                <li class="divider"></li>
                <li>
                  <%= link_to "Log out", logout_path, method: :delete %>
                </li>
              </ul>
            </li>
          <% else %>
            <li><%= link_to "Log in", login_path %></li>
          <% end %>
        </ul>
      </nav>
    </div>
  </header>
```

### 実際に動くユーザーのindex

ここまでの実装が完了すれば、ユーザーのindexは実際に動くようになります。

```
# rails test
Running via Spring preloader in process 1441
Started with run options --seed 33187

  36/36: [=================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.18539s
36 tests, 96 assertions, 0 failures, 0 errors, 0 skips
```

新たに実装したテストも含め、すべてのテストが無事通ることが確認できました。

<img width="1170" alt="スクリーンショット 2019-11-26 7.40.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/272f1235-ae3b-932c-92d0-78ea1ce3e849.png">

上記はログインユーザーでusersページを表示したスクリーンショットです。

# 演習 - ユーザーの一覧ページ

## 1. レイアウトにあるすべてのリンクに対して統合テストを書いてみましょう。ログイン済みユーザーとそうでないユーザーのそれぞれに対して、正しい振る舞いを考えてください。

> ヒント: log_in_asヘルパーを使って[リスト 5.32](https://railstutorial.jp/chapters/filling_in_the_layout?version=5.1#code-layout_links_test)にテストを追加してみましょう。

テストを実装するソースファイルは`test/integration/site_layout_test.rb`となります。

### ログイン状態の再現に必要な処理

ログイン状態を再現するために、まずは`setup`メソッドにより、fixtureから`@user`に有効なユーザー情報を与える必要があります。対応するコードは以下になります。

```ruby:
def setup
  @user = users(:rhakurei)
end
```

### ログイン済みユーザー・非ログインユーザー共通に必要となるテスト

リクエストの内容は、「当該レイアウトを使うリソースへの`GET`リクエスト」であれば何でも構いません。今回は「/ への`GET`リクエスト」とします。

```ruby:
get root_path
```

Home・Help・About・Contactへのリンクは、ログイン済みユーザー・非ログインユーザー関係なく共通です。対応するコードは以下になります。

```ruby:ログイン済みユーザー・非ログインユーザー共通に必要となるテスト
assert_select "a[href=?]", root_path
assert_select "a[href=?]", help_path
assert_select "a[href=?]", about_path
assert_select "a[href=?]", contact_path
```

### 非ログインユーザーに必要となるテスト

ログイン済みユーザーでない場合、レイアウトに存在するリンクは以下の要件を満たす必要があります。

* ログインページへのリンクが存在すること
* ユーザー一覧ページへのリンクが存在しないこと
* ユーザー情報ページへのリンクが存在しないこと
* ユーザー情報編集ページへのリンクが存在しないこと
* ログアウトページへのリンクが存在しないこと

対応するコードは以下になります。

```ruby:非ログインユーザーに必要となるテスト
assert_select "a[href=?]", login_path
assert_select "a[href=?]", users_path,            count: 0
assert_select "a[href=?]", user_path(@user),      count: 0
assert_select "a[href=?]", edit_user_path(@user), count: 0
assert_select "a[href=?]", logout_path,           count: 0
```

### ログイン済みユーザーに必要となるテスト

ログイン済みユーザーの場合、レイアウトに存在するリンクは以下の要件を満たす必要があります。

* ログインページへのリンクが存在しないこと
* ユーザー一覧ページへのリンクが存在すること
* 自身のユーザー情報ページへのリンクが存在すること
* 自身のユーザー情報編集ページへのリンクが存在しないこと
* ログアウトページへのリンクが存在すること

対応するコードは以下になります。

```ruby:ログイン済みユーザーに必要となるテスト
assert_select "a[href=?]", login_path, count: 0
assert_select "a[href=?]", users_path
assert_select "a[href=?]", user_path(@user)
assert_select "a[href=?]", edit_user_path(@user)
assert_select "a[href=?]", logout_path
```

### `test/integration/site_layout_test.rb`の変更内容

`test/integration/site_layout_test.rb`の変更内容の全体像は以下のようになります。

```diff:test/integration/site_layout_test.rb
  require 'test_helper'

  class SiteLayoutTest < ActionDispatch::IntegrationTest
+
+   def setup
+     @user = users(:rhakurei)
+   end

    ...略
+
+   test "layout links without login" do
+     get root_path
+     assert_select "a[href=?]", root_path
+     assert_select "a[href=?]", help_path
+     assert_select "a[href=?]", about_path
+     assert_select "a[href=?]", contact_path
+     assert_select "a[href=?]", login_path
+     assert_select "a[href=?]", users_path,            count: 0
+     assert_select "a[href=?]", user_path(@user),      count: 0
+     assert_select "a[href=?]", edit_user_path(@user), count: 0
+     assert_select "a[href=?]", logout_path,           count: 0
+   end
+
+   test "layout links with login" do
+     log_in_as @user
+     get root_path
+     assert_select "a[href=?]", root_path
+     assert_select "a[href=?]", help_path
+     assert_select "a[href=?]", about_path
+     assert_select "a[href=?]", contact_path
+     assert_select "a[href=?]", login_path, count: 0
+     assert_select "a[href=?]", users_path
+     assert_select "a[href=?]", user_path(@user)
+     assert_select "a[href=?]", edit_user_path(@user)
+     assert_select "a[href=?]", logout_path
+   end
  end
```

### 今実装したテストが成功することを確認する

ここまで実装が完了すれば、`test/integration/site_layout_test.rb`全体を使ったテストを実行できるようになります。ここまでに記述してきた振る舞いは、本当に正しいのでしょうか。実際にやってみましょう。

```
# rails test test/integration/site_layout_test.rb
Running via Spring preloader in process 1584
Started with run options --seed 33479

  3/3: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.33232s
3 tests, 25 assertions, 0 failures, 0 errors, 0 skips
```

テストは無事成功しました。ここまでに記述してきた振る舞いは確かに正しいようです。

# サンプルのユーザー

## Fakerというgem

Fakerはダミーデータを生成するためのgemです。「開発環境などで、開発中のアプリケーションの振る舞いを検証するため、ある程度まとまった数のデータが欲しい」という場合に便利です。

### `Gemfile`にFakerを追加する

```diff:Gemfile
  source 'https://rubygems.org'

  gem 'rails',        '5.1.6'
  gem 'bcrypt',       '3.1.12'
+ gem 'faker',        '1.7.3'
  ...略

  group :development do
    ...略
  end

  ...略
```

Faker gemは、通常開発環境でしか使わないため、通常はGemfileの`:development`グループ以下に追加します。しかしながら、Railsチュートリアルのサンプルプログラムにおいては、「本番環境でFakerを使う」という例外的な運用が発生するため、Faker gemをGemfileのグループ外に追加しています。

### `bundle install`でFakerをインストールする

FakerをGemfileに追加したら、例によって`bundle install`を実行します。

```
# bundle install
...略
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Bundler could not find compatible versions for gem "i18n":
  In snapshot (Gemfile.lock):
    i18n (= 1.6.0)

  In Gemfile:
    rails (= 5.1.6) was resolved to 5.1.6, which depends on
      activesupport (= 5.1.6) was resolved to 5.1.6, which depends on
        i18n (< 2, >= 0.7)

    faker (= 1.7.3) was resolved to 1.7.3, which depends on
      i18n (~> 0.5)

Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.
```

うまくいきませんでした。RailsとFakerで、それぞれが別に依存する`i18n`というgemの要求バージョンが合わないことが原因のようです。

指示通りに`bundle update`を実行してみます。

```
# bundle update
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies.....
...略
Fetching faker 1.7.3
Installing faker 1.7.3
...略
Bundle updated!
Gems in the group production were not installed.
```

今度こそ無事にFaker gemがインストールできたようです。

### 100人のサンプルユーザーをRDB上に生成する

`db/seeds.rb`というファイルに、100人のサンプルユーザーを生成するためのコードを追加します。サンプルユーザーの内訳は以下のとおりです。

* 「Example User」という名前とメールアドレスを持つ1人のユーザー
* それらしい名前とメールアドレスを持つ99人のユーザー

```ruby:db/seeds.rb
User.create(name:                  "Example User",
            email:                 "example@railstutorial.org",
            password:              "foobar",
            password_confirmation: "foobar")

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)
end
```

このコードのポイントは以下です。

* `create`ではなく`create!`メソッドを用いている
    * ユーザーが無効な場合に`nil`を返すのではなく、例外を投げる
    * エラーを検知できる可能性を高めることを狙っている

### RDBのリセットと、100人のユーザーの生成

RDBをリセットした上で、上記`db/seeds.rb`の内容に基づき、実際に100人のユーザーをサンプルアプリケーション上に生成してみます。

まずはRDBをリセットします。

```
# rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
...略
== [timestamp] AddRememberDigestToUsers: migrating =========================
-- add_column(:users, :remember_digest, :string)
   -> 0.0116s
== [timestamp] AddRememberDigestToUsers: migrated (0.0119s) ================
```

続いて、実際に`db/seeds.rb`の内容をRDBに反映します。コマンドは`rails db:seed`です。

```
# rails db:seed
```

エラーがなければ、シェルには何も表示されずに実行が終わります。

### 100人のユーザーが生成された！

ログインしてindexページを表示してみましょう。

<img width="1170" alt="スクリーンショット 2019-11-26 18.22.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/696eb013-f078-8b3e-d8c8-d4f4c6dc3969.png">

Michael Hartl氏の手により、最初のいくつかのメールアドレスには、デフォルトのGravatar画像以外の写真が関連付けられています。その写真もきちんと表示されていますね。

```
# rails console --sandbox

>> User.count
   (1.1ms)  SELECT COUNT(*) FROM "users"
=> 100
```

`rails console`から`User.count`を実行した結果も、きちんと`100`が返ってきました。

# 演習 - サンプルのユーザー

## 1. 試しに他人の編集ページにアクセスしてみて、[10.2.2](https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#sec-requiring_the_right_user)で実装したようにリダイレクトされるかどうかを確かめてみましょう。

現在、id=1のユーザーでログインしていることを前提とします。

```
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: users
  action: show
  id: '1'
permitted: false
```

/users/1/edit への`GET`リクエストに対しては、最終的に「200 OK」のステータスコードが返ってきます。

```
Started GET "/users/1/edit" ...略
Completed 200 OK in 648ms (Views: 604.0ms | ActiveRecord: 9.6ms)
```

```
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: users
  action: edit
  id: '1'
permitted: false
```

続いて、/users/2/edit に`GET`リクエストを送出してみます。

```
Started GET "/users/2/edit" ...略
Redirected to http://localhost:8080/
Filter chain halted as :correct_user rendered or redirected
Completed 302 Found in 14ms (ActiveRecord: 8.1ms)


Started GET "/" for ...略
Completed 200 OK in 378ms (Views: 351.2ms | ActiveRecord: 2.8ms)
```

「ルートURLにリダイレクトされる」という動作は、確かに実装した通りですね。

# ページネーション

## ページネーションの必要性

「1つのページに大量のユーザーが表示される」というのは、検索ロボットや自動処理用のスクリプトによるアクセスであればともかく、人間が使う上では非常に使いづらいです。100人でも人間が扱うには多いですし、今後ユーザー数が数千人に増える可能性もあります。1つのページに表示されるユーザーの数は、人間が扱いやすい人数、例えば30人にしたいところです。

そうした要求を実現する機能がページネーション(pagination)です。Railsチュートリアル本文では、[will_paginateメソッド](http://wiki.github.com/mislav/will_paginate/)によるページネーションを実装する、としています。

## ページネーションの実装に必要なgemの追加

前提として、ページネーションの実装は以下の条件で行うものとします。

* ページネーションには`will_paginate`メソッドを用いる
* Bootstrapのページネーションスタイルを適用する

上記前提条件の元で新たに必要となるgemは、`will_paginate`と`bootstrap-will_paginate`の2つになります。早速Gemfileに追加しましょう。

```diff:Gemfile
  source 'https://rubygems.org'

  gem 'rails',                   '5.1.6'
  gem 'bcrypt',                  '3.1.12'
  gem 'faker',                   '1.7.3'
+ gem 'will_paginate',           '3.1.6'
+ gem 'bootstrap-will_paginate', '1.0.0'
  ...略
```

続いて`bundle install`を実行します。

```
# bundle install
...略
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies....
...略
Fetching will_paginate 3.1.6
Installing will_paginate 3.1.6
Fetching bootstrap-will_paginate 1.0.0
Installing bootstrap-will_paginate 1.0.0
...略
Bundle complete! 26 Gemfile dependencies, 84 gems now installed.
Gems in the group production were not installed.
Bundled gems are installed into `/usr/local/bundle`
```

問題なくgemの追加が完了したようですね。

## ページネーションを実際に使う

### 必要となる実装

追加・変更が必要となる実装は以下です。

* indexビューでページネーションを使うようにする
* Usersコントローラーの`index`アクションの動作を、ページネーションに対応したものに変更する
    * 具体的には、RDBからユーザーを取得する処理を書き換える

### indexページでページネーションを使うようにする

ページネーションのリンクを表示するためのメソッドを`app/views/users/index.html.erb`に追加すればOKです。今回は`will_paginate`メソッドですね。

```diff:app/views/users/index.html.erb
  <% provide(:title, 'All Users') %>
  <h1>All Users</h1>

+ <%= will_paginate %>

  <ul class="users">
    <% @users.each do |user| %>
      <li>
        <%= gravatar_for user, size: 50 %>
        <%= link_to user.name, user %>
      </li>
    <% end %>
  </ul>

+ <%= will_paginate %>
```

Railsチュートリアル本文では、indexビュー内の`will_paginate`メソッドの動作について以下のように説明しています。

1. `users`ビューのコード中から、`@users`オブジェクトを自動的に見つけ出す
2. 他のページにアクセスするためのページネーションリンクを作成する

`will_paginate`をページの上下に2つ追加しているのは、「表示されているユーザー一覧の先頭・末尾両方にページネーションのリンクを表示する」ようにするためです。

### `will_paginate`メソッドが必要とする`@users`変数の内容

`will_paginate`メソッドを使う場合、`@users`変数は`paginate`メソッドの戻り値である必要があります。`paginate`メソッドの実行例を以下に示します。

```
# rails console --sandbox
>> User.paginate(page: 1)
  User Load (14.7ms)  SELECT  "users".* FROM "users" LIMIT ? OFFSET ?  [["LIMIT", 11], ["OFFSET", 0]]
   (0.8ms)  SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1,...
```

* `paginate`メソッドは、キーが`:page`で値がページ番号のハッシュを引数として取る
* `User.paginate`は、`:page`パラメーターに基づき、RDBからUserモデルに紐付けされたひとかたまり（デフォルトでは30）のデータを取り出す
    * 1ページ目は1番目から31番目のユーザー
    * 2ページ目は31番目から60番目のユーザー
    * 以下略
    * なお、キー`:page`に対する値が`nil`の場合は、単に最初のページが返ってくる

```
# rails console --sandbox
>> User.paginate(page: nil)
  User Load (0.2ms)  SELECT  "users".* FROM "users" LIMIT ? OFFSET ?  [["LIMIT", 11], ["OFFSET", 0]]
   (0.3ms)  SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1,...
```

上記はキー`:page`に対する値を`nil`として`User.paginate`を実行した例です。

### Usersコントローラーの`index`アクションの動作を、ページネーションに対応したものに変更する

具体的には、`index`アクション内の`all`を`paginate`に置き換えます。なお、`params[:page]`は、ビュー側の`will_paginate`によって自動的に与えられます。

```ruby:
def index
  @users = User.paginate(page: params[:page])
end
```

実際に`app/controllers/users_controller.rb`に対して行う変更は以下のとおりになります。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController
    before_action :logged_in_user, only: [:index, :edit, :update]
    before_action :correct_user,   only: [:edit, :update]

    def index
-     @users = User.all
+     @users = User.paginate(page: params[:page])
    end

    ...略
  end
```

### ページネーション機能が使えるようになる

ここまでの実装が完了すれば、サンプルアプリケーションにおけるページネーション機能が有効になります。

<img width="1170" alt="スクリーンショット 2019-11-27 12.35.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/4e8cb223-5db0-4575-c757-2a602545ebaf.png">

ページネーションのリンクが表示されているのがわかります。

<img width="1170" alt="スクリーンショット 2019-11-27 12.38.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/3c3f222a-2b0b-4a5f-17bb-444ccd50e2b5.png">

2ページ目は上記スクリーンショットのようになります。

# 演習 - ページネーション

## 1. Railsコンソールを開き、`page`オプションに`nil`をセットして実行すると、１ページ目のユーザーが取得できることを確認してみましょう。

本文中で例を示したとおりです。

## 2. 先ほどの演習課題で取得したpaginationオブジェクトは、何クラスでしょうか? また、`User.all`のクラスとどこが違うでしょうか? 比較してみてください。 

```
>> User.paginate(page: 1).class
=> User::ActiveRecord_Relation
>> User.paginate(page: 1).class.superclass
=> ActiveRecord::Relation
>> User.paginate(page: 1).class.superclass.superclass
=> Object

>> User.all.class                                    
=> User::ActiveRecord_Relation
```

paginationオブジェクトのクラスは`User::ActiveRecord_Relation`です。`User.all`のクラスと違いはないようです。

# ユーザー一覧のテスト

## 30人超のユーザーをfixtureに追加する

ページネーションに対するテストを行うためには、30人を上回るユーザー情報が必要となります。これだけの人数のユーザー情報を人手で追加するのは面倒です。

しかしながら、fixtureは埋め込みRubyに対応しています。2人のユーザー情報は手動で追加し、さらに30人のそれらしいユーザー情報はイテレータを使って追加することにしましょう。

```diff:test/fixtures/users.yml
  rhakurei:
    name: Reimu Hakurei
    email: rhakurei@example.com
    password_digest: <%= User.digest('password') %>

  mkirisame:
    name: Marisa Kirisame
    email: example.example@example.org
    password_digest: <%= User.digest('password') %>
+
+ skomeiji:
+   name: Satori Komeiji
+   email: example_example@example.net
+   password_digest: <%= User.digest('password') %>
+
+ rusami:
+   name: Renko Usami
+   email: example0@example.com
+   password_digest: <%= User.digest('password') %>
+
+ <% 30.times do |n| %>
+ user_<%= n %>:
+   name:  <%= "User #{n}" %>
+   email: <%= "user-#{n}@example.com" %>
+   password_digest: <%= User.digest('password') %>
+ <% end %>
```

## indexに対する統合テストの生成

続いて、indexに対する統合テストを生成します。

```
# rails generate integration_test users_index
Running via Spring preloader in process 11865
      invoke  test_unit
      create    test/integration/users_index_test.rb
```

## indexに対する統合テストを書く

「`pagination`クラスを持った`div`タグをチェックして、最初のページにユーザーがいることを確認する」というテストを書いていきます。

```ruby:test/integration/users_index_test.rb
require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest
  def setup
    @user = users(:rhakurei)
  end

  test "index including pagination" do
    log_in_as(@user)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
    end
  end
end
```

### テストは無事成功

`test/integration/users_index_test.rb`を対象としてテストを実行してみます。

```
# rails test test/integration/users_index_test.rb
Running via Spring preloader in process 12058
Started with run options --seed 56737

  1/1: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.12612s
1 tests, 32 assertions, 0 failures, 0 errors, 0 skips
```

無事テストが成功しました。

### 余談 - 「`div.pagination`が無い」と言われてテストが失敗する場合

私はこのテストが通らずに数十分悩むこととなりました。[Railsチュートリアル 第10章 - 「div.paginationが無い」と言われてテストが失敗する場合](https://qiita.com/rapidliner00/items/26b95e203776c62ec5aa)にて、顛末を記述しています。

# 演習 - ユーザー一覧のテスト

## 1. 試しに[リスト 10.45](https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#code-will_paginate_index_view)にあるページネーションのリンク (`will_paginate`の部分) を２つともコメントアウトしてみて、[リスト 10.48](https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#code-user_index_test)のテストが`red`に変わるかどうか確かめてみましょう。

```diff:app/views/users/index.html.erb
  <% provide(:title, 'All Users') %>
  <h1>All Users</h1>

- <%= will_paginate %>
+ <%# <%= will_paginate %> %>

  <ul class="users">
    <% @users.each do |user| %>
      <li>
        <%= gravatar_for user, size: 50 %>
        <%= link_to user.name, user %>
      </li>
    <% end %>
  </ul>

- <%= will_paginate %>
+ <%# <%= will_paginate %> %>
```

```
# rails test test/integration/users_index_test.rb
Running via Spring preloader in process 12071
Started with run options --seed 63044

 FAIL["test_index_including_pagination", UsersIndexTest, 2.9782001999847125]
 test_index_including_pagination#UsersIndexTest (2.98s)
        Expected at least 1 element matching "div.pagination", found 0..
        Expected 0 to be >= 1.
        test/integration/users_index_test.rb:12:in `block in <class:UsersIndexTest>'

  1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.98834s
1 tests, 2 assertions, 1 failures, 0 errors, 0 skips
```

`Expected at least 1 element matching "div.pagination", found 0..`というメッセージが出てテストが失敗しています。`pagination`というクラスを持つ`div`要素がない、という理由によるテストの失敗ですね。

## 2.1. 先ほどは２つともコメントアウトしましたが、１つだけコメントアウトした場合、テストが `green`のままであることを確認してみましょう。

```diff:app/views/users/index.html.erb
  <% provide(:title, 'All Users') %>
  <h1>All Users</h1>

- <%= will_paginate %>
+ <%# <%= will_paginate %> %>

  <ul class="users">
    <% @users.each do |user| %>
      <li>
        <%= gravatar_for user, size: 50 %>
        <%= link_to user.name, user %>
      </li>
    <% end %>
  </ul>

- <%# <%= will_paginate %> %>
+ <%= will_paginate %>
```

```
# rails test test/integration/users_index_test.rb
Running via Spring preloader in process 12084
Started with run options --seed 955

  1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.81957s
1 tests, 32 assertions, 0 failures, 0 errors, 0 skips
```

確かにテストが成功しますね。「`will_paginate`のリンクは2つ存在しなければテストが失敗する」という実装を実現するために…というのは次の演習です。

## 2.2. `will_paginate`のリンクが２つとも存在していることをテストしたい場合は、どのようなテストを追加すれば良いでしょうか?

> ヒント: 表 5.2を参考にして、数をカウントするテストを追加してみましょう。 

`assert_select 'div.pagination'`に、`count: 2`というオプションを追加します。

```diff:test/integration/users_index_test.rb
  require 'test_helper'

  class UsersIndexTest < ActionDispatch::IntegrationTest
    ...略

    test "index including pagination" do
      log_in_as(@user)
      get users_path
      assert_template 'users/index'
-     assert_select 'div.pagination'
+     assert_select 'div.pagination', count: 2
      User.paginate(page: 1).each do |user|
        assert_select 'a[href=?]', user_path(user), text: user.name
      end
    end
  end
```

```
# rails test test/integration/users_index_test.rb
Running via Spring preloader in process 12097
Started with run options --seed 32036

 FAIL["test_index_including_pagination", UsersIndexTest, 2.6599518999864813]
 test_index_including_pagination#UsersIndexTest (2.66s)
        Expected exactly 2 elements matching "div.pagination", found 1..
        Expected: 2
          Actual: 1
        test/integration/users_index_test.rb:12:in `block in <class:UsersIndexTest>'

  1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.66190s
1 tests, 2 assertions, 1 failures, 0 errors, 0 skips
```

`Expected exactly 2 elements matching "div.pagination", found 1`というメッセージが出てテストが失敗しています。「`pagination`というクラスを持つ`div`要素が2つ必要」と言ってきているので、テスト対象は確かに正しいようです。

`app/views/users/index.html.erb`のすべてのコメントアウトを外し、正しい実装コードに戻します。

```diff:app/views/users/index.html.erb
  <% provide(:title, 'All Users') %>
  <h1>All Users</h1>

- <%# <%= will_paginate %> %>
+ <%= will_paginate %>

  <ul class="users">
    <% @users.each do |user| %>
      <li>
        <%= gravatar_for user, size: 50 %>
        <%= link_to user.name, user %>
      </li>
    <% end %>
  </ul>

- <%# <%= will_paginate %> %>
+ <%= will_paginate %>
```

```
# rails test test/integration/users_index_test.rb
Running via Spring preloader in process 12110
Started with run options --seed 37850

  1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.50007s
1 tests, 32 assertions, 0 failures, 0 errors, 0 skips
```

今度こそテストは成功しました。

# パーシャルのリファクタリング

## 動機

indexページに関する機能的実装はここまでで完了し、テストも実装して成功する状態まで持っていくことができました。indexページに関するコードをさらに質の高いコードに書き換えていく準備はここまでで整っている…というのが現状の立ち位置です。

ここまで言及されていませんでしたが、Railsチュートリアル本文によれば、「Railsにはコンパクトなビューを作成するための素晴らしいツールがいくつもある」とのことです。そうしたコードを使ってindexページのコードをリファクタリングしたい、というのがこの節の動機です。

## `user`パーシャルを実装し、`render`で個別ユーザー情報のHTMLコードを生成するようにする

```diff:app/views/users/index.html.erb
  <% provide(:title, 'All Users') %>
  <h1>All Users</h1>

  <%= will_paginate %>

  <ul class="users">
    <% @users.each do |user| %>
-     <li>
-       <%= gravatar_for user, size: 50 %>
-       <%= link_to user.name, user %>
-     </li>
+     <%= render user %>
    <% end %>
  </ul>

  <%= will_paginate %>
```

上記コードのポイントは以下です。

* Userクラスの`user`変数を引数として`render`を呼び出している
* `app/views/users/_user.html.erb`というパーシャルが必要になる

まずは`app/views/users/_user.html.erb`を作成しましょう。

```
>>> touch app/views/users/_user.html.erb
```

続いて、`app/views/users/_user.html.erb`の内容を記述していきます。

```erb:app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
</li>
```

ここまでで、「`user`パーシャルを実装し、`render`で個別ユーザー情報のHTMLコードを生成するようにする」というリファクタリングはひとまず完了しました。

## 実は、`render`は`@users`を直接引数に取ることができる

先ほど、「Userクラスの`user`変数を引数として`render`を呼び出している」と言及しました。実は、`@users`を直接引数とし、ブロックを使わない形で`render`を呼び出すというリファクタリングが可能です。

```diff:app/views/users/index.html.erb
  <% provide(:title, 'All Users') %>
  <h1>All Users</h1>

  <%= will_paginate %>

  <ul class="users">
-   <% @users.each do |user| %>
-     <%= render user %>
-   <% end %>
+   <%= render @users %>   
  </ul>

  <%= will_paginate %>
```

上記コードのポイントは以下です。

* Railsは、`@users`をUserオブジェクトのコレクションであると推測する
* Userオブジェクトのコレクションを引数として`render`を呼び出した際、`render`の動作は以下となる
    * Userオブジェクトのコレクションを列挙する
    * Userオブジェクトの各インスタンスを、`_user.html.erb`パーシャルで出力する

最後にテストを実行します。

```
# rails test
Running via Spring preloader in process 12214
Started with run options --seed 3088

  39/39: [=================================] 100% Time: 00:00:05, Time: 00:00:05

Finished in 5.56925s
39 tests, 146 assertions, 0 failures, 0 errors, 0 skips
```

テストは無事成功しました。

# 演習 - パーシャルのリファクタリング

## 1. [リスト 10.52](https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#code-index_final_refactoring)にあるrenderの行をコメントアウトし、テストの結果が`red`に変わることを確認してみましょう。

```diff:app/views/users/index.html.erb
  <% provide(:title, 'All Users') %>
  <h1>All Users</h1>

  <%= will_paginate %>

  <ul class="users">
-   <%= render @users %>
+   <%# <%= render @users %> %>
  </ul>

  <%= will_paginate %>
```

```
# rails test test/integration/users_index_test.rb
Running via Spring preloader in process 12201
Started with run options --seed 4733

 FAIL["test_index_including_pagination", UsersIndexTest, 2.5006732000038028]
 test_index_including_pagination#UsersIndexTest (2.50s)
        Expected at least 1 element matching "a[href="/users/14035331"]", found 0..
        Expected 0 to be >= 1.
        test/integration/users_index_test.rb:14:in `block (2 levels) in <class:UsersIndexTest>'
        test/integration/users_index_test.rb:13:in `block in <class:UsersIndexTest>'

  1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.51114s
1 tests, 3 assertions, 1 failures, 0 errors, 0 skips
```

`Expected at least 1 element matching "a[href="/users/14035331"]", found 0`というのは、「ユーザー情報へのリンクが描画されていない」という趣旨のメッセージですね。

演習が終わったら、`app/views/users/index.html.erb`のコードを元に戻しておくことも忘れずに。

```diff:app/views/users/index.html.erb
  <% provide(:title, 'All Users') %>
  <h1>All Users</h1>

  <%= will_paginate %>

  <ul class="users">
-   <%# <%= render @users %> %>
+   <%= render @users %>
  </ul>

  <%= will_paginate %>
```