Module: ActionPolicy::Policy::Reasons
- Included in:
- Base
- Defined in:
- lib/action_policy/policy/reasons.rb
Overview
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.(: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.(: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>).
Class Method Summary collapse
Instance Method Summary collapse
- #allowed_to?(rule, record = :__undef__, inline_reasons: false, **options) ⇒ Boolean
- #deny!(reason = nil) ⇒ Object
-
#details ⇒ Object
Add additional details to the failure reason.
Class Method Details
.included(base) ⇒ Object
189 190 191 |
# File 'lib/action_policy/policy/reasons.rb', line 189 def included(base) base.result_class.prepend(ResultFailureReasons) end |
Instance Method Details
#allowed_to?(rule, record = :__undef__, inline_reasons: false, **options) ⇒ Boolean
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/action_policy/policy/reasons.rb', line 199 def allowed_to?(rule, record = :__undef__, inline_reasons: false, **) res = if (record == :__undef__ || record == self.record) && .empty? rule = resolve_rule(rule) policy = self with_clean_result { apply(rule) } else policy = policy_for(record: record, **) 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 |
#deny!(reason = nil) ⇒ Object
222 223 224 225 |
# File 'lib/action_policy/policy/reasons.rb', line 222 def deny!(reason = nil) result&.reasons&.add(self, reason, result.details) if reason super() end |
#details ⇒ Object
Add additional details to the failure reason
195 196 197 |
# File 'lib/action_policy/policy/reasons.rb', line 195 def details result.details ||= {} end |