この記事をみて、さっそく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も多いので何とかなりそう。