railsチュートリアルまとめ10 ユーザーの更新・表示・削除

# 個人的リマインド用
参考
[Ruby on Rails チュートリアル プロダクト開発の０→１を学ぼう](https://railstutorial.jp/)

## ユーザーの更新・表示・削除
### ユーザーを更新する
編集の時はeditアクション。同様に、PATCHリクエストに応答するupdateアクションを作成する。気をつけるのはユーザー情報を更新していいのは本人だけという点。これはbeforeフィルターを使ってなんとかする。
### 編集フォーム
編集用のページを作成するが、例えばユーザー1のページを作成したいときのURLは/users/1/edit。このユーザーIDはparams[:id]変数で取り出せる。
```ruby
app/controllers/users_controller.rb

def edit
    @user = User.find(params[:id])
end
```
ビューファイルは手動で作成する必用がある。
注目点としては、error_messagesパーシャルを再利用しているところと、Gravatarへのリンクでtarget="_blank"が使われている点。これは開く時に別のタブで開いてくれるようにする。
また@userインスタンス変数を使うと、NameやEmailのフィールド値が入った状態でレンダリングされる。
```erb
app/views/users/edit.html.erb

<div class="gravatar_edit">
  <%= gravatar_for @user %>
  <a href="https://gravatar.com/emails" target="_blank">change</a>
</div>
```
これから生成されたHTMLファイルはだいたい予想通り。しかし注目点としては以下の通り。
```erb
<input name="_method" type="hidden" value="patch" />
```
入力フィールドの中に隠し属性があるということ。WebブラウザはRESTの慣習として要求されているPATCHリクエストをそのままでは送信できないので、RailsはPOSTリクエストと隠しinputフィールドを利用して、PATCHリクエストを「偽装」している。

またここで気になる点がある。上のerbのform_with(@user)のコードは、第7章のコードと完全に同じ。では、どのようにそれらを判別しているのか。その答えは、Railsはユーザーが新規なのか、それともデータベースに存在する既存のユーザーなのかを、Active Recordのnew_record?論理値メソッドで区別できるから。
```
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false
```
trueの時はPOSTを使い、falseの場合はPATCHを使う。

仕上げに_header.html.erb内にある、Settingsへのリンクを更新する。
```erb
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
```
### 編集の失敗
まずはupdateアクションの作成から進めるが、これはupdateを使って送信されたparamsハッシュに基づいてユーザーを更新する。
```ruby
app/controllers/users_controller.rb

def update
    @user = User.find(params[:id])
    if @user.update(user_params) # ←user_paramsを使っている
      # 更新に成功した場合を扱う
    else
      render 'edit', status: :unprocessable_entity
    end
end
```
user_paramsを使っていることに注目。ここではStrong Parametersを使いマスアサインメントの脆弱性を防止している。
既にUserモデルのバリデーションとエラーメッセージのパーシャルがあるので、無効な情報を送信するとわかりやすいエラーメッセージが表示される。
### 編集失敗時のテスト
まずは統合テストの作成から。
```
rails g integration_test users_edit
```
簡単なテストを追加する。まず編集ページにアクセスし、editビューがレンダリングされているかをチェックする。その後、無効な情報を送信し、editビューが再描画されているかをチェック。
```ruby
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:  "",
                                              email: "foo@invalid",
                                              password:              "foo",
                                              password_confirmation: "bar" } }

    assert_template 'users/edit'
  end
end
```
結果はgreen。
### TDDで編集を成功させる
今度は編集フォームが動作するようにする。まずは実装前に統合テストを書くことから。ちなみにこれは「**受け入れテスト**」と呼ばれている。

手順としては、ユーザー情報を更新する正しい振る舞いをテストで定義する。次に、フラッシュメッセージが空ではないかどうか、プロフィールページにリダイレクトされるかどうかをチェック。また、データベース内のユーザー情報が正しく変更されたかどうかも検証する。
```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メソッドで最新のユーザー情報を反映させ、更新内容が正しいことを確認している。
上のテストを合格させるためにupdateの更新成功の欄を書き換える
```ruby
def update
  @user = User.find(params[:id])
  if @user.update(user_params)
    flash[:success] = "Profile updated"
    redirect_to @user
  else
    render 'edit', status: :unprocessable_entity
  end
end
```
しかしまだredのまま。これはパスワードの長さに関するバイデーションを設定しているので、現在の空である状態が引っかかっている。これはvalidatesにallow_nil: trueを追加することで解決できる。
```ruby
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: true
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
  . # ↑この部分
  .
  .
end
```
ちなみに空を許したとしても、新規作成の時には存在性を検証するようになっているので、パスワードが空で新規作成されることはない。

これでテストはgreenになる。
### 認可
Webアプリケーションでは、**認証**はサイトのユーザーを識別することであり、**認可**はそのユーザーが実行可能な操作を管理すること。

現在の大きな問題点は、誰でもユーザー情報を編集できること。そこでユーザーにログインを要求し、かつ自分以外のユーザー情報を変更できないように制御する。このようなセキュリティ上の制御機構をセキュリティモデルという。

**今回やること**
・ログインしていないユーザーが保護されたページにアクセスする時、ログインページに転送し、分かりやすいメッセージを表示する
・ログイン済みのユーザーが、許可されていないページにアクセスしようとしたら、ルートURLにリダイレクトすることにする
### ユーザーにログインを要求する
上のような転送の仕組みを使いたい時は**beforeフィルター**を使う。これは、なんらかの処理が実行される直前に特定のメソッドを実行する仕組み。
```ruby
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, status: :see_other
      end
    end
end
```
デフォルトでbeforeフィルターは全てのアクションに適用されるので、only:という風に制限する。また、今後どこかの時点で、:destroyアクションを保護するために使われる可能性があるので、logged_in_userの中で、ステータスコード:see_otherを含める。

現段階でテストがredになるが、なぜかというとedit,updateアクションでログインが必須になり、ログインしていないユーザーでこれらのテストが失敗するようになったため。では、テストする前にログインさせるためには、log_in_asヘルパーを使う。
```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フィルターのところをコメントアウトしてみると、テストがgreenになる。ここはredにならないといけない。

beforeフィルターはアクションごとに適用されているので、Usersコントローラのテストもアクションごとに書く。具体的には、正しい種類のHTTPリクエストを使ってeditアクションとupdateアクションをそれぞれ実行し、flashメッセージが代入されたかどうか、ログイン画面にリダイレクトされたかどうかを確認する。
```ruby
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
```
これでテストはredになる。そしてコメントアウト部分を戻したらgreenになる。
### 正しいユーザーを要求する
ログインを要求するだけではなく、ユーザーが自分の情報だけを編集できる必用がある。
fixtureで別アカウトを用意する。
```
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') %>
```
そして間違ったユーザーファ編集しようとしたときのテストを書く
```ruby
test/controllers/users_controller_test.rb

  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
```
では正しいユーザーかを判別するcorrect_userというメソッドを作成し、beforeフィルターからこのメソッドを呼び出す。
```ruby
app/controllers/users_controller.rb
  before_action :logged_in_user, only: [:edit, :update]
  before_action :correct_user,   only: [:edit, :update]

  private

    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url, status: :see_other) unless @user == current_user
    end
```
ちなみにこの時点で、correct_userで@user変数を定義したので、edit,updateアクションから@userへの代入文を削除している。

@user == current_userをcurrent_user(@user)にするリファクタリングは本文を参照。
### フレンドリーフォワーディング
残っている問題点としては、保護されたページにユーザーがアクセスしようとしたら、問答無用で自分のプロフィールページに移動させられる。リダイレクト先は直前にユーザーが開こうとしていたページにするのが親切。

まずはテスト。これはログインした後に、デフォルトのプロフィールページではなく編集ページにリダイレクトされるかのテスト。
```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)
```
失敗するテストが書けたので、次は実装。ユーザーを適切なページに転送するには、リクエストされたページをどこかに保存しておく。それはSessionヘルパーのstore_location、メソッドにカプセル化しておく。
```ruby
app/helpers/sessions_helper.rb

module SessionsHelper
  .
  .
  .
  # アクセスしようとしたURLを保存する
  def store_location
    session[:forwarding_url] = request.original_url if request.get?
  end
end
```
request.original_urlでリクエスト先を取得できる。GETリクエストの時だけにすることで、例えばログインしていないユーザーがフォームを送信した場合は、転送先のURLを保存しないようにできる。
```ruby
app/controllers/users_controller.rb

# ログイン済みユーザーかどうか確認
def logged_in_user
  unless logged_in?
    store_location
    flash[:danger] = "Please log in."
    redirect_to login_url, status: :see_other
  end
end
```
フォワーディング自体を実装する時は、リクエストされたURLが存在する場合はそこにリダイレクトし、存在しない場合は何らかのデフォルトURLにリダイレクトするようにする。ログイン成功後にリダイレクトしたいため、この実装はSessionコントローラのcreateアクションに追加する。またreset_sessionの呼び出しがあるので、以下のようにセットする必用がある。
```
forwarding_url = session[:forwarding_url]
reset_session
log_in user
```
転送先URLが存在する場合はそこにリダイレクトし、転送先URLがnilの場合はユーザーのプロフィールにリダイレクトできるようにする
```
redirect_to forwarding_url || url
```
フレンドリーフォワーディングを備えた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])
      forwarding_url = session[:forwarding_url]
      reset_session
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      log_in user
      redirect_to forwarding_url || user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new', status: :unprocessable_entity
    end
  end
