カテゴリー:
Rails
タグ:
 Rails4 CanCanCan

このエントリーをはてなブックマークに追加
更新日時:
2015年08月16日(日)
作成日時:
2015年08月16日(日)

前の記事 / 次の記事

CanCanCanは便利そうだけど設定面倒くさそうなイメージがあって
使っていなかったのだけど、使ってみたら全然そんなことなかった。

CanCanCanというのは権限管理のためのgemで、
元々CanCanというgemがあって、その後継がCanCanCan。

参考

何ができるのか?

例えば

if can? :update, @article
  =from_for @article do |f|
    =f.text_field :name
    =f.textarea :content

if cannot? destroy, @article
  %p この操作を行う権限がありません

みたいなことができるようになる。

インストール

gemのインストール

vi Gemfile
gem 'cancancan', '~> 1.10'

bundle install

gemをインストールできたら

rails g cancan:ability

設定すべきヶ所はモデルとコントローラの二ヶ所

Abilityというモデルができているので、そのinitializeに設定を追加していく。

class Ability
  include CanCan::Ability

  def initialize(user)
    # ここに設定を追加していく
  end
end

また、この initialize はコントローラ上に定義された
current_ability メソッドによってキックされるので、必要に応じてオーバーライドする。
(例えば initialize に渡す引数を増やしたい時とか、current_user以外を渡したい時とか)

def current_ability
  @current_ability ||= Ability.new(current_user)
  # e.g. @current_ability ||= Ability.new(current_user, params)
  # e.g. @current_ability ||= Ability.new(admin_user)
end

そして、この current_ability は
can?メソッド や cannot?メソッドが実行される時にキックされる。

設定方法

設定するためのメソッドとしてcanとcannotが用意されているので、
それを使ってサクサクと定義していく。

例えば、記事とそれに対するコメントがあるとして、

  • ログインしているユーザーだけがページを閲覧することができる
  • 各記事及びコメントの作成者自身は自由に編集削除ができる
  • 自身以外の各記事及びコメントの編集は自身が管理者か超管理者であればできる
  • 自身以外の各記事及びコメントの削除は超管理者だけができる

みたいな要件があった時、

class Ability
  include CanCan::Ability

  def initialize(user)
    # be singed out
    if user.new_record?
      cannot :manage, :all # 全てのモデルに対して権限を付与する

    # be signed in
    else
      if user.super_admin? || user.admin? 
        can :manage, Article # :manage は全ての権限を付与する
        can :manage, Comment
      end

      if user.admin?
        cannot :destroy, Article # 定義は後勝ち
        cannot :destroy, Comment
      end 

      can :manage, Article, user_id: user.id # スコープを絞ることができる
      can :manage, Comment, user_id: user.id
    end
  end
end

のように書ける。定義は後勝ちなので、順序は重要。
manage の他は例えば、

can :read,    Article
can :create,  Article
can :update,  Article
can :destroy, Article

のように書ける。

ただ、これはコントローラーのアクション名に対応させて定義した方が
使いやすいから自然とそうなるという話で、cancancan において特別な意味を持つのは
全てのメソッドに対して効力のある manage だけであり、
他の指定は単に権限にラベルを付けているだけである。

can :update, Article と定義すると can? :update, @article した時に true が返り
can :destroy, Article と定義すると can? :destroy, @article した時に true が返るようになるという話。
RailsのRESTfulなアクション名とも特に依存関係はない。

だから、次のように存在しないメソッドを書いても機能する。

can :sadafaga, Article
can?    :sadafaga, @article #=> true
cannot? :sadafaga, @article #=> false

cannot :sadafaga, Article
can?    :sadafaga, @article #=> false
cannot? :sadafaga, @article #=> true

また、権限管理だからcurrent_userと依存関係がありそうな気がしていたのだけど、
全然そんなことはなくて、デフォルトでcurrent_useが渡されているだけで、
特にcurrent_userとの依存関係はない。

とにかく何か判断材料を渡してcancanすればいいだけ。

だから例えば、作成から1週間しか編集できない、
みたいな権限を次のように設定することもできる。

class ApplicationController < ActionController::Base
  def current_ability
    @current_ability ||= Ability.new(@article)
  end
end

class Ability
  include CanCan::Ability

  def initialize(article)
    can :manage, Article, id: article.id

    if article.updated_at < 1.weeks.ago
      cannot :manage, Article, id: article.id
    end
  end
end

そもそも何も渡さなくてもいい、

class ApplicationController < ActionController::Base
  def current_ability
    @current_ability ||= Ability.new
  end
end

class Ability
  include CanCan::Ability

  def initialize
     # 日曜日だけ編集できる
    if Date.new.wday == 0
      can :manage, Article
    end
  end
end

認証ヘルパー

コントローラーで使える認証ヘルパーがあるけど自分では使ってないので割愛。