Railsチュートリアル(第6版)　第10章　ユーザーの更新・表示・削除

#第10章
今回の章では、Usersリソース用RESTアクションで未実装だった

・edit
・update
・index
・destroy

これらのアクションを追加する。
また、認可モデル(Authorization Model)についても触れていく。

そして、主にやる事は以下の事
・全てのユーザー一覧と、ページネーションを導入
・ユーザーの削除によって、データベースから完全に削除する機能
・管理ユーザーという特権クラスの作成

##ユーザーを更新する
新規登録の時の`new`アクションの代わりに、編集は`edit`アクションを作成し、
POSTリクエストに応答する`create`アクションの代わりに、`PATCH`リクエストに応答する`update`アクションを作成する。

新規登録の時との最大の違いは、ユーザー登録は誰でもできて、ユーザー情報を更新するのは、ユーザー自身に限られるという点。
また、認証機能に関しては、`before`フィルターを使えばアクセス制御可能。

###編集フォーム
流れとしては、Usersコントローラーに`edit`アクションを追加し、対応する`edit`ビューを作る。
最初に`edit`アクションの実装から入るが、まずはユーザーデータを読み込む必要があるということ。

例えば、ユーザー1のユーザー編集ページなら、URLは`/users/1/edit`となる。
このユーザIDは、`params[:id]`で取り出す。

```ruby:app/controllers/users_controller.rb
  def edit
    @user = User.find(params[:id])
  end
```

続いて、ユーザー編集ページのビューを作る。
`$ touch app/views/users/edit.html.erb`

```erb:app/views/users/edit.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(model: @user, local: true) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Save changes", class: "btn btn-primary" %>
    <% end %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="https://gravatar.com/emails" target="_blank">change</a>
    </div>
  </div>
</div>
```


ここでも`error_messages`パーシャルを再利用している。

htmlにあるGravatorリンクで`target="_blank"としているが、こうするとリンク先を新しいタブで開くようになる。(セキュリティ上小さな問題があるが、後でみていく。)

コントローラーでインスタンス変数`@user`を定義したおかげで、編集ページに`@user`に格納された情報はビューページで引き出されて名前、メールアドレス等の値が自動的に入力され大変便利。

erbから生成されたhtmlを見ると
`<input name="_method" type="hidden" value="patch" />`
となっていて、通常WEBブラウザはPATCHリクエストを送信できないため、RailsはPOSTリクエストと隠しinputフィールドを利用し、PATCHリクエストを偽造している。
(へぇ～って感じ。詳細がなんとなく気になる)

`new`ページと`edit`ページを見比べると、コードがほぼ同じなのだが、Railsはどのように新規ユーザーか既存ユーザーか区別しているのか。
答えは、`Active Record`の`new_record?`論理値メソッドを使って区別しているから。

```
$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false
```

Railsでゃ、`form_with(@user)`からフォームを構成した時、`@user.new_record?`がtrueならPOST、falseならPATCHとしている。

`_header.html.erb`にナビゲーションバーの`edit`アクションへのリンクを設定
`<%= link_to "Settings", edit_user_path(current_user) %>`
`current_user`ヘルパーメソッドを使うと楽。


演習

`target="_bkank?"`だとフィッシングサイトに引っかかる危険性があるので、`rel`属性に`"noopener"`と設定すれば良い。
`<a href="https://gravatar.com/emails" target="_blank" rel="noopener">change</a>`

先ほど`new`ページと`edit`ページのコードがほぼ同じだったので、リファクタリング

```erb:app/views/users/_form.html.erb
<%= form_with(model: @user, local: true) do |f| %>
  <%= render 'shared/error_messages', object: @user %>

  <%= f.label :name %>
  <%= f.text_field :name, class: 'form-control' %>

  <%= f.label :email %>
  <%= f.email_field :email, class: 'form-control' %>

  <%= f.label :password %>
  <%= f.password_field :password, class: 'form-control' %>

  <%= f.label :password_confirmation %>
  <%= f.password_field :password_confirmation, class: 'form-control' %>

  <%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>
```

```erb:app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<% provide(:button_text, 'Create my account') %>
<h1>Sign up</h1>
<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= render 'form' %>
  </div>