```
### すべてのユーザーを表示する
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]
  before_action :correct_user,   only: [:edit, :update]

  def index
  end
end
```
indexアクションにコードを追加。ユーザーを全表示させたい時はUser.allを使う。
```ruby
app/controllers/users_controller.rb

  def index
    @users = User.all
  end
```
それをビューに表示
```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>
```
_header.html.erbの名前付きルーティングも忘れずに
### サンプルユーザー
見栄え的にユーザーを100人ぐらい増やしたい。そんな時はGemfileにFaker gemを追加する。これはdevelopment環境だけで使うのが普通だが、本文では本番環境でも使っている。
```
Gemfile
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

gem "rails",           "7.0.4"
gem "bcrypt",          "3.1.16"
gem "faker",           "2.21.0"
gem "bootstrap-sass",  "3.4.1"
```
bundle

サンプルユーザーを生成するスクリプトを追加する。ファイルとしてはdb/seeds.rbを使う。
```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
```
create!は基本的にcreateメソッドと同じだが、ユーザーが無効な場合にfalseを返すのではなく、例外を発生させる。
```
rails db:migrate:reset
rails db:seed
```
### ページネーション
Gemfileにwill_paginate gemとbootstrap-will_paginate gemを追加し、Bootstrapのページネーションスタイルを使ってwill_paginateを構成する。
```
Gemfile

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

gem "rails",                   "7.0.4"
gem "bcrypt",                  "3.1.16"
gem "faker",                   "2.21.0"
gem "will_paginate",           "3.3.1"
gem "bootstrap-will_paginate", "1.0.0"
```
bundle

