Class: Reeve::Authorization::Adapters::Pundit

Inherits:
Object
  • Object
show all
Defined in:
lib/reeve/authorization/adapters/pundit.rb

Overview

Pundit policies, bridged rather than depended on. Pundit itself is never required by this gem (Constitution IV); this file only speaks its conventions, and reports that it supports nothing when Pundit is absent.

class InvoicePolicy
def index? = ...
class Scope < ApplicationPolicy::Scope
  def resolve = scope.where(owner: user)
end
end

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.missing_methods(policy) ⇒ Object



44
45
46
47
48
49
# File 'lib/reeve/authorization/adapters/pundit.rb', line 44

def self.missing_methods(policy)
  return [] if supports?(policy)
  return [:Scope] if policy.is_a?(Class) && !scope_defined?(policy)

  %i[query_method Scope]
end

.policy_ancestry(policy) ⇒ Object



40
41
42
# File 'lib/reeve/authorization/adapters/pundit.rb', line 40

def self.policy_ancestry(policy)
  policy.ancestors.take_while { |ancestor| ancestor != Object }
end

.pundit_loaded?Boolean

Returns:

  • (Boolean)


17
18
19
# File 'lib/reeve/authorization/adapters/pundit.rb', line 17

def self.pundit_loaded?
  defined?(::Pundit) ? true : false
end

.scope_defined?(policy) ⇒ Boolean

Pundit policies ordinarily inherit their Scope from a base policy (class LeadPolicy < LeadBasePolicy), so asking only the policy's own namespace rejected the common case. This walks the ancestry instead, stopping before Object — which is the whole reason the check was narrow to begin with: a top-level Scope constant must never make an unrelated class look Pundit-shaped. policy::Scope resolves along the same ancestry, so what supports? accepts is exactly what scope can later reach.

Returns:

  • (Boolean)


36
37
38
# File 'lib/reeve/authorization/adapters/pundit.rb', line 36

def self.scope_defined?(policy)
  policy_ancestry(policy).any? { |ancestor| ancestor.const_defined?(:Scope, false) }
end

.supports?(policy) ⇒ Boolean

Returns:

  • (Boolean)


21
22
23
24
25
26
27
# File 'lib/reeve/authorization/adapters/pundit.rb', line 21

def self.supports?(policy)
  return false unless pundit_loaded?
  return false unless policy.is_a?(Class)

  scope_defined?(policy) &&
    policy.public_instance_methods.any? { |method| method.to_s.end_with?("?") }
end

Instance Method Details

#authorize(principal:, policy:, action:, record: nil) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/reeve/authorization/adapters/pundit.rb', line 51

def authorize(principal:, policy:, action:, record: nil)
  query = "#{action}?"
  rule = "#{policy.name}##{query}"
  subject = record || inferred_subject(policy)

  if policy.new(principal,
                subject).public_send(query)
    Decision.allow(rule: rule)
  else
    Decision.deny(rule: rule)
  end
end

#policy_for(record_class) ⇒ Object

Pundit's own convention, which is what makes per-type scoping of a mixed result possible at all: Invoice => InvoicePolicy.



77
78
79
80
81
82
# File 'lib/reeve/authorization/adapters/pundit.rb', line 77

def policy_for(record_class)
  name = "#{record_class.name}Policy"
  Object.const_defined?(name) ? Object.const_get(name) : nil
rescue NameError
  nil
end

#scope(principal:, policy:, relation:) ⇒ Object

Raises:



64
65
66
67
68
69
# File 'lib/reeve/authorization/adapters/pundit.rb', line 64

def scope(principal:, policy:, relation:)
  scoped = policy::Scope.new(principal, relation).resolve
  return scoped unless scoped.nil?

  raise Error, "#{scope_rule(policy)} returned nil; a scope must return a relation"
end

#scope_rule(policy) ⇒ Object



71
72
73
# File 'lib/reeve/authorization/adapters/pundit.rb', line 71

def scope_rule(policy)
  "#{policy.name}::Scope"
end