Rails Tutorial 第１０章　簡易まとめ

#１０章　目標
・これまで未実装だったedit、update、index、destroyアクションを加え、RESTアクションを完成させます
・まず、ユーザーが自分のプロフィールを自分で更新できるようにします
・次に、すべてのユーザーを一覧できるようにします
・最後に、ユーザーを削除し、データベースから完全に消去する機能を追加します

#10.1　ユーザーを更新する

先にユーザーを編集するためのeditアクションを作成。newアクション同様に、POSTリクエストに応答するcreateの代わりに、PATCHリクエストに応答するupdateアクションを作成します
最大の違いは、ユーザー登録は誰でも実行できますが、ユーザー情報を更新できるのはそのユーザー自身に限られるということです。第8章で実装した認証機構を使えば、beforeフィルター (before filter) を使ってこのアクセス制御を実現できます。

##10・1・1　編集フォーム

Usersコントローラにeditアクション追加→対応するeditビューの作成
ここで注意して頂きたいのは、ユーザー編集ページの正しいURLが/users/1/editとなっていることです (ユーザーのidが1の場合)。
**ユーザーのidはparams[:id]変数で取り出すことができる**

　def edit
    @user = User.find(params[:id])
  end



```rb: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_for(@user) 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="http://gravatar.com/emails" target="_blank">change</a>　＜－－注目３
    </div>
  </div>
</div>
```

注目１・・・エラーメッセージパーシャルを挿入している

注目２・・・ビューのフィールドにはRailsが自動で@userから引き出したname、emailがそれぞれ値としてわたされている