今回は画面の上下にページネーションを配置
```erb
app/views/users/index.html.erb

<%= 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オブジェクトを自動的に見つけ出し、それから他のページにアクセスするためのページネーションリンクを作成している。

しかし、このままではページネーションは動かない。indexアクション内のUser.allを書き換える必用がある。
```ruby
app/controllers/users_controller.rb

  def index
    @users = User.paginate(page: params[:page])
  end
```
### ユーザー一覧のテスト
今回のテストは「ログイン」「indexページにアクセス」「最初のページにユーザーがいることを確認」「ページネーションのリンクがあることを確認」という順序で行う。少なくともユーザーが31人必用。fixtureでもありがたいことにERBが使えるので、これを使って対応する。
```
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 g integration_test users_index
```
```ruby
test/integration/users_index_test.rb

  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
```
paginationクラスを持つdivタグをチェックすることで、ユーザー一覧の最初のページが存在することを確認する。
### パーシャルページのリファクタリング
リファクタリングの第一歩はliをrender呼び出しに書き換える。
```erb
app/views/users/index.html.erb

<%= will_paginate %>

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

<%= will_paginate %>
```
renderをパーシャルに対してではなく、Userクラスのuser変数に対して実行していることに注目。ここでは、自動的に_user.html.erbという名前のパーシャルを探索するので、このパーシャルを作成する必用がある。
```erb
app/views/users/_user.html.erb