</div>
```

```erb:app/views/users/edit.html.erb
<% provide(:title, 'Edit user') %>
<% provide(:button_text, 'Save changes') %>
<h1>Update your profile</h1>
<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= render 'form' %>
    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="https://gravatar.com/emails" target="_blank">Change</a>
    </div>
  </div>
</div>
```

###編集の失敗
ユーザー登録の時と同様に、編集に失敗した場合についてを始めに扱う。
無効な情報が送信されたら、更新の結果をfalseとして、編集ページをレンダリングする。
初期の`create`と似ている。

```ruby:app/controllers/users_controller.rb
  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      # 更新に成功した場合を扱う。
    else
      render 'edit'
    end
  end
```

ここでもStrong Parametersの`user_params`を使っている。

Userモデルにはバリデーションとエラーメッセージのパーシャルが既に作成してるので、エラーメッセージがこの時点で出る。

演習

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/a517db29-4cba-e4d8-106d-e7e364a36b00.png)

メッセージが出たのでOK

###編集失敗時のテスト
エラー検知の統合テストを生成する。

```
$ rails generate integration_test users_edit
      invoke  test_unit
      create    test/integration/users_edit_test.rb
```

テストの内容
・編集ページにアクセスし、editビューが正しく描画されているか
・その後、無効な情報を送信、editビューが再描画されるか
　ここで、PATCHリクエストを送るのに、patchメソッドを使っていることが分かる。

```ruby:
require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "unsuccessful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user), params: { user: { name:  "",
                                              email: "foo@invalid",
                                              password:              "foo",
                                              password_confirmation: "bar" } }

    assert_template 'users/edit'
  end
end
```

これでテストすると成功する。

演習

これで、正しい数のエラーメッセージが表示されているかのテストはOK
`assert_select "div.alert", "The form contains 4 errors."`

###TDDで編集を成功させる
画像の編集は、Gravatarに任せているので、既に動作する。

次に「**受け入れテスト(Acceptance Tests)**」を書く。これは、ある機能の実装が完了し、ユーザー情報等のデータを入力できる状態=受け入れ可能状態になったかどうかを決めるテストのこと。

テスト内容
・有効な情報を送信
・フラッシュメッセージが空でないかどうか
・プロフィールページにリダイレクトすること
・データベース内のユーザー情報が正しく変更されたか

```ruby:test/integration/users_edit_test.rb
  test "successful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end
```

テストにて
`@user.reload`とあるが、これはデータベースから最新のユーザー情報よ読み直し、正しく更新されたかチェックしていうｒ。
`assert_equal`にて読み直した結果と、値が一致しているかテストしている。

`update`アクションに、`flash`と`redirect_to`を追記する。

```ruby:app/controllers/users_controller.rb
  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
  end
```

この時点でテストは失敗する。なぜなら、パスワードの長さのバリデーションに対して、パスワードを空にしているため、引っかかってしまう。
テストが成功するには、パスワードのバリデーションに対し、空だった時の例外処理を加えれば良い。
これを実装するため、`allow_nil: true`というオプションを`validates`に追加する。

``` ruby:
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
```

最初これ見た時に、「あれ？新規ユーザー登録時に空のパスワードが有効になるのでは」と思ったが、どうやら`has_secure_password`ではオブジェクト生成時に存在性を検証するため、空のパスワードが新規ユーザー作成時に有効になることはないとのこと。
前回、空のパスワードのエラーメッセージが2つあるという話があったが、今回の修正でエラーメッセージが1つだけになった。

これでテストは成功する。

演習
アップデートした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/6311313e-f979-f17e-ba67-96e120097d27.png)

##認可
WEBアプリケーションの文脈上の**認証(authetication)**と**認可(authorization)**の違い。

・認証：サイトのユーザーを識別すること
・認可：ユーザーが実行可能な操作を管理する事

`edit`アクションと`update`アクションは動作しているが、セキュリティ上ミスがある。それは、どのユーザー(ログインしてないユーザーでさえも)でもユーザー情報を編集できてしまう。

ユーザーにログインを要求して、かつ自分以外のユーザー情報を変更できないようにすることを、セキュリティモデルと呼ぶ。

・ログインしていなければ、ログインページに転送＋メッセージを表示。
・ログイン済みユーザーが別のユーザーにアクセスしようとした場合、ルートURLにリダイレクトさせる。

###ユーザーにログインを要求する
未ログインなら、ログインページに転送という動作をさせるには、`before`フィルターを使う。これは、何らかの処理が実行される直前に特定のメソッドを実行するもの。
今回は、ユーザーにログインを要求したい。
なので、`logged_in_user`メソッドを定義し、`before_action`に組み込む。

```ruby:app/controllers/users_controller.rb
  before_action :logged_in_user, only: [:edit, :update]

    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
