Ruby
Rails
cancan

CanCanを使って複数のモデルに対して異なった権限(Ability)を設定する

More than 1 year has passed since last update.

CanCan を使うとユーザモデルが各リソースに対してアクセスする際の CRUD 権限を付与できるが、
ユーザモデルが複数になった場合、それぞれに異なった権限を設定したくなるかと思います。

User モデルと Admin モデルがそれぞれ以下のような役割を持つ

■User
・一般ユーザには各リソースに対して参照権限と修正権限を付与
・特別なユーザには各リソースに対して作成権限、参照権限、修正権限、削除権限の全てを付与

■Admin
・カスタマーサポートには各リソースに対して参照権限を付与
・サービス管理者には各リソースに対して参照権限、修正権限、削除権限を付与

このような場合は、Ability クラスを複数に分けて権限を設定すると、幸せになれます。

モデルを作成して役割を与える

今回はユーザモデルの作成に devise を使用し、役割の判別は Rails 4.1 より追加された enum を使いますが、もちろん他の方法でも問題ありません。

bash
$ rails generate devise User
$ rails generate devise Admin
$ rails generate migration AddRoleToUsers role:integer
$ rails generate migration AddRoleToAdmins role:integer
$ rake db:migrate
user.rb
class User < ActiveRecord::Base

  enum role: {general: 1, special: 2}

end
admin.rb
class User < ActiveRecord::Base

  enum role: {support: 1, manager: 2}

end

権限を設定する

権限設定には CanCan を引き継いだ CanCanCan を使用し、設定は複数の Ability クラスを作成して行います。

bash
$ rails generate cancan:ability
      create  app/models/ability.rb
$ cp -ip app/models/ability.rb app/models/user_ability.rb
$ cp -ip app/models/ability.rb app/models/admin_ability.rb
user_ability.rb
class UserAbility
  include CanCan::Ability

  def initialize(user)
    if user.general?
      can :read, :all
      can :update, :all
    elsif user.special?
      can :manage, :all
    end
  end

end
admin_ability.rb
class AdminAbility
  include CanCan::Ability

  def initialize(admin)
    if admin.support?
      can :read, :all
    elsif admin.manager?
      can :read, :all
      can :update, :all
      can :destroy, :all
    end
  end

end

コントローラで適用する権限設定を振り分ける

ここが最も重要になりますが、CanCan::ControllerAdditionscurrent_ability を上書きします。

application_controller.rb
class ApplicationController < ActionController::Base  

  def current_ability
    if user_signed_in?
      @current_ability ||= ::UserAbility.new(current_user)
    elsif admin_signed_in?
      @current_ability ||= ::AdminAbility.new(current_admin)
    else
      @current_ability ||= ::Ability.new(nil)
    end
  end

end

この設定を入れることで、User でログインしている場合は UserAbility に設定した権限が適用され、
Admin でログインしている場合は AdminAbility に設定した権限が適用されます。
なお、未ログイン状態のユーザに対しては、Ability に設定した権限が適用されます。