注目３・・・target="_blank"　でリンクを開く際新しいタブで開かれる
![image.png](https://qiita-image-store.s3.amazonaws.com/0/202349/57be166e-e2d0-e966-641a-777cf3b3b1a8.png)

仕上げに、ナビゲーションバーにあるユーザー設定へのリンクを更新します。7章で示したedit_user_pathという名前付きルートと、 9章で定義したcurrent_userというヘルパーメソッドを使うと、実装が簡単です。

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

###Active Recordのnew_record?論理値メソッド

```rb: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_for(@user) do |f| %>
・
・
・
```

このform_for...の部分はNewビューのコードと全く同じとなっていて、Railsはどうやって新規ユーザー用のPOSTリクエストとユーザー編集用のPATCHリクエストを区別するのでしょうか

その答えは、Railsは、ユーザーが新規なのか、それともデータベースに存在する既存のユーザーであるかを、Active Recordのnew_record?論理値メソッドを使って区別できるからです。

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

Railsは、form_for(@user)を使ってフォームを構成すると、@user.new_record?がtrueのときにはPOSTを、falseのときにはPATCHを使います。

##10.1.2　編集の失敗
ユーザー登録に失敗したときと同様編集失敗も扱う

まずはupdateアクションの作成から進めますがupdate_attributes を使って送信されたparamsハッシュに基いてユーザーを更新します。
無効な情報が送信された場合、更新の結果としてfalseが返され、elseに分岐して編集ページをレンダリングします。

```rb:  

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

```

update_attributesへの呼び出しでuser_paramsを使っていることに注目してください。7章でも説明したように、ここではStrong Parametersを使ってマスアサインメントの脆弱性を防止しています。

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

##10.1.3　編集失敗テスト

統合テスト作成
$ rails generate integration_test users_edit
      invoke  test_unit
      create    test/integration/users_edit_test.rb

かんたんに

```rb:test/integration/users_edit_test.rb
 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:  "",　＜－－注目1
                                              email: "foo@invalid",
                                              password:              "foo",
                                              password_confirmation: "bar" } }

    assert_template 'users/edit'
　　assert_select 'div.alert' , "The form contains 4 errors"＜－－注目２
  end
end
```
注目１・・・PATCHリクエストを送るためにpatchメソッドを使っていることに注目してください。これはgetやpost、deleteメソッドと同じように、HTTPリクエストを送信するためのメソッドです。

注目２・・・演習問題から。

##10.1.4　TDDで編集を成功させる　:　「受け入れテスト (Acceptance Tests)」

アプリケーション用のコードを実装する前にテストをあらかじめかいた方が便利な時がある。
そう言ったテストを「受け入れテスト (Acceptance Tests)」と呼ぶ。
ある機能の実装が完了し、受け入れ可能な状態になったかどうかを決めるテストとして知られています。

今回は有効な情報を更新する正しいふるまいのテストを書いていく

```rb:test/integration/users_edit_test.rb
 require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  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
end
```
注目１・・・空になっているのは更新の度パスワードを入力は面倒のため。後ほどひっかかるバリデーションをエスケープするように書き直す

注目２・・・@user.reloadを使って、データベースから最新のユーザー情報を読み込み直して、正しく更新されたかどうかを確認している

更新成功のためのupdateアクション

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

上記のとおりバリデーションを再編集：オプションallow_nill

```rb:app/models/user.rb
 class User < ApplicationRecord
  attr_accessor :remember_token
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
  .
  .
  .
end
```
ここで懸念されるのは新規ユーザー登録の際にも空パスが通ってしまうのかというところだがそんなことはなく、has_secure_passwordでは (追加したバリデーションとは別に) オブジェクト生成時に存在性を検証するようになっているため、空のパスワード (nil) が新規ユーザー登録時に有効になることはありません。

これで更新成功テストもgreen


#10.2　認可

**ウェブアプリケーションの文脈では、認証 (authentication) はサイトのユーザーを識別することであり、認可 (authorization) はそのユーザーが実行可能な操作を管理することです。**

現時点での大きなセキュリティーホールとして、だれでもどのページにアクセス可能であると言う事
でも (ログインしていないユーザーでも) ユーザー情報を編集できてしまうのです。この節では、ユーザーにログインを要求し、かつ自分以外のユーザー情報を変更できないように制御してみます

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

１、ログインしていないユーザーが保護されたページにアクセスしようとした際のケースについて対処していきます。こういったケースはアプリケーションを使っていると普通に起こることなので、ログインページに転送して、そのときに分かりやすいメッセージも表示するようにしましょう

２、一方で、許可されていないページに対してアクセスするログイン済みのユーザーがいたら (例えば他人のユーザー編集ページにアクセスしようとしたら)、ルートURLにリダイレクトさせるようにします 

上を実現させるために使うのはbefore_actionメソッド。
これをUsersコントローラに使って何らかの処理が実行される直前に特定のメソッドを実行する仕組みです

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

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

    # beforeアクション

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

注目・・・privateの中で定義している

before_action：logged_in_userといった形式でメソッドを引っ張ってきて、オプションでonlyを持たせ、どのアクションで行うのかを指定している。

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

なのでテストが行われる前にログインさせるコードをかく

```rb:test/integration/users_edit_test.rb
 require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  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
end
```
このじてんでテストはgreenになるがbeforeフィルターに対するテストがないのでもし攻撃者がフィルターをコメントアウトしてしまうと大きなセキュリティーホールになりかねない！
ので、基本的アクション毎にフィルターを適応させているのでテストもアクション毎に書かなければいけない

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

class UsersControllerTest < ActionDispatch::IntegrationTest

  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
end
```
注目・・・HTTPリクエストはそれぞれ。patchメソッドを使ってuser_path(@user)にPATCHリクエストを送信している点に注目してください7章にあるように、このリクエストはUsersコントローラのupdateアクションへと適切に繋いでくれます

これで完了

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

当然のことですが、ログインを要求するだけでは十分ではありません。ユーザーが自分の情報だけを編集できるようにする必要があります。そこで本項では、セキュリティモデルが正しく実装されている確信を持つために、テスト駆動開発で進めていきます。（Usersコントローラテスト）

別のユーザーをfixtureに作成

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

これを利用してユーザー２がユーザー１を編集しようとした時といったテストをさきにかく


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

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end
  .
  .
  .
  test "should redirect 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
end
```
注目・・・ログイン済みユーザーなのでログインページではなくルートURL


ここでもbeforeフィルター
別のユーザーのプロフィールを編集しようとしたらルートへリダイレクトさせたいので、correct_userというメソッドを作成し、beforeフィルターからこのメソッドを呼び出すようにします 
その際editとupdateの各アクションから、@userへの代入文を削除している点にも注意してください。

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

  def update
    
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
  end
  .
  .
  .
  private

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

    # beforeアクション

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

    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless @user == current_user #ユーザーが正しくなければルート
    end
end
```

少しRuby調にリファクタリングすると

```rb:app/helpers/sessions_helper.rb
 module SessionsHelper

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

```rb:app/controllers/users_controller.rb

  # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end
end
```


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

簡潔にいうと**ログインしていないユーザーが編集ページにアクセスしようとしていたなら、ログインを要求され、ユーザーがログインした後にはその直前までアクセスしようとしていた編集ページにリダイレクトされるようにする**　だ


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

```rb:test/integration/users_edit_test.rb
 require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

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

ユーザーを希望のページに転送するには、
１、リクエスト時点のページをどこかに保存しておき（store_location）
２、その場所にリダイレクトさせる必要があります（redirect_back_or）

として、sessionヘルパーへ

```rb:app/helpers/sessions_helper.rb
 module SessionsHelper
  .
  .
  .


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

  # 記憶したURL (もしくはデフォルト値) にリダイレクト
  def redirect_back_or(default)
    redirect_to(session[:forwarding_url] || default)＜－－注目２
    session.delete(:forwarding_url)＜－－注目３
  end

```

注目１・・・request.original_urlでリクエスト先が取得できます。さらにsessionメソッドにforwarding_urlキーを与えて値として渡している。**ただし、GETリクエストが送られたときだけ格納するようにしておきます**

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

注目３・・・転送用のURLを削除している点にも注意してください。これをやっておかないと、次回ログインしたときに保護されたページに転送されてしまい、ブラウザを閉じるまでこれが繰り返されてしまいます 


beforeフィルターに組み込みたい（log_in_user）

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

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

    # beforeアクション

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

    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end
end
```

フォワーディング自体を実装するには、createアクションに組み込んでredirect_back_orメソッドを使います。リクエストされたURLが存在する場合はそこにリダイレクトし、ない場合は何らかのデフォルトのURLにリダイレクトします。デフォルトのURLは、Sessionコントローラのcreateアクションに追加し、サインイン成功後にリダイレクトします

```rb:app/controllers/sessions_controller.rb
 class SessionsController < ApplicationController
  .
  .
  .
  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
  .
  .
  .
end
```


レンドリーフォワーディング用統合テストはパスするはずです。成功すれば、基本ユーザー認証機能とページ保護機能の実装は完了


#10.3　すべてのユーザーを表示する
indexアクションを整備していきます

![image.png](https://qiita-image-store.s3.amazonaws.com/0/202349/6c33117e-7b71-de8e-8e3d-279bd951c41a.png)

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

セキュリティモデルとしてshowビューは全てユーザーが閲覧可能であるのに対し、indexビューはグインしたユーザーにしか見せないようにし、未登録のユーザーがデフォルトで表示できるページを制限します

不正アクセスを受けた場合indexアクションが正しくリダイレクトするか検証するテストを書いてみます 

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

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end

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

次にbeforeフィルターにindexアクションを追加
indexアクションにはインスタンス変数を用意して全てのユーザーを格納
これが完了した時点でgreenだがindexの整備を完了させてしまう

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

  def show
    @user = User.find(params[:id])
  end
  .
  .
  .
end
```

実際のindexページを作成するには、ユーザーを列挙してユーザーごとにliタグで囲むビューを作成する必要があります。ここではeachメソッドを使って作成します。それぞれの行をリストタグulで囲いながら、各ユーザーのGravatarと名前を表示します 。


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

CSSも

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

```
そしてヘッダーパーシャル
`   <li><%= link_to "Users", users_path %></li>`

これでgreen

##10.3.2　サンプルユーザー

この項ではユーザーをある程度大きな数生成する方法を学ぶ
全部手作業でやるわけにはいかないしね

```
source 'https://rubygems.org'
・
・
gem 'faker',          '1.7.3'
```
これは、実際にいそうなユーザー名を作成するgemです。ちなみにfaker gemは開発環境以外では普通使いませんが、今回は例外的に本番環境でも適用させる予定なので、次のようにすべての環境で使えるようにしています
bundle installして

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

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

Example Userという名前とメールアドレスを持つ1人のユーザと、それらしい名前とメールアドレス（ユーザー数＋１で被る事が無い）を持つ99人のユーザーを作成します

```
$ rails db:migrate:reset
$ rails db:seed
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/202349/b8f3cb19-c899-58dd-7f37-66a18340db0d.png)



##10.3.3　ページネーション

100人のユーザーが作成できたのでここでページネーション (pagination) というもので見易くしていく
この場合は、例えば1つのページに一度に30人だけユーザーを表示するというものです。

Railsには豊富なページネーションメソッドがあります。
が、今回はその中で最もシンプルかつ堅牢なwill_paginateメソッドを使ってみましょう。これを使うためには、Gemfileにwill_paginate gem とbootstrap-will_paginate gemを両方含め、Bootstrapのページネーションスタイルを使ってwill_paginateを構成する必要があります

`gem 'will_paginate',           '3.1.5'
gem 'bootstrap-will_paginate', '1.0.0'`

`bundle　install`

サーバーを再起動

ページネーションが動作するには、ユーザーのページネーションを行うようにRailsに指示するコードをindexビューに追加する必要があります。

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

また、indexアクションにあるUser.allを、ページネーションを理解できるオブジェクトに置き換える必要もあります。
現在の@users変数にはUser.allが含まれていますが、will_paginateではpaginateメソッドを使った結果が必要だからです。必要となるデータの例は次のとおり

```
$ rails console
>> User.paginate(page: 1)
  User Load (1.5ms)  SELECT "users".* FROM "users" LIMIT 30 OFFSET 0
   (1.7ms)  SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1,...
```
paginateでは、キーが:pageで値がページ番号のハッシュを引数に取ります。User.paginateは、:pageパラメーターに基いて、データベースからひとかたまりのデータ (デフォルトでは30) を取り出します(nilの場合は最初のページ)
ということで

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/202349/c3988a22-9297-f239-2d93-70affdc4423e.png)


ビューで
<%= will_paginate %>
を上下2カ所配置した為ページネーションのリンクも上下に。


##10.3.4　ページネーションのテスト

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

最後の2つのステップでは、テスト用のデータベースに31人以上のユーザーがいる必要があります。

そこでfixtureに。
ここではERBを利用してユーザーを30
と他にこの後必要になる為２名分の名前付きユーザーも追加

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

`$ rails generate integration_test users_index
      invoke  test_unit
      create    test/integration/users_index_test.rb`


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

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

green

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

実はRailsにはコンパクトなビューを作成するための素晴らしいツールがいくつもあります。この節ではそれらのツールを使って一覧ページのリファクタリング (動作を変えずにコードを整理すること) を行うことにします

```rb: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 %>
</ul>

<%= will_paginate %>
```

注目・・・パーシャル (ファイル名の文字列) に対してではなく、Userクラスのuser変数に対して実行している点に注目してください。この場合、Railsは自動的に_user.html.erbという名前のパーシャルを探しにいく
だったらそのファイルを作らなければ

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

今度はrenderを@users変数に対して直接実行します

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

<%= will_paginate %>

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

<%= will_paginate %>
```
Railsは@users をUserオブジェクトのリストであると推測します。さらに、ユーザーのコレクションを与えて呼び出すと、Railsは自動的にユーザーのコレクションを列挙し、それぞれのユーザーを_user.html.erbパーシャルで出力します。

これに限らず、リファクタリングを行う場合には、アプリケーションのコードを変更する前と後で必ずテストを実行し、いずれも green になることを確認するようにしてください。

大分コンパクトになりました。

#10.4　ユーザーを削除する

残ったdestroyを整備していきます。

１、ユーザーを削除するためのリンクを追加、
２、削除を実行できる権限を持つ管理 (admin) ユーザーのクラスを作成、
３、削除を行うのに必要なdestroyアクションも実装

##10.4.1　管理ユーザー

特権を持つ管理ユーザーを識別するために、論理値をとるadmin属性をUserモデルに追加します。この後で説明しますが、こうすると自動的にadmin?メソッド (論理値を返す) も使えるようになります
属性の型はbooleanです

`$ rails generate migration add_admin_to_users admin:boolean`

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

default: falseという引数はデフォルトでは管理者になれないということを示すためです 

`$ rails db:migrate`

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

```
$ rails console --sandbox
>> user = User.first
>> user.admin?
=> false
>> user.toggle!(:admin)
=> true
>> user.admin?
=> true
```
（ここではtoggle!メソッドを使って admin属性の状態をfalseからtrueに反転しています。）


```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、再び**

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

`patch /users/17?admin=1`

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

この様な危険があるからこそadmin属性をstrong parameterの許可する属性には置いていけないのです

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

下は演習問題のnon管理ユーザーがpatchでadmin：trueを送ってもfalseのままか？
のテスト

```rb：test/controllers/users_controller_test.rb

test "should not allow the admin attribute to be edited via the web" do
    
log_in_as(@other_user)
    assert_not @other_user.admin?
    patch user_path(@other_user), params: {
                                    user: {password:               @other_user.password,
                                           password_confirmation:  @other_user.password,
                                           admin: true}}
    @other_user.reload.admin?  
  end
```

##10.4.2　destroyアクション
まずdestroyアクションまでのリンクを作成しますが管理ユーザーにのみビューに表示されるようなコード

```rb: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>
```
注目・・・各リンクをif文で囲い、管理者にだけ削除リンクが表示されるようにしています

アクションをつくって、もちろんログイン済みでないとなので

```rb:app/controllers/users_controller.rb
 class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  before_action :correct_user,   only: [:edit, :update]
  .
  .
  .
  def destroy
    User.find(params[:id]).destroy＜－－注目
    flash[:success] = "User deleted"
    redirect_to users_url
  end

  private
  .
  .
  .
end
```
注目・・・チェーンメソッドで1行に

結果として、管理者だけがユーザーを削除できるようになります (より具体的には、削除リンクが見えているユーザーのみ削除できる)
しかし、直接コマンドラインからdeleteリクエストを送られる可能性がありますのでdestroyアクションにもアクセス制御を行う必要があります


```rb:app/controllers/users_controller.rb
 class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy
  .
  .
  .
  private
    .
    .
    .
    # 管理者かどうか確認
    def admin_user
      redirect_to(root_url) unless current_user.admin?
    end
end
```


###10.4.3　削除のテスト

テストで使う1人を管理ユーザーに

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

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

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

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

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

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end
  .
  .
  .
  test "should redirect 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
end
```

assert_no_differenceメソッド を使ってそのボックスが実行後、ユーザー数が変化しないことを確認している点に注目してください。

続いて管理者ユーザーのテストです。そこで、管理者であればユーザー一覧画面に削除リンクが表示される仕様を利用してページネーションのテストと複合させます。

```rb:test/integration/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　#管理者であればdeleteリンクが
        assert_select 'a[href=?]', user_path(user), text: 'delete'
      end
    end
    assert_difference 'User.count', -1 do　#deleteリクエスト後ユーザー数が-１
      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　#non管理者のindexにdeleteリンクは0
  end
end
```

greenです


#10.5 最後に

5.4でUsersコントローラをご紹介して以来、長い道のりをたどってきました。あの頃はユーザー登録すらありませんでしたが、今は登録もログインもログアウトもできます。プロフィールの表示も、設定の編集も、すべてのユーザーの一覧画面もあります。さらに、一部のユーザーは他のユーザーを削除することすらできるようになりました。

この時点で、サンプルアプリケーションはWebサイトとしての十分な基盤 (ユーザーを認証したり認可したり) が整ったといえるでしょう。続く第11章と第12章では、さらに2つの改善を加えます。１つはメールアドレスを使ってアカウントを有効化する機能 (すなわち本当に有効なメールアドレスか検証する機能) で、もう１つはユーザーがパスワードを忘れてしまったときのためのパスワードを再設定する機能です。

##10.5.1 本章のまとめ

・ユーザーは、編集フォームからPATCHリクエストをupdateアクションに対して送信し、情報を更新する
・Strong Parametersを使うことで、安全にWeb上から更新させることができる
・beforeフィルターを使うと、特定のアクションが実行される直前にメソッドを呼び出すことができる
・beforeフィルターを使って、認可 (アクセス制御) を実現した
・認可に対するテストでは、特定のHTTPリクエストを直接送信する低級なテストと、ブラウザの操作をシミュレーションする高級なテスト (統合テスト) の2つを利用した
・フレンドリーフォワーディングとは、ログイン成功時に元々行きたかったページに転送させる機能である
・ユーザー一覧ページでは、すべてのユーザーをページ毎に分割して表示する
・rails db:seedコマンドは、db/seeds.rbにあるサンプルデータをデータベースに流し込む
・render @usersを実行すると、自動的に_user.html.erbパーシャルを参照し、各ユーザーをコレクションとして表示する
・boolean型のadmin属性をUserモデルに追加すると、admin?という論理オブジェクトを返すメソッドが自動的に追加される
・管理者が削除リンクをクリックすると、DELETEリクエストがdestroyアクションに向けて送信され、該当するユーザーが削除される
・fixtureファイル内で埋め込みRubyを使うと、多量のテストユーザーを作成することができる