```

boforeフィルターはデフォルトだとコントローラ内のすべてのアクションに適用されてしまうため、`:only`オプション(ハッシュ)を使い、`:edit`と`:update`だけフィルタを適用している。

現時点ではテストは失敗する。
理由は、editアクションやupdateアクションでログインを要求するようになり、未ログインユーザーだとこれらのテストが失敗するから。

なので、テストする前にログインしておく必要がある。
`log_in_as`ヘルパーを使えばOK

```ruby:test/integration/users_edit_test.rb
  test "unsuccessful edit" do
    log_in_as(@user)
    get edit_user_path(@user)
    .
    .
    .
  end

  test "successful edit" do
    log_in_as(@user)
    get edit_user_path(@user)
    .
    .
    .
  end
```

これでテストは成功
しかし、`before`フィルターをコメントしてもテストが通ってしまうため、もし`before`フィルターをコメントアウトされセキュリティホールが作られたら、非常にまずい。
なので、それを検出できるテストを書く。

```ruby:
  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "should redirect edit when not logged in" do
    get edit_user_path(@user)
    assert_not flash.empty?
    assert_redirected_to login_url
  end

  test "should redirect update when not logged in" do
    patch user_path(@user), params: { user: { name: @user.name,
                                              email: @user.email } }
    assert_not flash.empty?
    assert_redirected_to login_url
  end
```

テストとしては
①正しい種類のHTTPリクエストを使う
②editアクションとupdateアクションをそれぞれ実行
③flashにメッセージが代入されたかどうか
④ログイン画面にリダイレクトされたか

これで、きちんと`log_in_user`が動作してるか分かる。

これでコメントアウトした時にテストが失敗し、コメントアウトを解除すればテストが成功する。

演習

```
..F

Failure:
SiteLayoutTest#test_layout_links [/home/ubuntu/environment/sample_app/test/integration/site_layout_test.rb:15]:
Expected at least 1 element matching "title", found 0..
Expected 0 to be >= 1.


rails test test/integration/site_layout_test.rb:5

....F

Failure:
UsersControllerTest#test_should_get_new [/home/ubuntu/environment/sample_app/test/controllers/users_controller_test.rb:11]:
Expected response to be a <2XX: success>, but was a <302: Found> redirect to <http://www.example.com/login>
Response body: <html><body>You are being <a href="http://www.example.com/login">redirected</a>.</body></html>


rails test test/controllers/users_controller_test.rb:9

....F

Failure:
UsersSignupTest#test_invalid_signup_information [/home/ubuntu/environment/sample_app/test/integration/users_signup_test.rb:13]:
expecting <"users/new"> but rendering with <[]>


rails test test/integration/users_signup_test.rb:5

F

Failure:
UsersSignupTest#test_valid_signup_information [/home/ubuntu/environment/sample_app/test/integration/users_signup_test.rb:20]:
"User.count" didn't change by 1.
Expected: 2
  Actual: 1
```

###正しいユーザーを要求する
ここでは、ユーザーが自分の情報だけを編集できるようにする。

ユーザーの情報が互いに編集できないことを確認したいので、サンプルユーザーを一人追加する。

```yml:test/fixtures/users.yml
archer:
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>
```

そして、`log_in_as`メソッドを使い、`edit`アクションと`update`アクションでテストする。

```
  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end
  .
  .
  .
  test "should redirect edit when logged in as wrong user" do
    log_in_as(@other_user)
    get edit_user_path(@user)
    assert flash.empty?
    assert_redirected_to root_url
  end

  test "should redirect update when logged in as wrong user" do
    log_in_as(@other_user)
    patch user_path(@user), params: { user: { name: @user.name,
                                              email: @user.email } }
    assert flash.empty?
    assert_redirected_to root_url
  end
