Spree(Solidus)にみるCancancanの設定方法

権限管理に興味があり、オープンソースのRailsアプリケーションを読んでいます。

Spreeのコードを読んでいる時にCancancanの使いかたでへぇと思ったことがあったので、メモに残します。

概要

Cancancanはinclude CanCan::Abilityしたクラスのインスタンスで、canメソッドを用いて権限を設定します。

class Ability
  include CanCan::Ability
  def initialize(user)
    case
      when user.admin?
        can :index, User
        can :show, User
        can :create, User
        can :update, User
        can :destroy, User
      when user.woker?
        can :index, User
        can :show, User
        can :update, User do |target_user|
          target_user == user
        end
      else
        can :index, User
    end
  end
end

権限を管理したいわけですから、必ず分岐が起こります。

しかしこのようにinitializeで分岐してそのままメソッドを呼ぶなどをすると、将来的にだんだん気まずくなってくる予感がビンビンありますね。

そこでAdminAbilityWorkerAbilityを個別に用意したうえで、HogeController#current_ability上で分岐したり、なんとかするのがひとつの方法だと思いますが、

Spreeの場合

Spreeはinclude CanCan::AbilityしたクラスはSpree::Abilityひとつしかありませんし、それを継承したクラスがあるわけでもありません。

solidus/ability.rb at master · solidusio/solidus

そのかわりに、Spree::Abilityインスタンスのcanメソッドを呼びだすためのSpree::PermissionSets::Baseサブクラスが何種類も(めちゃ多い)用意されています。

solidus/core/lib/spree/permission_sets at master · solidusio/solidus

Spree::Ability#initializeで必要なSpree::PermissionSets::Baseサブクラスを組みあわせ、それらがSpree::Ability#canを呼ぶことにより権限を設定しています。

Spree::PermissionSets::Base

solidus/base.rb at master · solidusio/solidus

module Spree
  module PermissionSets
    class Base
      def initialize ability
        @ability = ability
      end

      # Spree::Abilityインスタンスから呼ばれることになるメソッド
      def activate!
        raise NotImplementedError.new
      end

      private

      attr_reader :ability
      delegate :can, :cannot, :user, to: :ability
    end
  end
end

Spree::PermissionSetsdelegate :can, :cannot, :user, to: :abilityを行うことによって、シンタックス上ではCancan::Abilityと同じ感覚で設定できるようになっています。

solidus/user_display.rb at master · solidusio/solidus

module Spree
  module PermissionSets
    class UserDisplay < PermissionSets::Base
      # Spree::Abilityインスタンスから呼ばれることになるメソッド
      def activate!
        can [:display, :admin, :edit, :addresses, :orders, :items], Spree.user_class
        can [:display, :admin], Spree::StoreCredit
        can :display, Spree::Role
      end
    end
  end
end

この方式ならば、かつてCancanが滅亡したようにCancancanが滅亡しても、切りかえが楽でいいかもしれませんね。