Ruby on Rails チュートリアル 第10章 ユーザー更新 beforeフィルター フレンドリーフォワーディング adminまで

#近況報告

エンジニア転職成功しました。YouTubeもはじめました。

https://www.youtube.com/channel/UCcXQG8lu_EdyP2Cbhtz1bcA


[前回の続き](https://qiita.com/yuuki_netb/items/639cbace7cb806379452)

著者略歴
著者：[YUUKI](https://twitter.com/bitcoinjpnnet)
ポートフォリオサイト：[Pooks](https://pooks.herokuapp.com/)
現在：RailsTutorial2周目

#第10章　ユーザーの更新・表示・削除　難易度 ★★★　5時間
<b>挫折しないRailsチュートリアルの進め方を先にお読みください↓↓</b>

[Railsチュートリアルで挫折しない３つのポイント](https://webnewage.com/rails-tutorial-point)

この章ではUsersリソース用のRESTアクションのうち、これまで未実装だった

- edit
- update
- index
- destroy

アクションを加えて**RESTアクション**を完成させる。

まずはユーザーが自分のプロフィールを更新できるようにする。
その際、8章で実装した認証用のコードを使う(認可モデル(Authorization Model)についても考える)

次に、全てのユーザーを一覧できるようにする。
ここでページネーションと呼ばれるページ分割機能を導入する。

最後に、ユーザーを削除し、DBから完全に消去する機能を追加する。

ユーザーの削除には、管理ユーザーという**特権クラス**を作成し、このユーザーにのみ削除を許可する。


##10.1 ユーザーを更新する

ユーザー情報を編集するパターンは、新規ユーザーの作成と似ている。

例えば、新規ユーザー用のビューはnewアクションで、createアクションへPOSTリクエストしていたが、
ユーザー情報を編集する場合は、**newの代わりにeditアクション、createアクションの代わりにPATCHリクエストに対応するupdateアクション**を作成する。

ここでの最大の違いは、ユーザーの登録は誰でも実行できるが、
**ユーザー情報を更新できるのはそのユーザー自身に限られる**ということ。

８章で実装した認証機構を使えば、beforeフィルダーを使ってこのアクセス制御を実現できるとのこと。

```ruby
git checkout -b updating-users
```

###10.1.1 編集フォーム

編集フォームのモックアップ

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/7c470f67-8515-a222-041d-20a97e7ff249.png)

出典:[図 10.1: ユーザー編集ページのモックアップ](https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#cha-updating_showing_and_deleting_users)

このようなページを動かすには、Usersコントローラにeditアクションを追加して、それに対応するeditビューを実装する必要がある。

まずはeditアクションの実装から始めるが、ここではDBから適切なユーザーデータを読み込む必要がある。

ここで注意しておきたいのは、Usersリソースが提供するユーザー編集ページの正しいURLガ`/users/1/edit`となっている点。

ユーザーのidは`params[:id]`変数で取り出すことができるので、以下のようにeditアクションを指定することで、ユーザーを受け取ることができる。

```rg:users_controller.rb
  def edit
    @user = User.find(params[:id])                                              # URLのユーザーidと同じユーザーをDBから取り出して@userに代入
  end
```

次にユーザー編集ページに対応するビューを作成する。

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

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    
    <!--formの送信先を指定-->
    
    <%= form_for(@user) do |f| %>
    
      <!--エラーメッセージ-->

     <%= render 'shared/error_messages' %>

      <!--form作成-->
      
     <%= f.label :name %>
     <%= f.text_field :name, class: 'form-control' %>
     
     <%= f.label :email %>
     <%= f.text_field :email, class: 'form-control' %>
     
     <%= f.label :password %>
     <%= f.password_field :password, class: 'form-control' %>
     
     <%= f.label :password_contfirmation, "Confirmation" %>
     <%= f.password_field :password_confirmation, class: "form-control" %>
     
     <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
    
    <!--編集完了ボタンと画像を表示-->
    
    <div class="grabatar_edit">
      <%= gravatar_for @user %>
      <a fref="http://gravatar.com/emails" target="_blank">編集</a>
    </div>
  </div>
</div>
```


上のコードでは、newビューで使った`error_messages`パーシャルを再利用している。
一度設定したので、エラーメッセージは使い回しでOK。

また、Gravatarへのリンクで`target="_blank"`を使っているが、これを使うと新しいタブが開くようになっているが、セキュリティ上の問題もある。(後の演習で解説する)

@userインスタンス変数を使ったことで、編集ページうまく描画されていることを確認する。

![スクリーンショット 2019-01-10 23.47.09.png](https://qiita-image-store.s3.amazonaws.com/0/244639/03afaf36-35fb-ca9f-54a3-66b87e0c7267.png)

ここで、NameやEmailの部分に名前やメールアドレスのフィールドの値が自動的に入力されていることが分かる。
これは、editアクションでDBからidのユーザーを取り出して、fブロック変数で一個一個のフォームに入れてるからである。

ここで、HTMLソースを確認。

```ruby
<form class="edit_user" id="edit_user_1" action="/users/1" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="&#x2713;" />
<input type="hidden" name="_method" value="patch" />
```

さらに、入力フィールドに隠し属性があることに注目。

```ruby
<input type="hidden" name="_method" value="patch" />
```

Webブラウザは**ネイティブではPATCHリクエストを送信できない。**
RailsはPOSTリクエストと隠しinputフィールドを利用してPATCHリクエストを偽造している。
(単語集のhiddenを参照)

patchの値がhidden属性によって、`_method`という名前と共に、隠して送信される。

ここでもう一つ、form_for(@user)のコードは、newビューと完全に同じ。

しかし、Railsはどうやって新規ユーザー用のPOSTリクエストと
ユーザー編集用のPATCHリクエストを区別するのか。

その答えは、Railsは新規ユーザーか、既存のDBにいるユーザーか、
**Active Record**の`new_record?`論理値メソッドを使って区別できるから。

Rails Consoleで確認してみる。

```ruby
>> User.new.new_record?
=> true
>> User.first.new_record?
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> false
```

Railsは、`form_for(@user)`を使ってフォームを構成すると、`@user.new_record?`がtrueの時にはPOSTリクエスト、falseのときにはPATCHリクエストを使う。

仕上げに、ナビゲーションバーにあるユーザー設定へのリンクを更新する。

Usersリソースの名前付きルートである、`edit_user_path`と、`current_user`というヘルパーメソッドを使うと、実装が簡単。

```ruby
<%= link_to "Settings", edit_user_path(current_user) %>
```

これを、headerパーシャルに差し込む。

```erb:_heaer.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home", root_path %></li>
        <li><%= link_to "Help", help_path %></li>
        <% if logged_in? %>                                                     <!-- ユーザーがログインしている場合の処理 -->
          <li><%= link_to "Users", '#' %></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>                        <!-- ログインURLを自動生成-->
          <% end %>
      </ul>
    </nav>
  </div>
</header>
```

#### 演習

1:`target="_blank"`で新しいページを開くときには、セキュリティ上の小さな問題がある。
それは、リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう、という点。

例えばフィッシングサイトのような悪意のあるサイトを導入させられてしまう恐れがあるので、
それを防ぐために、aタグのrel(relationship)属性に`rel="noopener"`と設定するだけ。

Gravatarの編集ページへのリンクにこの設定をしてみる。

```erb:edit.html.erb
    <!--編集完了ボタンと画像を表示-->
    
    <div class="grabatar_edit">
      <%= gravatar_for @user %>
      <a fref="http://gravatar.com/emails" target="_blank" rel="noopener">編集</a>
    </div>
```

```erb:new.html.erb
<% provide(:title, 'Sign up') %>
<% provide(:button_text, "新規登録") %>
<h1>新規登録</h1>
  <%= render 'form' %>
```

2:`_form.html.erb`のパーシャルを使って、`new.html.erb`ビューと、`edit.html.erb`ビューをリファクタリングしてみる。三章で使った、`provide`メソッドを使って重複を取り除く。

```erb:_form.html.erb
    <!--ボタンと画像を表示-->

    <div class="row">
      <div class="col-md-6 col-md-offset-3">
    <!-- formの送信先を指定 -->
    
    <%= form_for(@user) do |f| %>                                               <!-- 、fブロックに代入-->
      <%= render 'shared/error_messages', object: @user %>                      <!-- エラーメッセージ用のパーシャルを表示 -->

    <!-- form作成-->
    
      <%= f.label :name %>                                                      <!-- Userモデルの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 yield(:button_text), class: "btn btn-primary" %>
    <% end %>
      </div>
    </div>
```

```erb:new.html.erb
<% provide(:title, 'Sign up') %>
<% provide(:button_text, "新規登録") %>
<h1>新規登録</h1>
  <%= render 'form' %>
```

これで綺麗にまとまった。

###10.1.2 編集の失敗

ユーザー登録に失敗した時と似た方法で、編集に失敗した場合について扱っていく。

まずはupdateアクションの作成から進める。

`update_attributes`を使って送信されたparmasハッシュに基づいて、ユーザーを更新する。

無効な情報が送信された場合、更新の結果としてfalseが返され、elseに分岐して編集ページをレンダリングする。
この構造は、`create`アクションの最初のバージョンと極めて似通っている。

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

更新に失敗したらelseに分岐して編集ページをレンダリング。

`update_attributes`での呼び出しで、`user_params`を引数に取っている点に注目

7章で扱ったStrong Parametersを使って、マスアサインメントの脆弱性を防止している。

Userモデルのバリデーション、エラーメッセージのパーシャルは既にあるので
無効な情報を送信するとエラーメッセージが表示されるようになった。

![スクリーンショット 2019-01-11 8.29.28.png](https://qiita-image-store.s3.amazonaws.com/0/244639/1e62cc65-52c2-7b7a-851d-b99c5726f95a.png)

#### 演習

1:編集フォームから有効でない値を送信して、編集に失敗することを確認。

確認済み。

###10.1.3 編集失敗時のテスト

編集失敗のエラーを検知するための統合テストを書いていく。
とりあえずまずは統合テストを生成

```ruby
$ rails g integration_test users_edit
```

最初のテストは、まず編集ページにアクセスし、editビューが描画されるかどうかをチェック。
次に、無効な情報を送信してみて、editビューが再描画されるかどうかをチェック。

```ruby
require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest
  
  def setup
    @user = users(:michael)
  end
  
  test "unsuccessful edit" do
    get edit_user_path(@user)                                                   # userIDを取得(michael)
    assert_template 'users/edit'                                                # editビューが描画できてたらtrue
    patch user_path(@user), params: { user: { name: "",                         # 引数としてわざと失敗する値を持ったuserIDをpatchリクエストで送信（更新）する
                                              email: "foo@invalid",
                                              password:              "foo",
                                              password_confirmation: "bar" } }
    assert_template 'users/edit'                                                # editビューが描画できてたらtrue
  end
end
```

ここで、重要なのは、PATCHリクエストを送るために`patchメソッド`を使っている所。
これはgetやpost、deleteメソッドと同じように、HTTPリクエストを送信するためのメソッド。

テストを調べる。

```ruby
 FAIL["test_invalid_signup_information", UsersSignupTest, 0.4198383340044529]
 test_invalid_signup_information#UsersSignupTest (0.42s)
        Expected at least 1 element matching "form[action="/signup"]", found 0..
        Expected 0 to be >= 1.
        test/integration/users_signup_test.rb:19:in `block in <class:UsersSignupTest>'

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

Finished in 0.46134s
28 tests, 67 assertions, 1 failures, 0 errors, 0 skips
```

エラーやんけ！

実際にnewビューで送信してみると、URLが`/users`になる件。
そーいえば以前にもこんなのあったな。

ん、待てよ

```erb:new.html.erb
<% provide(:url, user_path) %>
```

```erb:edit.html.erb
<% provide(:url, signup_path) %>
```

こうやってそれぞれにurlを指定し、

```erb:_form.html.erb
    <%= form_for(@user, url: yield(:url)) do |f| %>                                               <!-- 、fブロックに代入-->
```

ってやればいいんじゃね。

```ruby
Finished in 0.54764s
8 tests, 31 assertions, 0 failures, 0 errors, 0 skips
```

よしきた。どうやら前回の演習でミスをしたようだw


####演習

1:`assert_select`を使って`alert`クラスのdivタグを探しだし、`The form contains 4 errors.`というテキストを精査してみる。


```ruby
 assert_select "div.alert", "The form contains 4 errors."
```


###10.1.4

今度は編集フォームが動作するようにする。

プロフィール画像の編集は画像アップロードをGravatarに任せてあるので、既に動作するようになっている。

changeボタンをクリックすれば、Gravatarを編集できる。

ここで、より快適にテストするためには、アプリ用のコードを実装する前に統合テストを書いた方が便利ということで、そういった**受け入れテスト**を行う。

受け入れテストは、ある機能の実装が完了し、受け入れ可能な状態になったかどうかを決めるテストとして知られている。

今回はテスト駆動開発を使ってユーザーの編集機能を実装してみる。

まずは、ユーザー情報を更新する正しい振る舞いをテストで定義する。

次に、flashメッセージが空でないかどうかと、プロフィールページにリダイレクトされるかどうかをチェックする。
また、DB内のユーザー情報が正しく変更されたかどうかも検証する。

```rb:users_edit_test.rb
  test "successful edit" do
    get edit_user_path(@user)                                                   # userIDを取得(michael)
    assert_template 'users/edit'                                                # editビューが描画できてたらtrue
    name  = "Foo Bar"                                                           # フォーム欄に値を入力する
    email = "foo@bar.com"                                                       
    patch user_path(@user), params: { user: { name: name,                       # 引数としてわざと失敗する値を持ったuserIDをpatchリクエストで送信（更新）する
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?                                                     # エラー文が空じゃなければtrue
    assert_redirected_to @user                                                  # michaelのユーザーidページへ移動できたらtrue
    @user.reload
    assert_equal name,  @user.name                                              # DB内の名前と@userの名前が一致していていたらtrue
    assert_equal email, @user.email                                             # DB内のEmailと@userの名前が一致
  end
```

パスワードとパスワード確認が空であることに注目。

ユーザー名やメルアドを編集する時に毎回パスワードを入力するのは不便なので、
わざとパスワードを入力せずに更新している。

また、user.reloadを使ってDBから最新のユーザー情報を読み込み直して、正しく更新されたかどうか確認している点にも注目。

受け入れテストでは先にテストを書くので、効果的なユーザー体験について考えるようになる。


テストにパスする必要のあるupdateアクションは、**createアクションの最終的なフォームとほぼ同じである。**

```rb:users_controller.rb
  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      flash[:success] = "プロフィール更新完了"
      redirect_to @user
    else
      render 'edit'
    end
  end
```

このテストはまだ失敗する。
理由として、パスワードの長さに対するバリデーションがあるので、パスワードやパスワード確認の欄を空にしておくと引っかかってしまう恐れがあるから。

テストをパスさせるためには、空だった時の**例外処理**を加える必要がある。

そのために、Userモデルのバリデーションに**allow_nil :true**というオプションを使う。

```rb:user.rb
class User < ApplicationRecord
  # インスタンス変数の定義
  attr_accessor :remember_token
  
  
  before_save { email.downcase! }                                               #DB保存前にemailの値を小文字に変換する
  validates :name, presence: true, length: { maximum: 50 }                      #nameの文字列が空でなく、50文字以下ならtrue
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i                      #正規表現でemailのフォーマットを策定し、定数に代入
  validates :email, presence: true, length: { maximum: 255 },                   #emailの文字列が空でなく、255文字以下ならtrue
                    format: { with: VALID_EMAIL_REGEX },                        #emailのフォーマットを引数に取ってフォーマット通りか検証する。
                    uniqueness: { case_sensitive: false }                       #大文字小文字を区別しない(false)に設定する　このオプションでは通常のuniquenessはtrueと判断する。
  
  has_secure_password                                                           #passwordとpassword_confirmation属性に存在性と値が一致するかどうかの検証が追加される
  validates :password, presence: true,length: { minimum: 6 }, allow_nil: true   #passwordの文字列が空でなく、6文字以上ならtrue。例外処理に空(nil)の場合のみバリデーションを通す(true)
```

これでpasswordが空だったとしたらvalidationをスルー(true)する例外処理を加えたが、
has_secure_passwordでは**DBにレコード(オブジェクト)が生成された時だけ存在性(nilかどうか)のvalidationを行う**
性質があるので、実際にpasswordを作成する際は、nilかどうかの検証を行ってくれる。

つまり、**実行環境でpasswordが空だった場合のvalidation機能を保持したまま、テストで空だった場合にvalidationを通すことができるという訳。(今回のテストではuserオブジェクトを生成していないので)**

また、allow_nilのおかげでhas_secure_passwordによるバリデーションがそれぞれ実行され、二つのエラーメッセージが表示されるバグも解決できた。(オブジェクト生成時と、実際に空かどうかの二つの検証を行っていた為)


![スクリーンショット 2019-01-11 19.35.31.png](https://qiita-image-store.s3.amazonaws.com/0/244639/e9c678f4-3137-c24c-8b28-5005f4697cec.png)

ほんとだ。

これでテストはパスする。
![スクリーンショット 2019-01-11 20.08.13.png](https://qiita-image-store.s3.amazonaws.com/0/244639/8f88dfff-5a52-b8ed-ca8e-4c919035407b.png)


```ruby
$ rails t
```

![スクリーンショット 2019-01-11 20.06.01.png](https://qiita-image-store.s3.amazonaws.com/0/244639/d9b74cff-dcb6-6258-5c61-ebbdb65e4196.png)


実際に編集もできた。

#### 演習

1:実際に編集が成功するかどうか、有効な情報を送信して確かめる

確認済み

2:Gravatarと紐づいていない適当なメールアドレスに変更した場合、画像がどう変化するか確認

![スクリーンショット 2019-01-11 20.08.13.png](https://qiita-image-store.s3.amazonaws.com/0/244639/32990262-ac96-e587-bc9d-2fc3833b163d.png)

Gravatarとの連動も確認


##10.2 認可

第8章ではauthentication(認証)システムを構築することで、サイトのユーザーを識別する機能を加えたが、今回はそのユーザーをauthorization(認可)することで、ログインユーザーのみが実行可能な操作を実現する。

editアクションとupdateアクションにはセキュリティ上の大穴が一つある。
それは、**どのユーザーでもあらゆるアクションにアクセスできる**為、誰でもユーザー情報を編集出来てしまうところ。

今回はユーザーにログインを要求し、かつ自分以外のユーザー情報を変更できないように制御してみる。

さらに、ログインしていないユーザーが保護されたページにアクセスしようとした際に、ログインページに転送して、分かりやすいメッセージを表示させる仕組みも作る。

また、許可されていないページに対してアクセスするログイン済みのユーザーがいたら、ルートURLにリダイレクトさせるようにする。


モックアップはこちら


![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/3aa17564-f2f0-9842-534a-d92fa32ae937.png)

出典：[図 10.6: 保護されたページにアクセスしたときのページのモックアップ](https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#cha-updating_showing_and_deleting_users)


###10.2.1 ユーザーにログインを要求する

モックアップのように転送させる仕組みを実装したい時は、Usersコントローラの中で`beforeフィルター`を使う。

beforeフィルターは、`before_action`メソッドを使って、何らかの処理が実行される直前に特定のメソッドを実行する仕組み。

ユーザーにログインを要求するために、`logged_in_user`メソッドを定義して、`before_action:logged_in_user`という形式で使う。


```rb:users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]                         # editとupdateアクションにlogged_in_userメソッドを適用

    def logged_in_user                                                          # ログイン済みユーザーかどうか確認
      unless logged_in?                                                         # ユーザーがログインしていなければ(false)処理を行う
        flash[:danger] = "Please log in."                                       # エラーメッセージを書く
        redirect_to login_url                                                   # ログインユーザーのidを引数に取ったURLのページへ飛ぶ
      end
    end
```

beforeフィルターはデフォルトで、コントローラ内の全てのアクションに適用されるので、:onlyオプションを渡すことで、editとupdateというアクションのみフィルタを適用されるよう制限を掛けた。

beforeフィルターを使って実装した結果、一度ログアウトしてユーザー編集ページ(/users/1/edit)にアクセスしてみることで確認できる。

![スクリーンショット 2019-01-12 0.15.42.png](https://qiita-image-store.s3.amazonaws.com/0/244639/f3ac7496-9243-ae0c-9a62-a070e8b72fa4.png)

怒られてloginページに飛ばされた。

また、テストはこの時点で失敗。
理由は、editアクションやupdateアクションでログインを要求するようになったため、ログインしていないユーザーだとこれらのテストで失敗してしまう。

このため、テストではeditアクションやupdateアクションを実行する前にログインしておく必要がある。

解決策は簡単。test_helperの`log_in_as`ヘルパーを使うだけ。

```rb:users_edit_test.rb
  test "unsuccessful edit" do
    log_in_as(@user)                                                            # ログインする
    get edit_user_path(@user)

  test "successful edit" do
    log_in_as(@user)                                                            # ログインする
```

これでテストはパスする。

setupメソッド内でログイン処理をまとめてしまうことも可能だが、後のテストでログインする前に編集ページにアクセスするように変更したいので、あえてこうしている。


```ruby
2 tests, 8 assertions, 0 failures, 0 errors, 0 skips
```


しかし、実はbeforeフィルターの実装はまだ終わっておらず、セキュリティモデルに関する実装を取り外してもテストが失敗するかどうか、実際にコメントアウトして確かめる必要がある。

```rb:users_controller.rb
  #before_action :logged_in_user, only: [:edit, :update]                         # editとupdateアクションにlogged_in_userメソッドを適用
```


何とテストが通ってしまった。

```ruby
$ rails t
```

これでは問題なので、対処をする。

beforeフィルターは基本的にアクションごとに適用していくので、
**Usersコントローラのテストもアクションごとに書いていく。**

具体的には

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

を確認する。

HTTPリクエストは、　editにはget、updateにはpatchを割り当てる。

```rb:users_controller_test.rb
require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest
  
  def setup
    @user = users(:michael)
  end
  
  test "should get new" do
    get signup_path
    assert_response :success
  end

  test "should redirect edit when not logged in" do
    get edit_user_path(@user)                                                   # ログインユーザーの編集ページを取得
    assert_not flash.empty?                                                     # flashが空でないならtrue
    assert_redirected_to login_url                                              # ログインユーザーのidのURLへ飛べたらtrue
  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?                                                     # flashが空でないならtrue
    assert_redirected_to login_url                                              # ログインユーザーのidのURLへ飛べたらtrue
  end

end
```

これで先ほどのbefore_actionをコメントアウトするとテストが失敗する。

```ruby
$ rails t
test/controllers/users_controller_test.rb:24:in `block in <class:UsersControllerTest>'
test/controllers/users_controller_test.rb:16:in `block in <class:UsersControllerTest>'
11 tests, 41 assertions, 2 failures, 0 errors, 0 skips
```

どうやらflashメッセージが表示されないのと、ログインページへリダイレクトできないのが原因見たい。

これでコメントアウトを解除すればtrue

```ruby
$ rails t
11 tests, 41 assertions, 0 failures, 0 errors, 0 skips
```

#### 演習

1:beforeフィルターを全てに適用して見てテストがそのエラーを検知できるかどうか確認。

```ruby
$ rails t
 FAIL["test_valid_signup_information", UsersSignupTest, 0.35811705499872915]
 test_valid_signup_information#UsersSignupTest (0.36s)
        "User.count" didn't change by 1.
        Expected: 2
          Actual: 1
        test/integration/users_signup_test.rb:25:in `block in <class:UsersSignupTest>'

 FAIL["test_invalid_signup_information", UsersSignupTest, 0.37334744699910516]
 test_invalid_signup_information#UsersSignupTest (0.37s)
        expecting <"users/new"> but rendering with <[]>
        test/integration/users_signup_test.rb:16:in `block in <class:UsersSignupTest>'

 FAIL["test_should_get_new", UsersControllerTest, 0.475691736999579]
 test_should_get_new#UsersControllerTest (0.48s)
        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>
        test/controllers/users_controller_test.rb:11:in `block in <class:UsersControllerTest>'
```

OK

###10.2.2 正しいユーザーを要求する

ユーザーが自分の情報だけを編集できるようにする必要がある。
ここではセキュリティモデルが正しく実装されている確信を持つために、テスト駆動開発で進めていく。

したがって、Usersコントローラのテストを補完するように、テストを追加していく。

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

ユーザー用のfixtureファイルに2人目のユーザーを追加してみる。

```yml: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') %>
```

次に、`log_in_as`メソッド(ログインユーザー)を使ってeditアクションとupdateアクションをテストする。

このとき、既にログイン済みのユーザーを対象としているため、ログインページではなくルートURLにリダイレクトしている点に注意。

```rb:users_controller_test.rb
  test "should redirect edit when logged in as wrong user" do                   # @other_userで編集できるか確認
    log_in_as(@other_user)                                                      # @other_userでログインする
    get edit_user_path(@user)                                                   # @userの編集ページを取得
    assert flash.empty?                                                         # flashが空ならtrue
    assert_redirected_to root_url                                               # root_urlへ移動できればtrue
  end
  
  test "should redirect update when logged in as wrong user" do
    log_in_as(@other_user)                                                      # @other_userでログインする
    patch user_path(@user), params: { user: { name:  @user.name,                # @userのユーザーページへ、フォームに入力したname値・email値を送信(更新)
                                              email: @user.email } }
    assert flash.empty?                                                         # flashが空ならtrue
    assert_redirected_to root_url                                               # root_urlへ移動できればtrue
  end
```

テストは失敗する。

ログイン済みの@other_userで、@userのページを取得・送信した際にルートURLへリダイレクトさせたいので、
`correct_user`というメソッドを作成し、beforeフィルターからこのメソッドを呼び出すようにする。

beforeフィルターのcorrectuserで@user変数を定義し、`edit`と`update`の各アクションから、`@user`の代入文を削除している点にも注意。

```rb:users_controller.rb
  before_action :correct_user,   only: [:edit, :update]                         # editとupdateアクションにcorret_userメソッドを適用

    def correct_user                                                            # 正しいユーザーかどうか確認
      @user = User.find(params[:id])                                            # URLのidの値と同じユーザーを@userに代入
      redirect_to(root_url) unless @user == current_user                        # @userと記憶トークンcookieに対応するユーザー(current_user)を比較して、失敗したらroot_urlへリダイレクト
    end
```

ちょっと難しいが、@userと記憶トークンcookieに対応するユーザーとを比較し、値が異なっていればroot_urlへリダイレクトしている。
要は、ログインユーザーとURLに入力されたユーザーが異なっていれば、root_urlに飛ばしますよ〜という処理。

root_urlに飛ばすことで、flash(エラーメッセージ)を無くしている。

これでテストはパスする。

最後にリファクタリングとして、current_user?という論理値を返すメソッドを実装する。
`correct_user`の中で使えるようにしたいのでSessionsヘルパーの中にこのメソッドを追加する。

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

これを

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

とする。

```rb:sessions_helper.rb
  # 渡されたユーザーがログイン済みユーザーであればtrueを返す  
  def current_user?(user)
    user == current_user
  end
```

先ほどのメソッドを使って比較演算していた行を置き換えると


```rb:users_controller.rb
    def correct_user                                                            # 正しいユーザーかどうか確認
      @user = User.find(params[:id])                                            # URLのidの値と同じユーザーを@userに代入
      redirect_to(root_url) unless current_user?(@user)                         # @userと記憶トークンcookieに対応するユーザー(current_user)を比較して、失敗したらroot_urlへリダイレクト
    end
```

これでテストはパスする。

####演習

1:何故editアクションとupdateアクションを両方とも保護する必要があるのか？

updateアクションにpatchリクエストを送って不正に更新しようとする行為を防止する為

2:上記アクションのうち、どちらがブラウザで簡単にテストできるか？

editやろ

###10.2.3 フレンドリーフォワーディング

Webサイトの認可機能は完成したかのようん見えるが、後1つ小さなキズがあります。

保護されたページにアクセスしようとすると、問題無用で自分のプロフィールページに移動させられてしまう。

例えば、ログインしていないユーザーが編集ページにアクセスしようとしていたなら、ユーザーがログインした後には、その編集ページにリダイレクトさせてあげるのが望ましい。

リダイレクト先は、ユーザーが開こうとしていたページにしてあげる。

フレンドリーフォワーディングのテストコードは、シンプルに書くことができて、例えばログインした後に編集ページへアクセスする、という順序を逆にしてあげるだけ。

具体的には、

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

これを、`users_edit_text.rb`で実際に行う

なお、リダイレクトによってedit用のテンプレが描画されなくなったので、edit_testにある`assert_template 'users/edit'`を削除する。

```rb:users_edit_test.rb
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)                                                   # @userのユーザー編集ページを取得
    log_in_as(@user)                                                            # @userでログイン
    assert_redirected_to edit_user_path(@user)                                  # @userのユーザー編集ページへ移動する
    log_in_as(@user)                                                            # ログインする
    get edit_user_path(@user)                                                   # userIDを取得(michael)
    name  = "Foo Bar"                                                           # フォーム欄に値を入力する
    email = "foo@bar.com"                                                       
    patch user_path(@user), params: { user: { name: name,                       # 引数としてわざと失敗する値を持ったuserIDをpatchリクエストで送信（更新）する
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?                                                     # エラー文が空じゃなければtrue
    assert_redirected_to @user                                                  # michaelのユーザーidページへ移動できたらtrue
    @user.reload
    assert_equal name,  @user.name                                              # DB内の名前と@userの名前が一致していていたらtrue
    assert_equal email, @user.email                                             # DB内のEmailと@userの名前が一致
  end
```



これでテストは失敗。

これでフレンドリーフォワーディングを実装する準備が整った。

ユーザーを希望のページに転送するには、リクエスト時点のページをどこかに保存しておき、その場所にリダイレクトさせる必要がある。
この動作を`store_location`と`redirect_back_or`の２つのメソッドを使って実現してみる。

これらのメソッドはSessionsヘルパーで定義している。

```rb: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?              # :forwarding_urlキーにリクエスト先のURLを、GETリクエストが送られた時だけ代入
  end
```

:forwarding_urlキーへの値の格納で、GETリクエストが送られたときだけ格納するようにする。
こうすることで、例えばログインしていないユーザーがフォームを使って送信した場合、転送先のURLを保存させないようにする。

これは稀なケースだが起こりえる。

例えば、ユーザがセッション用のcookieを手動で削除して、フォームから送信するケース。

こういったケースに対処しておかないと、POSTやPATCH、DELETEリクエストを期待しているURLに対して、GETリクエストが送られてしまい、場合によってはエラーが発生する。

これらの問題を
`if request.get?`という条件文を使って対応している。

先ほど定義した`store_location`メソッドを使って、早速beforeフィルター`logged_in_user`を修正してみる。

```rb:users_controller.rb
    # ログイン済みユーザーかどうか確認
    def logged_in_user                                                          # ログイン済みユーザーかどうか確認
      unless logged_in?                                                         # ユーザーがログインしていなければ(false)処理を行う
        store_location                                                          # アクセスしようとしたURLを覚えておく
        flash[:danger] = "ログインしないとダメですよ"                     　　　　　　　　　　　　　　　　　　　　　　　　　　# エラーメッセージを書く
        redirect_to login_url                                                   # ログインユーザーのidを引数に取ったURLのページへ飛ぶ
      end
    end
```

これで、ログインできなかった場合、アクセスしようとしたURLを記憶する。


フォワーディング自体を実装するには、`redirect_back_or`メソッドを使う。
リクエストされたURLが存在する場合はそこにリダイレクトし、ない場合は何らかのデフォルトのURLにリダイレクトする。

デフォルトのURLは、Sessionコントローラのcreateアクションに追加し、サインイン成功後にリダイレクトする。
`redirect_back_or`メソッドでは、次のようにor演算子||を使う。

```ruby
session[:forwarding_url] || default
```

このコードは、値がnilでなければ`session[:forwarding_url]`を評価し、そうでなければデフォルトのURLを使っている。

また、`redirect_back_or(default)`メソッドでは、`session.delete(:forwarding_url)`という行を通して転送用のURLを削除している点にも注意。

これをやっておかないと、次回ログインしたときに保護されたページに転送されてしまい、ブラウザを閉じるまでこれが繰り返されてしまう。
ちなみに、最初にredirect文を実行しても、セッションが削除される。
実は、明示的にreturn文やメソッド内の最終行が呼び出されない限り、リダイレクトは発生しない。

したがって、redirect文の後にあるコードでも、そのコードは実行される。

```rb:sessions_controller.rb
  def create
    @user = User.find_by(email: params[:session][:email].downcase)              # paramsハッシュで受け取ったemail値を小文字化し、email属性に渡してUserモデルから同じemailの値のUserを探して、user変数に代入
    if @user && @user.authenticate(params[:session][:password])                 # user変数がデータベースに存在し、なおかつparamsハッシュで受け取ったpassword値と、userのemail値が同じ(パスワードとメールアドレスが同じ値であれば)true
      log_in @user                                                              # sessions_helperのlog_inメソッドを実行し、sessionメソッドのuser_id（ブラウザに一時cookiesとして保存）にidを送る
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)   # ログイン時、sessionのremember_me属性が1(チェックボックスがオン)ならセッションを永続的に、それ以外なら永続的セッションを破棄する
      redirect_back_or @user                                                    # userの前のページもしくはdefaultにリダイレクト
    else
      flash.now[:danger] = 'Invalid email/password combination'                 # flashメッセージを表示し、新しいリクエストが発生した時に消す
      render 'new'                                                              # newビューの出力
    end
  end
```

*Tutorialでは@userではなくuserが使われているが、使うとusers_login_testでエラーが発生するので@userを使う。

これで、フレンドリーフォワーディング用統合テストもパスする。

```rails
$ rails t
5 tests, 20 assertions, 0 failures, 0 errors, 0 skips
```

####演習

1:フロンドリーフォワーディングで、渡されたURLに初回のみ転送されていることを確認するテストを書く

```rb:users_edit_test.rb
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)                                                   # @userのユーザー編集ページを取得
    assert_equal session[:forwarding_url], edit_user_url(@user)                 # 渡されたURLに転送されているか確認
    log_in_as(@user)                                                            # @userでログイン
    assert_nil session[:forwarding_url]                                         # forwarding_urlの値がnilならtrue(deleteが効いてる)
    name  = "Foo Bar"                                                           # フォーム欄に値を入力する
    email = "foo@bar.com"                                                       
    patch user_path(@user), params: { user: { name: name,                       # 引数としてわざと失敗する値を持ったuserIDをpatchリクエストで送信（更新）する
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?                                                     # エラー文が空じゃなければtrue
    assert_redirected_to @user                                                  # michaelのユーザーidページへ移動できたらtrue
    @user.reload
    assert_equal name,  @user.name                                              # DB内の名前と@userの名前が一致していていたらtrue
    assert_equal email, @user.email                                             # DB内のEmailと@userの名前が一致
  end
```


2:debuggerメソッドを使ってSessionsコントローラのnewアクションに置いてみる。

その後、ログアウトして/users/1/editにアクセス。すると、デバッガーが途中で処理をやめる。
ここでコンソールでsession[:forwarding_url]の値が正しいかどうか、newアクションにアクセスした時のrezuest.get?の値も確認してみる。

```ruby
(byebug) "https://eac437457e484fe491559aaa135f7f93.vfs.cloud9.us-east-2.amazonaws.com/users/1/edit"
(byebug) true
```

OK。


##10.3 すべてのユーザーを表示する

全てのユーザーを表示するためのindexアクション(/users users_path)を追加する。

その際、DBにサンプルデータを追加する方法や、将来ユーザー数が膨大になってもindexページを問題なく表示できるようにするためのユーザー出力のページネーション（ページ分割）の方法を学ぶ。

ユーザーの一覧、ページネーション用リンク、移動用の[Users]リンクのモックアップはこちら

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/6e12b6f2-0ab5-9fcf-5991-5006d9cb340c.png)


出典：[図 10.8: ユーザー一覧ページのモックアップ](https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#cha-updating_showing_and_deleting_users)

###10.3.1 ユーザーの一覧ページ

実装前にセキュリティモデルについて考えてみる。

ユーザーのshowページはサイトを訪れた全てのユーザーから見えるようにしておくが、
ユーザーのindexページはログインしたユーザーにしか見えないようにし、未登録のユーザーがデフォルトで表示できるページを制限する。

indexページを不正なアクセスから守るために、まずはindexアクションが正しくリダイレクトするかを検証するテストを書いてみる。

```rb:users_controller_test.rb
  test "should redirect index when not logged in" do
    get users_path                                                              # index(/users)を取得
    assert_redirected_to login_url                                              # ログインページへリダイレクトできたらtrue
  end
```

次に、users_controllerのbeforeフィルターの`logged_in_user`にindexアクションを追加して、ログインしていなければログインページを飛ばす

```rb:users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]                         # editとupdateアクションにlogged_in_userメソッドを適用
  before_action :correct_user,   only: [:edit, :update]                         # editとupdateアクションにcorret_userメソッドを適用

  def index
  end
```

今度は、usersページで全てのユーザーを表示するために、全ユーザーが格納された変数を作成し、順々に表示するindexビューを実装する。
Toyアプリケーションにも同じindexアクションがあったことを思い出して、`User.all`を使って全DB上のユーザーを取得し、ビューで使えるインスタンス変数@users`に代入させる。

```rb:users_controller.rb
  def index
    @users = User.all
  end
```

実際のindexページを作成するには、ユーザーを列挙してユーザーごとにliタグで囲むビューを作成する必要がある。
ここでは`each`メソッドを使って作成する。

それぞれの行をulで囲いながら、各ユーザーのGravatarと名前を表示する。

```erb:index.html.erb
<% provide(:title, 'All users') %>
<h1>ユーザー一覧</h1>

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

ここでgravatar_for で画像を表示し、デフォルト以外のサイズを指定するオプションを渡している点に注目。

7章の演習でgravatar_forメソッドの引数にオプションでsizeを渡していたので、それを今回は変更している。

Gravatar側の準備が整ったら、SCSSにも手を加える。

```scss:custom.scss
/* ユーザー一覧 */

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

最後に、ヘッダーにユーザー一覧用のリンクを追加。
リンクには`users_path`(名前付きルート)を割り当てる。

```erb:_header.html.erb
          <li><%= link_to "Users", users_path %></li>                         <!-- ユーザー一覧リンク-->
```

これでテストは成功。


```ruby
$ rails t
```

ユーザー一覧も表示できた。

![スクリーンショット 2019-01-15 0.13.15.png](https://qiita-image-store.s3.amazonaws.com/0/244639/05fe4321-753b-eed8-dfb8-24e4c81cf79e.png)

####演習

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

```rb:site_layout_test.rb
require 'test_helper'

class SiteLayoutTest < ActionDispatch::IntegrationTest
  # test "the truth" do
  #   assert true
  # end
  
  test "layout links" do
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]",   help_path
    assert_select "a[href=?]",   about_path
    assert_select "a[href=?]",   contact_path
    assert_select "a[href=?]",   login_path
    get contact_path
    assert_select "title", full_title("Contact")
    get signup_path
    assert_select "title", full_title("Sign up")
  end
  
  def setup
    @user = users(:michael)
  end
  
  test "layout links when logged in" do                                         # ホームページ用の統合テスト
    log_in_as(@user)
    get root_path
    assert_template 'static_pages/home'
    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
```

全部描画されているのでtrue

```ruby
$ rails t
2 tests, 13 assertions, 0 failures, 0 errors, 0 skips
```

###10.3.2 サンプルユーザー

indexページに複数のユーザーを表示させるために、Rubyを使ってユーザーを一気に作成してみる。

その為にGemfileに`Faker gem`を追加する。

これは実際にいそうなユーザー名を作成するgem。faker gemは開発環境以外では普通使わないが、今回は例外的に本番環境でも適用させる予定なので、次のようにすべての環境で使えるようにする。

```Gemfile
gem 'bootstrap-sass', '3.3.7'
gem 'bcrypt',       '3.1.12'
gem 'faker',        '1.7.3'

$ bundle update
# bundle install
```

次に、サンプルユーザーを生成するRubyスクリプト(Railsタスク)を追加してみる。
Railsでは`db/seed.rb`というファイルを標準として使う。

作成したコードを次に示す。

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

このコードでは、Example Userという名前とメールアドレスを持つ1人のユーザと、
それらしい名前とメールアドレスを持つ99人のユーザーを作成する。

create!は基本的にcreateメソッドと同じものだが、ユーザーが無効な場合にfalseを返すのではなく、例外を発生させる点が異なる。

こうしておくと見過ごしやすいエラーを回避できるので、デバッグが容易になる。

ここえDBをリセットして、Railsタスクを実行(db:seed)してみる。


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

データベース上にデータを追加するのは遅くなりがちで、システムによっては数分掛かることもあり得る。

また、Railsサーバーを動かしている状態だと`rails db:migrate:reset`コマンドがうまく動かない時もあるので注意

ここで、ユーザー一覧を確認してみる。

![スクリーンショット 2019-01-15 4.11.23.png](https://qiita-image-store.s3.amazonaws.com/0/244639/5f73dd2c-4afe-ff63-c09e-bce0d5a69c83.png)


db:seedでRailsタスクを実行し終わると、サンプルアプリケーションのユーザーが100人になっている。

最初のいくつかのメールアドレスはデフォルトのGravatar画像以外の写真を関連付けている。

####演習

1:試しに他人の編集ページにアクセスしてみて、実装したようにリダイレクトされるか確認。

確認済み。

###10.3.3 ページネーション

大量のユーザーを１つのページに表示させているが、これを分割する為にページネーションを行う。

今回は、１ページに30人だけユーザーを表示してみる。

Railsには豊富なページネーションメソッドがあり、今回は`will paginate`メソッドを使って見る。
これを使うために、`Gemfile`に`will_paginate`gemと`bootstrap-will_paginate`gemを両方含め、Bootstrapのページネーションスタイルを使って`will_paginate`を構成する必要がある。

まずは各gemをGemfileに追加してみる。

```ruby:Gemfile
gem 'faker',        '1.7.3'
gem 'will_paginate','3.1.6'
gem 'bootstrap-will_paginate','1.0.0'

$ bundle install
```

実行後、新しいgemが正しく読み込まれるように、Webサーバーを再起動する。

ページネーションが動作するには、ユーザーのページネーションを行うようにRailsに指示するコードをindexビューに追加する必要がある。
また、indexアクションにあるUser.allを、ページネーションを理解できるオブジェクトに置き換える必要もある。

まずはビューに`will_paginate`メソッドを追加する。

```erb:index.html.erb
<% provide(:title, 'All users') %>
<h1>ユーザー一覧</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オブジェクトを自動的に見つけ出し、それから他のページにアクセスするためのページネーションリンクを作成している。

ただし、このままでは上記のビューは動かない。
理由は現在の@users変数には`User.all`が含まれているが、will_paginateではpaginateメソッドを使った結果が必要だから。

必要となるデータは

```ruby
$ rails c
>> User.paginate(page: 1)
  User Load (0.9ms)  SELECT  "users".* FROM "users" LIMIT ? OFFSET ?  [["LIMIT", 11], ["OFFSET", 0]]
   (0.1ms)  SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2019-01-14 19:08:30", updated_at: "2019-01-14 19:08:30", password_digest
```

paginateでは、キーが:pageで値がページ番号のハッシュを引数に取る。

`User.paginate`では、:pageパラメーターに基づいて、DBからひとかたまりのデータ(デフォルトでは30)を取り出す。

したがって、1ページ目は1から30のユーザー、2ページ目は31~60のユーザーと行った具合にデータが取り出される。

ちなみに、pageがnilの場合、paginateは単に最初のページを返す。

paginateを使うことで、サンプルアプリケーションのユーザーのページネーションを行えるようになる。
具体的には、indexアクション内のallをpaginateメソッドに置き換える。

ここで、`:page`パラメーターには`params[:page]`が使われるが、これは`will_paginate`によって自動的に生成される。

```rb:users_controller.rb
  def index
    @users = User.paginate(page: params[:page])                                 # Userを取り出して分割した値を@usersに代入
  end
```

以上で、ユーザー一覧ページを確認してみる。

![スクリーンショット 2019-01-15 4.37.15.png](https://qiita-image-store.s3.amazonaws.com/0/244639/d22a16bc-1a16-167f-72fd-c797644493a5.png)

![スクリーンショット 2019-01-15 4.37.20.png](https://qiita-image-store.s3.amazonaws.com/0/244639/f39d984d-8196-5afd-3321-8e52b73d8c5f.png)


次ページへの遷移も可能。

####演習

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

```ruby
>> User.paginate(page: nil)
  User Load (0.2ms)  SELECT  "users".* FROM "users" LIMIT ? OFFSET ?  [["LIMIT", 11], ["OFFSET", 0]]
   (0.1ms)  SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2019-01-14 19:08:30", updated_at: "2019-01-14 19:08:30", password_digest:
```

OK

2:paginationオブジェクトは何クラスか確認。また、`User.all`のクラスとどこが違うか？

```ruby
>> user = User.all
>> page = User.paginate(page: 1)
>> user.class
=> User::ActiveRecord_Relation
>> page.class
=> User::ActiveRecord_Relation
```

ActiveRecord_RelationクラスでUser.allと同じ。

###10.3.4 ユーザー一覧のテスト

これでユーザー一覧ページが動くようになったので、ページネーションに対する簡単なテストを書いておく。

今回のテストでは、

①ログイン
②indexページにアクセス
③最初のユーザーのページにユーザーがいることを確認
④ページネーションのリンクがあることを確認

という順にテストしていく。

最後の２つのステップでは、テスト用のDBに31人以上のユーザーがいる必要がある。

そこで、今回はfixtureに埋め込みRubyを使って31人以上のテストユーザーを作成する。

```yml: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ページに対するテストを書いていく。
まずは統合テストから作成

```ruby
$ rails g integration_test users_index
```

今回のテストでは、paginationクラスを持ったdivタグをチェックして、最初のページにユーザーがいることを確認する。

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

これでテストはパス

```ruby
$ rails t
36 tests, 121 assertions, 0 failures, 0 errors, 0 skips
```

####演習

1:ページネーション(will_paginate)をコメントアウトしてテストが失敗するかどうか確認。

確認済み

2:１つだけコメントアウトした場合にテストがパスすることを確認。
また、will_paginateのリンクが２つとも存在していることをテストしたい場合は、どのようなテストを追加すれば良いか？

```rb:users_index_test.rb
  test "index including pagination" do
    log_in_as(@user)
    get users_path
    assert_template 'users/index'
    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
```

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

ユーザー一覧ページにページネーションを実装できたが、ここではパーシャルを使ってリファクタリングしていく。

今回は一覧ページのリファクタリング(動作を変えずにコードを整理)を行うことにする。

サンプルアプリケーションのテストは既に完了しているので、Webサイトの機能を損なうことなく安心してリファクタリングに取りかかれる。

リファクタリングの第一歩は、ユーザーのliをrender呼び出しに置き換えること。

```erb:index.html.erb
<% provide(:title, 'All users') %>
<h1>ユーザー一覧</h1>

<%= will_paginate %>

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

<%= will_paginate %>
```

パーシャル用のファイルを作成し、そこにliタグを置く。

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

さらに、今度はrenderを@users変数に対して直接実行する。

```erb:index.html.erb
<% provide(:title, 'All users') %>
<h1>ユーザー一覧</h1>

<%= will_paginate %>

<ul class="users">
  <%= render @users %>
</ul>

<%= will_paginate %>
```

@usersをrenderで読み出すと、Railsは自動的に@usersをUserオブジェクトのリストであると推測。
さらに、ユーザーのコレクションを列挙し、それぞれのユーザーを_user.html.erbパーシャルで出力する。

つまり、each doメソッドを使わずに、@usersのユーザーを順に取り出して表示することが可能。


```ruby
$ rails t
15 tests, 79 assertions, 0 failures, 0 errors, 0 skips
```

テストがパスしたので、ユーザーを取り出せてることを確認した。

#####演習

1: renderの行をコメントアウトし、テストが失敗することを確認。

確認済み。

##10.4 ユーザーを削除する

ユーザー一覧ページが完了したので、残るはdestroyのみ。
これを実装することで、RESTに準拠した正統なアプリケーションとなる。

まずはユーザーを削除するためのリンクを追加する。
次に、削除を行うのに必要なdestroyアクションも実装する。

モックアップはこちら

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/55259d86-12c1-e1da-abec-a7b5c14e6444.png)

出典：[図 10.13: 削除リンクを追加したユーザー一覧のモックアップ](https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#cha-updating_showing_and_deleting_users)


###10.4.1 管理ユーザー

特権を持つ管理ユーザーを識別するために、論理値をとるadmin属性をUserモデルに追加する。
追加したら、自動的に`admin?`という論理値を返すメソッドも使えるようになる。
これを使って、管理ユーザーの状態をテストできる。


変更後のデータモデル

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/518df72f-ce3a-c762-495b-271ffafacea5.png)

出典：[図 10.14: 論理値をとるadmin属性が追加されたUserモデル](https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#cha-updating_showing_and_deleting_users)

いつものように、マイグレーションを実行してadmin属性を追加する。
属性の型は`boolean`とする。

```ruby
$ rails g migration add_admin_to_users admin:boolean
```

マイグレーション実行後、adminカラムがusersテーブルに追加される。
`default: false`という引数を`add_column`に追加している。

これは、デフォルトでは管理者になれないと明示している。
なお、デフォルトではadminの値はnilなのでfalseを指定せずともいいのだが、あえてfalseを渡すことでコードの意図をRailsと開発者に明確に示せる。

```rb:[timestamp]_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration[5.1]
  def change
    add_column :users, :admin, :boolean, default: false
  end
end
```

あとは、マイグレーションを実行して変更を反映

```ruby
$ rails db:migrate
```

Railsコンソールで動作を確認すると、期待どおりadmin属性が追加されて論理値をとり、さらに疑問符の付いたadmin?メソッドも利用できるようになっている。

```ruby
>> user = User.first
  User Load (0.7ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2019-01-14 19:08:30", updated_at: "2019-01-14 19:08:30", password_digest: "$2a$10$dljD1bDoK.bH4l8WOY0CIueP7hTv.84hOJW76oNEf2w...", remember_digest: nil, admin: false>
>> user.admin?
=> false
>> user.toggle!(:admin)
   (0.1ms)  SAVEPOINT active_record_1
  SQL (3.6ms)  UPDATE "users" SET "updated_at" = ?, "admin" = ? WHERE "users"."id" = ?  [["updated_at", "2019-01-15 00:50:25.017787"], ["admin", "t"], ["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
```

これでuserがデフォルトでは管理者ではないことがわかった。

また、`toggle!`メソッドでadmin属性の状態をfalseからtrueに反転している。

仕上げに、最初のユーザーだけをデフォルトで管理者にするようサンプルデータを更新する。

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

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

次に、DBをリセットして、サンプルデータを再度生成する。

```ruby
rails db:migrate:reset
rails db:seed
```

####Strong Parameters　再び

seeds.rbでは初期化ハッシュにadmin: trueを設定することでユーザーを管理者にしている。
ここでは、荒れ狂うWeb世界にオブジェクトを晒すことの危険性を改めて強調している。

もし、任意のWebリクエストの初期化ハッシュをオブジェクトに渡せるとなると、攻撃者は次のようなPATCHリクエストを送信してくるかもしれない。

```ruby
patch /users/17?admin=1
```

このリクエストは、17番目のユーザーを管理者に変えてしまう。
ユーザーのこの行為は少なくとも重大なセキュリティ違反となる可能性があるし、それだけでは済まされない。

このような危険があるからこそ、編集してもよい安全な属性だけを更新することが重要になる。

これを、7章で使ったStrong Parametersを使って対策をする。

次のように、`params`ハッシュに対して`require`と`permit`を呼び出す。

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

上のコードでは、許可された属性リストにadminが含まれていないことに注目。

これにより、任意のユーザーが自分自身にアプリケーションの管理者権限を与えることを防止できる。
この問題は重大であるため、編集可能になってはならない属性に対するテストを作成してみる。

####演習

1:Web経由でadmin属性を変更できないことを確認してみる。
具体的には、PATCHを直接ユーザーのURL(/users/:id)に送信するテストを作成してみる。
テストが正しい振る舞いをしているかどうか確信を得るために、まずはadminを`user_params`メソッド内の許可されたパラメータ一覧に追加するところから始めてみる。

```rb:users_controller_test.rb
    test "should not allow the admin attribute to be edited via the web" do
      log_in_as(@other_user)                                                    # @other_userでログインする
      assert_not @other_user.admin?                                             # @toher_userが管理権限あれば(adminがtrueなら)falseを返す
      patch user_path(@other_user), params: {                                   # /users/@other_user へparamsハッシュの中身を送る
                                    user: { password:               'password',
                                            password_confirmation:  'password',
                                            admin: true } }
      assert_not @other_user.reload.admin?                                      # @other_userを再読み込みし、admin論理値が変更されてないか検証(falseやnilならtrue)
    end
```

###10.4.2 destroyアクション

Usersリソースの最後の仕上げとして、destroyアクションへのリンクを追加する。
まず、ユーザーindexページの各ユーザーに削除用のリンクを追加し、続いて管理ユーザーへのアクセスを制限する。

これによって、現在のユーザーが管理者のときに限り`[delete]`リンクが表示されるようになる。

```erb:_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リクエストを発行するリンクの生成は、method: :deleteによって行われている点に注意。
また、各リンクをif文で囲い、管理者にだけ削除リンクが表示されるようにしている。
う
ブラウザはネイティブではDELETEリクエストを送信できないため、RailsではJavaScriptを使って偽造する。
つまり、**JavaScriptがオフになっているとユーザー削除のリンクも無効になる。**

JavaScriptをサポートしないブラウザをサポートする必要がある場合は、フォームとPOSTリクエストを使ってDELETEリクエストを偽造することもできる。（これはJavaScriptがなくても動作する）


この削除リンクが動作するためには、`destroy`アクションを追加する必要がある。
該当するユーザーを見つけてActive Recordのdestroyメソッドを使って削除し、最後にユーザーのindexに移動する。
ユーザーを削除するためにはログインしていなければならないので、destroyアクションも`logged_in_user`フィルターに追加する。


```rb:users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]       # logged_in_userメソッドを適用
  before_action :correct_user,   only: [:edit, :update]                         # editとupdateアクションにcorret_userメソッドを適用
  before_action :admin_user,     only: :destroy

  def destroy
    User.find(params[:id]).destroy
    flash[:success] = "削除完了"
    redirect_to users_url
  end
```

destroyアクションでは、findメソッドとdestroyメソッドを1行で書くために2つのメソッドを連結している。

結果として、管理者だけがユーザーを削除できるようになる。
(つまり、削除リンクが見えているユーザーのみ削除できる)

しかし、ここでコマンドラインでDELETEリクエストを直接発行するという方法でサイトの全ユーザーを削除してしまうことができる。
サイトを正しく防衛するためには、destroyアクションにもアクセス制御を行う必要がある。

これを実装してようやく、管理者だけがユーザーを削除できるようにする。

またbeforeアクションを使う。今回は`:admin_user`をdestoryアクションのみに適用する。

```rb:users_controller.rb
  before_action :admin_user,     only: :destroy

    def admin_user                                                              # 管理者のみに適用
      redirect_to(root_url) unless current_user.admin?                          # 現在のユーザーが管理者でなければroot_urlへリダイレクト
    end
```

####演習

1:管理者ユーザーとしてログインし、試しにサンプルユーザを2~3人削除してみる。
ユーザーを削除すると、Railsサーバーのログにはどのような情報が表示されるか？

![スクリーンショット 2019-01-15 13.09.59.png](https://qiita-image-store.s3.amazonaws.com/0/244639/f487b9ec-ddb1-4b75-aaba-b15d201dd24e.png)

```ruby
   (0.1ms)  begin transaction
  SQL (2.6ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 3]]
   (8.5ms)  commit transaction
```

transaction処理でid=3のユーザーを削除していることが分かる。

###10.4.3 ユーザー削除のテスト

ユーザーを削除するといった重要な操作については、期待された通りに動作するか確かめるテストを書くべき。

まずは、fixtureファイルのmichaelに管理者権限を与える。

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

次に、Usersコントローラをテストするために、アクション単位でアクセス制御をテストする。
ログアウトのテストと同様に、削除をテストするために、DELETEリクエストを発行してdestroyアクションを直接動作させる。

この時、２つのケースをチェック

①ログインしていないユーザーであれば、ログイン画面にリダイレクトさせる
②ログイン済みであっても管理者でなければ、ホーム画面にリダイレクトさせる。

```erb:users_controller_test.rb
    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
```

`assert_no_difference`メソッドを使って、ユーザー数が変化しないことを確認している点に注目。

管理者ではないユーザーの振る舞いについて検証するが、管理者ユーザーの振る舞いと一緒に確認できるといい。
そこで、管理者であればユーザー一覧画面に削除リンクが表示される仕様を利用して、今回のテストを追加していくことにする。

これにより、後ほど追加する管理者の振る舞いについても簡単にテストが書けそう。

今回のテストで唯一の手の込んだ箇所は、管理者が削除リンクをクリックしたときに、
ユーザーが削除されたことを確認する部分。

その部分のテストがこれ。

```ruby
 assert_difference 'User.count', -1 do
   delete user_path(@other_user)
 end
```

7章では`assert_difference`メソッドを使ってユーザーが作成されたことを確認していたが、
今回は同じメソッドを使ってユーザーが削除されたことを確認。

具体的には、DELETEリクエストを適切なURLに向けて発行し、`User.count`を使ってユーザー数が1減ったかどうかを確認している。

したがって、管理者や一般ユーザーのテスト、そしてページネーションや削除リンクのテストを全てまとめると、以下のようになる。

```rb:users_index_test.rb
require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest

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

各ユーザーの削除リンクをテストする時に、ユーザーが管理者であればスキップしている点にも注目。
これは、`_user.html.erb`で、ログイン時、現在のユーザーがログインしていて、なおかつ特定のユーザーがログイン成功したユーザーでない場合のみ削除リンクを表示しているから。

結果として、管理者権限のないユーザーに削除リンクが表示されてるため、このテストが通る。

```ruby
$ rails t
```

####演習

1:`users_controller`の管理者ユーザーのbeforeフィルターをコメントアウトしてテストが失敗するか確認

確認済み。


##10.5 最後に

ログイン/ログアウト、ユーザー一覧画面まで作り、管理者権限を持ったユーザーはユーザーを削除することができる仕組みも実装した。

この時点で、サンプルアプリケーションはWebサイトとしての十分な基盤が整ったと言える。
今後はアカウント有効化機能、パスワード再設定、投稿機能などを実装する。

いつも通りブランチ切ってマージ、Herokuにデプロイ

```ruby
$ git add -A
$ git commit -m "Finish user edit, update, index, and destroy actions"
$ git checkout master
$ git merge updating-users
$ git push
```

```ruby
$ rails test
$ git push heroku
$ heroku pg:reset DATABASE
$ heroku run rails db:migrate
$ heroku run rails db:seed
$ heroku restart
```

seedを走らせることで、ユーザー一覧をローカル環境の標準順序と合わせている。

[第11章へ](https://qiita.com/yuuki_netb/items/3a544d9183f4008f6aa2)

#単語集

- target="_blank"

新しい窓(ウィンドウ)、又はタブでリンク先を開く

- type="hidden"

hidden属性の中で指定し、name属性で名前をつけ、value属性で送信される値を指定する。
そうすることで、**非表示データを送信することができる。**

- allow_nil: true

validatesのオプションで、validatesに追加すると、trueならばnilの検証はスキップする。
なお、初期値はfalseである。

使い方

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

上記のように明示的にtrueを指定することで、passwordのバリデーションに引っ掛かったならば、nil(空かどうか)
の検証を行い、trueを返す(バリデーションを通す)

- セキュリティモデル

セキュリティ上の制御機構のこと。
エンドユーザと管理者との間の権限を管理する。

- before_action

何らかの処理を実行させる前に、特定のメソッドを実行させる。

- フレンドリーフォワーディング

ログインしていないユーザーが何らかのページへアクセスした際に、ログイン画面に遷移させて、ユーザーがログイン後にそのページへリダイレクトさせてあげる施策のこと。

- redirect_back_or(default)

引数に渡したデフォルト値を使い、一個前(直前)のページにリダイレクトするメソッド

- default

デフォルト値を設定

- forwarding_url

一個前のURLを記憶するsessionのキー。

使い方

```ruby
session[:forwarding_url]
```

- request.original_url

HTTPリクエストでクライアントがサーバーに渡した値を、_urlでURLを取得

- get?

getしたかどうか聞いている。

- permit

引数に取った属性のみ受け取りを許可し、オブジェクトに渡すメソッド。