```

`@other_user`でログインして、`@user`の編集ページや更新を行おうとする時のテストを書いている。

テストを成功させるための、処理を書く。

```ruby:app/controllers/users_controller.rb
    before_action :correct_user,   only: [:edit, :update]
.
.
.
    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless @user == current_user
    end
```

`correct_user`メソッドを作成し、これをbeforeフィルターから呼ぶ。
`correct_user`メソッドでは、取得したユーザーを`@user`にいれ、`current_user`と比較して、一致しなければ、ルートURLにリダイレクトさせる。

ここで、リファクタリングを行う。
`current_user?`という論理値を返すメソッドにする。

```ruby
unless @user == current_user
```

から

```
unless current_user?(@user)
```

になる。

このメソッドは、Sessionsヘルパーの中にメソッドを定義する。

```ruby:app/helpers/sessions_helper.rb
  # 渡されたユーザーがカレントユーザーであればtrueを返す
  def current_user?(user)
    user && user == current_user
  end
```

演習

`update`アクションを保護しないとcurlコマンドで直接値を送れるから。

editの方が簡単にテストできる。

###フレンドリーフォワーディング
ここは、親切心で機能を実装することが良いとのことで実装する。

現状の課題
①未ログインユーザーが編集ページにアクセスしようとする。
②当然、ログインページページにリダイレクトされる。
③ログイン後には、問答無用で自分のプロフィールページに移動する。

本来は編集ページにアクセスしたかったのだから、ログイン後は編集ページにリダイレクトされるのが優しさ。

テストとしては
①編集ページにアクセス
②ログインする
③編集ページにリダイレクトされるかチェック

テストを書く

```ruby:test/integration/users_edit_test.rb
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user)
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end
```

ユーザーを希望のページに転送させるには、リクエスト時点のページを保存し、その場所にリダイレクトさせればOK。
この動作をさせるには
・`store_location`
・`redirect_back_or`
２つのメソッドを定義し、使えば良い。なお、これらはSessionsヘルパーで定義する。

```ruby:app/helpers/sessions_helper.rb
  # 記憶したURL（もしくはデフォルト値）にリダイレクト
  def redirect_back_or(default)
    redirect_to(session[:forwarding_url] || default)
    session.delete(:forwarding_url)
  end

  # アクセスしようとしたURLを覚えておく
  def store_location
    session[:forwarding_url] = request.original_url if request.get?
  end
```

`store_location`メソッドは、リクエストが送られたURLを`session`変数の`:forwarding_url`キーに格納するが、GETリクエスト時のみにしたいので、`if request.get?`としている。
これにより、未ログインユーザーがフォームを送信した時、転送先のURLを保存させないようにできる。
例として、ユーザーがセッション用のcookieを手動で削除後、フォームから送信するケース等。
POSTやPATCH、DELETEリクエストを期待しているURLに対して、GETリクエストが送られてしまって、エラーが発生する。
この現象の回避のために、`if request.get?`という条件分を使っている。

先ほどの`store_location`メソッドを使う。

```ruby:app/controllers/users_controller.rb
    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
