module ActionPolicy::Policy::FailureReasons::Reasons
Provides failure reasons tracking functionality. That allows you to distinguish between the reasons why authorization was rejected.
It's helpful when you compose policies (i.e. use one policy within another).
For example:
class ApplicantPolicy < ApplicationPolicy def show? user.has_permission?(:view_applicants) && allowed_to?(:show?, object.stage) end end
Now when you receive an exception, you have a reasons object, which contains additional information about the failure:
rescue_from ActionPolicy::Unauthorized do |ex| ex.policy #=> ApplicantPolicy ex.rule #=> :show? ex.result.reasons.details #=> {stage: [:show?]} end
NOTE: the reason key (`stage`) is a policy identifier (underscored class name by default). For namespaced policies it has a form of:
class Admin::UserPolicy < ApplicationPolicy # .. end reasons.details #=> {:"admin/user" => [:show?]}
You can also wrap local rules into `allowed_to?` to populate reasons:
class ApplicantPolicy < ApplicationPolicy def show? allowed_to?(:view_applicants?) && allowed_to?(:show?, object.stage) end def view_applicants? user.has_permission?(:view_applicants) end end
NOTE: there is `check?` alias for `allowed_to?`.
You can provide additional details to your failure reasons by using a `details: { … }` option:
class ApplicantPolicy < ApplicationPolicy def show? allowed_to?(:show?, object.stage) end end class StagePolicy < ApplicationPolicy def show? # Add stage title to the failure reason (if any) # (could be used by client to show more descriptive message) details[:title] = record.title # then perform the checks user.stages.where(id: record.id).exists? end end # when accessing the reasons p ex.result.reasons.details #=> { stage: [{show?: {title: "Onboarding"}] }
NOTE: when using detailed reasons, the `details` array contains as the last element a hash with ALL details reasons for the policy (in a form of <rule> => <details>).
Public Class Methods
# File lib/action_policy/policy/reasons.rb, line 189 def included(base) base.result_class.prepend(ResultFailureReasons) end
Public Instance Methods
# File lib/action_policy/policy/reasons.rb, line 199 def allowed_to?(rule, record = :__undef__, inline_reasons: false, **options) res = if (record == :__undef__ || record == self.record) && options.empty? rule = resolve_rule(rule) policy = self with_clean_result { apply(rule) } else policy = policy_for(record: record, **options) rule = policy.resolve_rule(rule) policy.apply(rule) policy.result end if res.fail? && result&.reasons inline_reasons ? result.reasons.merge(res.reasons) : result.reasons.add(policy, rule, res.details) end res.clear_details res.success? end
# File lib/action_policy/policy/reasons.rb, line 222 def deny!(reason = nil) result&.reasons&.add(self, reason) if reason super() end
Add additional details to the failure reason
# File lib/action_policy/policy/reasons.rb, line 195 def details result.details ||= {} end