##管理ユーザー
###admin属性
ユーザーを削除する機能を追加するが、この機能は特別な権限を持ったユーザーのみが行えるようにする。
まずはUserモデルにadmin属性を追加する。
$ rails generate migration add_admin_to_users admin:boolean
マイグレーションファイルのadd_columnにdefault: falseオプションを与えて、admin属性のデフォルト値をfalseにする。
class AddAdminToUsers < ActiveRecord::Migration[5.0]
def change
add_column :users, :admin, :boolean, default: false
end
end
サンプルユーザーの一人のadmin属性値をtrueにする。
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
###admin属性のテスト
admin属性を変更されるとセキュリティ上問題になるので、ユーザーの新規登録と編集ではユーザー情報を送信する場合にStrong Parametersという形式を用いていた。
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
これが機能しているかどうかのテストを書く。
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: '',
password_confirmation: '',
admin: true } }
assert_not @other_user.admin?
end
admin?メソッドはadmin属性を追加したことで生成されるメソッドで、Userオブジェクトに使うとadminの値に応じて論理値を返す。
##ユーザーの削除
###削除用リンク
ユーザー一覧ページに、管理者にのみ表示される削除用リンクを表示する。
<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>
リンク表示の条件式に!current_user?(user)を付けることで、管理ユーザー自身は削除できないようになっている。
link_toにmethod: :deleteを追加することで、DELETEリクエストを送信できるようになっている。
また、data: { confirm: "You sure?" }を付けると削除前に確認用のポップアップが表示される。
###destroyアクション
ユーザーを削除するdestroyアクションを書く。
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
destroyメソッドでUserオブジェクトを削除する。
その後、フラッシュメッセージを表示してユーザー一覧ページにリダイレクトする。
また、logged_in_userを使ってログインユーザー以外はアクセスできないようにしておく。
###admin_userフィルター
管理者以外に削除用リンクを表示しないようにするだけでは、セキュリティ上の問題がある。
そこで、destroyアクションには管理者のみがリクエストを送れるようにするadmin_userメソッドを用意する。
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
destroyアクションにDELETEリクエストを送っても、現在のユーザーが管理者でなければリダイレクトする。
##ユーザー削除のテスト
###管理者権限によるユーザー削除の制限のテスト
テスト用ユーザーの一人を管理者にしておく。
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
admin: true
ユーザー削除に関するテストは次の2点である。
ユーザーを削除しようとした時、
①ログインしていないユーザーの場合はログイン画面にリダイレクトする。
②ログインしているが管理者でないユーザーの場合はホーム画面にリダイレクトする。
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
ユーザーが削除されていないことをassert_no_differenceで確認している。
###ユーザー削除のテスト
ユーザー一覧画面のテストに管理者によるユーザー削除を含める。
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
管理者であれば、ユーザー一覧画面にはユーザー削除用リンクが表示されていることをassert_selectで確認している。
二つ目のテストではこれの逆である。
また、unless user == @adminとすることで、管理者以外の削除リンクだけが表示されていることを確認している。