この記事をみて、さっそくbankenの乗り換えてみた。
準備
bankenのREADME.mdにある通りGemfileに追記して、
gem 'banken'
gemとApplicationLoyaltyをインストールをする。
$ bundle install
$ bundle exec rails g banken:install
乗り換えるにあたっての手抜き
乗り換えるにあたり、なるべくコード量を減らしたかったので、手抜きのためのコードを書いた。
ApplicationLoyalty#allow_all?
「この権限ならこのコントローラー全部アクセスできるでしょ!」っていうのがあると思うので、ApplicationLoyalty
にallow_all?
というメソッドを追加して、各メソッドはその値を参照するようにした。
class ApplicationLoyalty
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
allow_all?
end
def show?
allow_all?
end
def create?
allow_all?
end
def new?
create?
end
def update?
allow_all?
end
def edit?
update?
end
def destroy?
allow_all?
end
private
def allow_all?
false
end
end
使うときはこんな感じ。「この権限なら」の権限毎にallow_all?
を定義したモジュールを用意してインクルードしてる。
# app/loyalties/concerns/allow_no_signed_in_loyal.rb
module AllowNotSignedInLoyal
def allow_all?
true
end
end
# app/loyalties/concerns/allow_singed_in_loyal.rb
module AllowSignedInLoyal
def allow_all?
user.present?
end
end
# app/loyalties/welcome_loyalty.rb
class WelcomeLoyalty < ApplicationLoyalty
include AllowNotSignedInLoyal
end
# app/loyalties/posts_loyalty.rb
class PostsLoyalty < ApplicationLoyalty
include AllowSignedInLoyal
# 条件個別に定義することもできる。
def index?
true
end
end
「ログインしていればOK」「管理者権限があればOK」とかそういう大味な権限管理の場合に使う。
アクションのメソッドを定義するのをやめる
- いちいちアクションを定義するのがだるい1
- 水際で食い止めるのは
allow_all?
でいいじゃん
という気持ちがあったので、method_missing
でBanken#banken_query_name
(実際は?
で終わるメソッド)に自動で応答するようにした。
module DynamicLoyal
def method_missing(method_name, *argments, &block)
if method_name.to_s.end_with?('?')
allow_all?
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
method_name.to_s.end_with?('?') || super
end
end
やんちゃなのでApplicationLoyalty
でincludeしちゃう。
class ApplicationLoyalty
include DynamicLoyal
# snip ...
end
権限は専用のオブジェクトにまとめて移譲する。
bankenは「権限とコントローラー、ビューの紐付けをするgem」で「権限自体を定義するgemではない」って認識なので、HogeLoyalty
に具体的な権限の定義を書くのはやめてAbility
のようなクラスにまとめて定義した。2
class Ability
attr_reader :user
def initialize(user)
@user = user
end
def not_singed_in?
true
end
def signed_in?
user.present?
end
def admin?
user.try(:admin?)
end
def update_post?(post)
admin? || post.unpublished?
end
end
確認はAbility
に委譲する。
class ApplicationLoyalty
def ability
@ability ||= Ability(user)
end
end
module AllowSignedInLoyal
def allow_all?
ability.signed_in?
end
end
class PostsLoyalty < ApplicationLoyalty
include AllowSignedInLoyal
def index?
ability.not_singed_in?
end
def update?
ability.update_post?(record)
end
end
テストをかく
愚直に書くとcontroller_name
とaction_name
が分離してしまってわかりにくいので
describe 'loyalty' do
context 'user' do
let(:user) { create(:user) }
it { expect(Banken.loyalty!(:posts, user, create(:post))).to be_update }
end
end
ヘルパークラス書いた。
# spec/support/banken_permission.rb
class BankenPermission
attr_reader :user
def initialize(user)
@user = user
end
def allow?(controller_name, action_name, record = nil)
Banken.loyalty!(controller_name, user, record).public_send("#{action_name}?")
end
end
describe 'loyalty' do
context 'user' do
subject { BankenPermission.new(create(:user)) }
it { should be_allow(:posts, :update, create(:post)) }
end
end
乗り換えてみて
乗り換え前は#386 Authorization from Scratch Part 2のコードを使っていた。これと比べると、
Pros.
- 権限周りの修正の影響範囲が狭くなった。
- コントローラーごとに別クラス、別ファイルなので影響しにくい
- コンフリクトに強そう。diffが見やすそう。
- 定義順に依存とかそういうのを気にする必要が無くなった。
Cons.
- コントローラーの数だけ
Loyalty
を用意するし記述が冗長。- 冗長だけど、3行で済むLoyaltyも多いので何とかなりそう。