Railsにおいて権限設定を簡単に実装できる方法は、Cancancanを使うことが1つの手段として考えられます。
https://github.com/CanCanCommunity/cancancan
cancancanを使った権限設定では、特定の条件を満たすリソースについては特定のアクションを許可したい、という場合があります。
そんな時に取れる対応方法を紹介します。
想定ケース
ユーザーは、企業名(name)にhogeの文字列を含む企業についてはメールを送れる。
こんな権限設定がしたいとします。
準備として、companyモデルで「名前にhogeが入っている」ことを判定するインスタンスメソッドを定義します。
class Company < ApplicationRecord
...(略)
def include_hoge?
name.include?('hoge')
end
end
Abilityの書き方
この場合、ブロックを使って以下のように書くと、上記を満たす権限設定を書くことができます。
class Ability
include Cancan::Ability
def initialize(user)
can :send_mail, Company, { include_hoge?: true }
end
end
Companyの中から、include_hoge?がtrueを返す企業に対してのみ:send_mailの権限を付与できます。
ControllerやViewで使うのは基本の使い方と同じ
一応注意点として、この方法だと、authorize_resourceを使って自動的にaction名と一致した権限を見る、といったことができなくなるので、
before_actionやaction内でauthorize!メソッドを使いながら権限チェックをすることになると思います。
- Controllerで使いたい時
FoosController < < ApplicationController
def xxx
@company = Company.find(params[:id])
authorize! :send_mail, @company
...(略)
end
end
- Viewで使いたい時
<% if can? :send, @company %>
...(略) <%# 権限があるときに表示したい項目を書く %>
<% end %>
他の方法(あまり良くないと思われる)
別の方法として、Abilityクラスのinitializeの引数でcompanyを受け取る方法でも同じことが実現できるように見えます。
class Ability
include Cancan::Ability
def initialize(user, company: nil)
can :send_mail, Company if company&.include_hoge?
end
end
この時、controllerは以下のようなかたちで書けて、authorize_resourceも使えるので一見これでも良さそうです。
FoosController < < ApplicationController
before_action :set_current_ability
authorize_resource class: Company
def xxx
...(略)
end
private
def set_current_ability
@current_ability = Ability.new(current_user, @company) # @current_abilityを上書きする。(@companyは別途定義しているものとします)
end
end
ただし、この方法だと、Abilityのインスタンス(current_ability)をその都度生成する必要があり、拡張性に欠けます。
indexページなどでよくある、@companies.each do |company| ...
のような、ループを回すときに、それぞれのcompanyに対して権限があるかどうかをチェックしたい時に都度Abilityインスタンスを生成することになり、あまり良くありません。
(最初、私はこの方法で実装していたのですがレビューで1番目の方法を教えていただきました。)