```

`redirect_back_or`メソッドで、リクエストされたURLが存在する場合は、そこへリダイレクトし、ない場合はデフォルトのURLにリダイレクトする。
なお、デフォルトURLはSessionコントローラーの`create`アクションに追加して、ログイン成功時にリダイレクトさせる。

`session[:forwarding_url] || default`
これについて、or演算子は`||`で表現され、左側の`session[:forwarding_url]`が`nil`でなければ、こちらを評価し、そうでなければデフォルトURLを使う。
また、`session.delete(:forwarding_url)`で転送用のURLは削除する。
これは、次回ログイン時に保護されたページへ転送され、ブラウザを閉じるまでこれが繰り返されるから。
(sessionはブラウザを閉じるまで値を保持することを以前習った)

`redirect_back_or`を`create`アクションに追加

```ruby:app/controllers/sessions_controller.rb
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      redirect_back_or user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end
```

`redirect`文に関してだが、これは明示的に`return`文やメソッド内の最終行が呼び出されない限りは、リダイレクトが発生しない。つまり、リダイレクトはメソッドの最後に行われる。

※railsチュートリアルでは、`redirect_back_or user`となっていて、`user`としているが、`@user`としないと`users_login_test`でエラー発生するので、ここでは`@user`を使おう。

これでテストは成功する。

演習

`assert_nil session[:forwarding_url]`を追加する。

```ruby:test/integration/users_edit_test.rb
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    log_in_as(@user)
    assert_nil session[:forwarding_url] #追加
    assert_redirected_to edit_user_url(@user)
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end
```

##すべてのユーザーを表示する
ここでは、ユーザー出力で**ページネーション**を使って、ユーザー一覧ページを作る。

ページネーションとは、よくある[1][2][3]とかページが分割されているやつ。

###ユーザーの一覧ページ
`show`ページに関しては、ログインの有無に関わらず捨ててのユーザーが閲覧できるようにする。
`index`ページは、ログインユーザーのみ閲覧可能とする。

`index`ページを不正アクセスから守るために、`index`アクションが正しくリダイレクトしてくれるかの検証

```ruby:test/controllers/users_controller_test.rb
  test "should redirect index when not logged in" do
    get users_path
    assert_redirected_to login_url
  end
```

続いて、beforeフィルターの`logged_in_user`に`index`アクション追加し保護する。

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

  def index
　　@users = User.all
  end
```

`@users`に全ユーザーを格納する。
(すべてのユーザーを一気に読みだすと問題が生じるので後で修正する)

全てのユーザーを順々に表示するためのindexビュー実装する。

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

`each`メソッドを使い、`li`タグの中に次々と表示する。表示内容は、各ユーザーのGravatarと名前。
`gravatar_for`というものは下記で定義してえある。

```ruby:app/helpers/users_helper.rb
module UsersHelper

  # 渡されたユーザーのGravatar画像を返す
  def gravatar_for(user, options = { size: 80 })
    size         = options[:size]
    gravatar_id  = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end
```

SCSSに手を加える。

```scss:app/assets/stylesheets/custom.scss
/* Users index */

.users {
  list-style: none;
  margin: 0;
  li {
    overflow: auto;
    padding: 10px 0;
    border-bottom: 1px solid $gray-lighter;
  }
}
```

最後に`_header.html.erb`パーシャルにユーザー一覧表示用のリンクを追加する。
`user_path`を使って、名前付きルートを割り当てる。

` <li><%= link_to "Users", users_path %></li>`

これでテストして成功した。

###サンプルユーザー
複数のユーザーを手作業で一つずつ追加するのはしんどいので、一気にユーザーを登録しよう。

`Gemfile`にFaker gemを追加する。これはありそうなユーザー名を作成してくれるやつ。

`gem 'faker',                   '2.1.2'`

次にいつも通りのバンドルインストール。

`$ bundle install`

データベース上にサンプルユーザーを生成するRialsタスク

```ruby:db/seeds.rb
# メインのサンプルユーザーを1人作成する
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
```

一人のExample userという名前のユーザーとメールアドレスと、99人のそれっぽいユーザーを作成する。
`create!`は、ユーザーが無効だったら`false`ではなく例外を発生させるもの。
これでエラーを回避できて良い。

データベースのリセットとRailsタスク実行

