シナリオの記述
ページの権限管理ができていないので、この部分を実装する。以前は、ページごとに before_action で制御していたが、view の定義と一致しない問題が見受けられた。新システムでは権限を管理するメソッドを定義することにする。
まず、定義の前にシナリオを作成する。ここでは、一般ユーザが教務室のトップページを表示できないことを確認する。現在は、権限管理ができていないので、このテストはエラーとなる。
# language: ja 機能: 権限の確認 シナリオ: 一般ユーザでログインして、教務室のページが見られないことを確認する 前提 教員でログインする もし "teacher/kyoumus"にアクセスする ならば 教務室トップページと表示されていないこと
コントローラにおけるユーザ権限の実装
権限の確認は、各 controller に privilege_check を before_action することで行う。各 controller には Privileges 定数を設定し、メソッドごとの権限を記述する。app/controllers/teacher/kyoumus_controller.rb は以下のようにした。Privileges 定数はアクションをキーとして、権限一覧を配列として保持する。
before_action :privilege_check Privileges = { index: %w( is_kyoumu? ) }
privilege_check メソッドは app/controllers/application_controller.rb に記述する。権限の確認自体は、user または admin の has_privilegges? に丸投げする。
# @note ページの権限を確認して、権限がない場合にはリダイレクト # @return [Boolean] 権限がある場合には true を返す def privilege_check privileges = self.class::Privileges[self.action_name.to_sym] redirect_to_root_path_with_message(current_user_or_admin.has_privileges?(each_session, privileges)) end # @return [User,Admin] 現在のユーザまたは管理者を得る def current_user_or_admin current_user || current_admin end
モデルにおけるユーザ権限のテスト
User および Admin モデルに has_privileges? メソッドがないので記述する。モデルなので、実装の前に先に spec を記述する。これからテストが増えるので、ユーザごとの複数のテストを it 一つにまとめた(ユーザごとに共通の it を繰り返す)。
context "Each user of users" do user_keys = %i( user_hkob user_skyoumu ) context "checking the privileges" do answers = { user_hkob: [ nil ], user_skyoumu: [ true ], } privileges = [ %w( is_kyoumu? ), %w( is_kyoumu? is_kyougaku? ), ] has_privileges_answers = { user_hkob: [ nil, nil ], user_skyoumu: [ true, true ], } user_keys.each do |user_key| it "#{user_key} should have correct privileges" do user = FactoryGirl.build(user_key) session = {} user.set_privileges(session) expect(session.values_at(*User::Privileges)).to eq(answers[user_key]) privileges.each_with_index do |privilege, i| ans = has_privileges_answers[user_key][i] expect(user.has_privileges?(session, privilege)).to eq(ans) end end end end end
モデルにおけるユーザ権限の実装(共通モジュール)
has_privileges? は user および admin の両方で実装するが、内容がほぼ同一なので module として、 app/models/concerns/privilege.rb に記載する。
module Privilege # @param [Array<Symbol>] session 確認するセッション # @param [Array<Symbol>] privileges 許容する権限のシンボル配列 # @return [Boolean] 権限があれば true def has_privileges?(session, privileges) session.values_at(*privileges).compact.first end end
この module を読み込むために、app/models/user.rb に include を記述する。
class User < ActiveRecord::Base (中略) include Privilege
同様に app/models/admin.rb にも追加する。
class Admin < ActiveRecord::Base (中略) include Privilege
コントローラのテスト失敗 → ヘルパーメソッドの修正
User および Admin に has_privileges? が生成されたが、welcome#index を経由しない Devise のテストでは session に権限が設定されないために正しくテストが通過しないことがわかった。そこで、spec/rails_spec.rb の login_user_as メソッドで controller.user_session に権限を設定するように記載した。
def login_user_as(fg_key, controller) user = FactoryGirl.create(fg_key) sign_in :user, user user.set_privileges(controller.user_session) user end
spec/controllers/welcome_controller_spec.rb および spec/controllers/teacher/kyoumus_controller.rb の呼び出し側も controller を渡すように修正する。
login_user_as :user_skyoumu, controller
テスト通過 → さらなるテストの追加
これでテストがすべて通過した。以下のようにシナリオを増やして各ページに権限設定を行う。
シナリオ: 一般ユーザでログインして、年度変更のページが見られないことを確認する 前提 教員でログインする もし "teacher/years"にアクセスする ならば 年度一覧と表示されていないこと
取り急ぎ、app/controllers/teacher/years_controller.rb に権限設定を追加した。
class Teacher::YearsController < ApplicationController before_action :privilege_check Privileges = { index: %w( is_kyoumu? ) }
テストの漏れの発見 → シナリオの追加
この結果、受入テストは通過したが、spec/teacher/years_controller_spec.rb が以下のようなエラーで動かなくなった。これは、spec でログイン処理の記述が抜けているためである。すなわちログインしていない場合に、このページを表示すると user が nil のためにエラーとなることがわかる。
1) Teacher::YearsController GET index returns http success Failure/Error: get :index NoMethodError: undefined method `has_privileges?' for nil:NilClass
受入テストが足りていないことがわかったので、先にログインしていない場合のシナリオを追加する。
シナリオ: ログインしない状態で、教務室のページが見られないことを確認する もし "teacher/kyoumus"にアクセスする ならば ログインと表示されていること
コントローラの修正
さきほどの spec の記述ミスと同じエラーがでることを確認できた。そこで、app/controllers/applicaton_controller.rb の privilege_check を修正する。
def privilege_check if ua = current_user_or_admin privileges = self.class::Privileges[self.action_name.to_sym] redirect_to_root_path_with_message(ua.has_privileges?(each_session, privileges)) else flash[:alert] = 'ログインしないとこのページは表示できません' redirect_to new_user_session_path end
最後に、spec/controllers/teacher/years_controller.rb に login_user_as を追加する。これですべてのテストが通過した。
describe "GET index" do it "returns http success" do login_user_as :user_skyoumu, controller get :index expect(response).to have_http_status(:success) end end
今日はここまで。