<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
</li>
```
さらに改善して、今度はrenderを@users変数に対して直接呼び出す。
```erb
app/views/users/index.html.erb

<ul class="users">
  <% @users.each do |user| %>
    <%= render @users %>
  <% end %>
</ul>
```
Railsは@usersをUserオブジェクトのリストであると推測する。renderにユーザーのコレクションを渡して呼び出すと、Railsは自動的にユーザーのコレクションを列挙し、それぞれのユーザーを_user.html.erbパーシャルで出力する。
### ユーザーを削除する
まずは、削除を実行できる権限を持つ**管理**ユーザーのクラスを作成する。承認においては、このような特権のセットを**ロール**という。
### 管理ユーザー
論理値型のadmin属性をUserモデルに追加する。
```
rails g migration add_admin_to_users admin:boolean
```
```ruby
db/migrate/[timestamp]_add_admin_to_users.rb

class AddAdminToUsers < ActiveRecord::Migration[7.0]
  def change
    add_column :users, :admin, :boolean, default: false
  end
end
```
default: falseに関しては、デフォルトでは管理者になれないということを明示している。
```
rails db:migrate
```
では仕上げに、最初のユーザーだけをデフォルトで管理者にするようサンプルデータを更新する。
```ruby
db/seeds.rb

# メインのサンプルユーザーを1人作成する
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             admin: true)
```
```
rails db:migrate:reset
rails db:migrate
```
**Strong Parameters**
このadmin属性は非常に慎重に扱うべきもので、もしも一般ユーザーからtrueに変えるよう操作されたらとんでもないことになる。なので**Strong Parameters**を使う。
```
def user_params
  params.require(:user).permit(:name, :email, :password,
                               :password_confirmation)
end
```
上のコードでは、許可された属性リストにadminが含まれていない。これにより、任意のユーザーが自分自身にアプリケーションの管理者権限を与えることを防止できる。
### destoroyアクション
現在のユーザーが管理者の時に限り[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, data: { "turbo-method": :delete,
                                          turbo_confirm: "You sure?" } %>
  <% end %>
</li>
```
"turbo-method": :deleteは、リンクに必用なDELETEリクエストを発効する準備。
2つ目のturbo_confirm: "You sure?"はJSのconfirmボックスを表示する。

ブラウザはネイティブではDELETEリクエストを送信できないので、RailsではJSを使って偽造する。JSが使えない場合は、フォームとPOSTリクエストを使って偽造できる。

ではdestroyアクションの作成と、beforeフィルターによるログインの要求を行う。
```ruby
app/controllers/users_controller.rb

  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, status: :see_other
  end
```
これで管理者だけが削除できるようになったが、まだ大きな穴がある。それは、コマンドラインでDELETEリクエストを直接発効するという方法でサイトの全ユーザーを削除できること。サイトを適切に防衛するには、destroyアクションにもアクセス制御を行う必用がある。
```ruby
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, status: :see_other) unless current_user.admin?
    end
end
```
### ユーザー削除のテスト
サンプルユーザーの1人を管理者に変える。
```
test/fixtures/users.yml

michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
  admin: true
```
テストでは2つのケースをチェックする。1つは、ユーザーがログインしていない場合は、ログイン画面にリダイレクトされること。もう1つは、ログイン済みであっても管理者でなければ、ホーム画面にリダイレクトされること。
```ruby
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_response :see_other
    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_response :see_other
    assert_redirected_to root_url
  end
end
```
destroyアクションに直接DELETEリクエストを発効するためにdeleteメソッドを使う。また、assert_no_differenceを使って、ユーザー数に変化がないことを確認している。

では管理者の振る舞いに関してもテストをする。
```ruby
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
        assert_select 'a[href=?]', user_path(user), text: 'delete'
      end
    end
    assert_difference 'User.count', -1 do
      delete user_path(@non_admin)
      assert_response :see_other
      assert_redirected_to users_url
    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
```
各ユーザーの削除リンクをテストする時、削除対象のユーザーが管理者の場合はテストをスキップしている。これにより管理者自身には削除リンクが表示されないようになっている。