```
$ rails db:migrate:reset
$ rails db:seed
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/be8523a7-ac1d-e97a-b144-eca275912767.png)

ユーザー沢山いる。

###ページネーション
一つのページに数千ものユーザーが表示されるのは良くない。
そのため、ページ分割機能を実装する。

今回は最もシンプルな`will_paginate`メソッドを使う。
`Gemfile`に`will_paginate`gemと`bootstrap-will_paginate`gemをいれ、Bootstrapのページネーションスタイルを活用し、will_paginateを構成する。

```Gemfile
gem 'will_paginate',           '3.1.8'
gem 'bootstrap-will_paginate', '1.0.0'
```

バンドルインストールし、Webサーバーの再起動をする。

`$ bundle install`

ページネーションをRailsに指示するコードをindexビューに追加し、`User.all`をページネーションを理解できるオブジェクトに変換する必要がある。

```ruby: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 %>
```

`<%= will_paginate %>`は、`@users`オブジェクトを自動的にみつけ、それから他のページへのアクセスするためのページネーションリンクを作成する。
しかし、現時点では動作しない。理由としては、`@users`変数には`User.all`の結果があるが、`will_paginate`では`paginate`メソッドを使った結果が必要だから。

`paginate`は、キーが`:page`で値がページ番号となる。
`User.paginate`では、`:page`パラメータを見て、データベースから一塊のデータを取り出す。例えば30だったら、1ページ目は、1から30で2ページ目は31から60のような感じ。
`page`が`nil`だったら、`paginate`は最初のページを返す。

`index`アクション内の`all`を`paginate`メソッドにすれば、ページ毎の一塊分のデータを取れる。
`:page`パラメータには、`params[:page]`が使われる。

```ruby:app/controllers/users_controller.rb
  def index
    @users = User.paginate(page: params[:page])
  end
```

これでユーザー一覧のページネーションができた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/411a6b7e-c631-9473-f6ba-f8608af1f79d.png)

ページネーションオブジェクトのクラスは、`ActiveRecord_Relation`クラスになる。

###ユーザー一覧のテスト
ページネーションが実装できたので、これに対するテストを書いておく。

テスト内容
・ログインしindexページにアクセス
・最初のページにユーザーがいることを確認
・ページネーションのリンクがあることを確認

fixtureには埋め込みRubyをサポートしているので、fixtureにユーザーを追加していく。。

```yml:test/fixtures/users.yml
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>

archer:
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>

lana:
  name: Lana Kane
  email: hands@example.gov
  password_digest: <%= User.digest('password') %>

malory:
  name: Malory Archer
  email: boss@example.gov
  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ページへのテストを書くので、統合テストを生成する。

`$ rails generate integration_test users_index`

テストとしては、`pagination`クラスを持った`div`タグをチェックし、ユーザーがいるか確認している。

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

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  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
```

###パーシャルのリファクタリング
最初のリファクタリングは、ユーザーの`li`タグを`render`呼び出しに変える事。

```erb: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 %>　←renderにする
  <% end %>
</ul>

<%= will_paginate %>
```

パーシャルは`index`に表示していた内容を持ってくる。

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

`index`ビューの`render`を`@users`にする。

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

<%= will_paginate %>

<ul class="users">
  <%= render @users %> ←@usersにする
</ul>

<%= will_paginate %>
```

`@users`にすることで、each doを使わずにリスト表示ができる。
理由としては、Railsは`@users`をUserオブジェクトとして認識し、ユーザーコレクションを与えると、ユーザーを列挙する。それぞれのユーザーは`_user.html.erb`パーシャルで出力する。

##ユーザーを削除する
`destroy`アクションでユーザーを削除できるようにしよう。

内容
・ユーザーを削除するためのリンク追加
・削除を行う`destroy`アクション追加
・管理権限(admin)ユーザークラスを作成。(承認においては、このような特権のセットをroleと呼ぶ)

###管理ユーザー
特権を持つ管理ユーザーを識別するのに、論理値をとる`admin`属性をUserモデルに追加する。
これで自動的に`admin?`メソッド(論理値を返す)を使えるようになる。

`$ rails generate migration add_admin_to_users admin:boolean`

```ruby:db/migrate/[timestamp]_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :admin, :boolean, default: false
  end
end
```

`default: false`にすることで、デフォルトでは管理者になれないということを示せるが、別に`default: false`にしなくても`admin`の値はデフォルトで`nil`になる。`false`と同じなので、必ず記載しなければならないという訳ではないが、意図的に開発者に示せる。

マイグレートする。
`$ rails db:migrate`

仕上げに管理者にするサンプルデータを更新する。

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

サンプルデータを再度生成する。

```
$ rails db:migrate:reset
$ rails db:seed
```

再びStrong Parametersの出番
`admin: true`とするとユーザーを管理者にできるわけだが、Webリクエストの初期化ハッシュをオブジェクトに渡せると、PATCHリクエストを送信されるかもしれない。

`patch /users/17?admin=1`

これだと17番目のユーザーに管理者権限を与えてやばいことになる。
なので、Strong Parametersで対策する。

paramsハッシュに対して、requireとpermitを呼び出す。

```ruby
    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
```

許可された属性リストに`admin`が含まれないことによって、管理者権限を与えることを防止できる。


###destroyアクション
`destroy`アクションへのリンクを追加する。

流れ
・ユーザーindexページの各ユーザーに削除用リンクを追加
・管理ユーザーへのアクセス制限
・これにより、現在のユーザーが管理者の時に限り`delete`リンクが表示される

ユーザー削除用リンク

```erb:app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete", user, method: :delete,
                                  data: { confirm: "You sure?" } %>
  <% end %>
</li>
```

通常ブラウザはネイティブで`DELETE`リクエストを送信できないので、RailsではJavaScriptを使って偽造している。
なので、JavaScriptがオフになっているとユーザー削除用のリンクも無効になる。
そのため、JavaScriptをサポートしないブラウザでも使えるようにする場合、フォームと`POST`リクエストを使い、`DELETE`リクエストを偽造することで、JavaScriptがなくても動作する。

`destroy`アクションの追加

```ruby:app/controllers/users_controller.rb
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  def destroy
    User.find(params[:id]).destroy
    flash[:success] = "User deleted"
    redirect_to users_url
  end
```

`destroy`アクションは、該当するユーザーを見つけてActive Recordの`destroy`メソッドを使って削除し、ユーザーindexにリダイレクトする。
ユーザーを削除するためにはログインの必要があるので、`logged_in_user`フィルターに`:destroy`アクションを追加している。

しかし、これではセキュリティホールがある。
攻撃者がコマンドラインで`DELETE`リクエストを直接発行するという方法でサイトの全ユーザーを削除してしまうというもの。
なので、`destroy`アクションにもアクセス制御を行う必要がある。

beforeフィルターを使い、`admin_user`フィルターを作る。

```ruby:app/controllers/users_controller.rb
before_action :admin_user,     only: :destroy

  private
    # 管理者かどうか確認
    def admin_user
      redirect_to(root_url) unless current_user.admin?
    end
```

###ユーザー削除のテスト
まずfixtureファイルを修正し、サンプルユーザーの一人を管理者にする。

```yml:test/fixtures/users.yml
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
  admin: true
```

テスト手順
・`DELETE`リクエストを発行
・`destroy`アクションを直接動作
・2つのケースをチェック。1つは、未ログインユーザーなら、ログイン画面にリダイレクトさせる。2つ目は、ログイン済ユーザーであっても、管理者でなければ、ホーム画面にリダイレクトされる。

```ruby:test/controllers/users_controller_test.rb
  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end
  .
  .
  .
  test "should redirect destroy when not logged in" do
    assert_no_difference 'User.count' do
      delete user_path(@user)
    end
    assert_redirected_to login_url
  end

  test "should redirect destroy when logged in as a non-admin" do
    log_in_as(@other_user)
    assert_no_difference 'User.count' do
      delete user_path(@user)
    end
    assert_redirected_to root_url
  end
```

続いて、管理者ユーザーのふるまいのテストを追加する。
管理者であれば、ユーザー一覧画面に削除用リンクが表示される仕様を利用して、ユーザーが減ることを確認すれば良い。

```rb:test/integration/users_index_test.rb
  def setup
    @admin     = users(:michael)
    @non_admin = users(:archer)
  end

  test "index as admin including pagination and delete links" do
    log_in_as(@admin)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    first_page_of_users = User.paginate(page: 1)
    first_page_of_users.each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
      unless user == @admin
        assert_select 'a[href=?]', user_path(user), text: 'delete'
      end
    end
    assert_difference 'User.count', -1 do
      delete user_path(@non_admin)
    end
  end

  test "index as non-admin" do
    log_in_as(@non_admin)
    get users_path
    assert_select 'a', text: 'delete', count: 0
  end
```

##最後に
あとは、いつものようにGithubとHerokuにプッシュでOK

第11章と第12章では、メールアドレスを使ったアカウント有効化機能と、パスワード再設定機能をやる。