action_policy 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +62 -0
- data/lib/.rbnext/2.7/action_policy/i18n.rb +56 -0
- data/lib/.rbnext/2.7/action_policy/policy/cache.rb +101 -0
- data/lib/.rbnext/2.7/action_policy/policy/pre_check.rb +162 -0
- data/lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb +89 -0
- data/lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb +124 -0
- data/lib/.rbnext/2.7/action_policy/utils/pretty_print.rb +159 -0
- data/lib/.rbnext/3.0/action_policy/behaviour.rb +115 -0
- data/lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb +62 -0
- data/lib/.rbnext/3.0/action_policy/behaviours/scoping.rb +35 -0
- data/lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb +59 -0
- data/lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb +72 -0
- data/lib/.rbnext/3.0/action_policy/policy/aliases.rb +69 -0
- data/lib/.rbnext/3.0/action_policy/policy/authorization.rb +87 -0
- data/lib/.rbnext/3.0/action_policy/policy/cache.rb +101 -0
- data/lib/.rbnext/3.0/action_policy/policy/core.rb +161 -0
- data/lib/.rbnext/3.0/action_policy/policy/defaults.rb +31 -0
- data/lib/.rbnext/3.0/action_policy/policy/execution_result.rb +37 -0
- data/lib/.rbnext/3.0/action_policy/policy/pre_check.rb +162 -0
- data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +210 -0
- data/lib/.rbnext/3.0/action_policy/policy/scoping.rb +160 -0
- data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +89 -0
- data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +124 -0
- data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +159 -0
- data/lib/.rbnext/3.0/action_policy/utils/suggest_message.rb +19 -0
- data/lib/action_policy/version.rb +1 -1
- metadata +27 -2
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy
|
4
|
+
module Behaviours
|
5
|
+
# Adds `authorized_scop` method to behaviour
|
6
|
+
module Scoping
|
7
|
+
# Apply scope to the target of the specified type.
|
8
|
+
#
|
9
|
+
# NOTE: policy lookup consists of the following steps:
|
10
|
+
# - first, check whether `with` option is present
|
11
|
+
# - secondly, try to infer policy class from `target` (non-raising lookup)
|
12
|
+
# - use `implicit_authorization_target` if none of the above works.
|
13
|
+
def authorized_scope(target, type: nil, as: :default, scope_options: nil, **options)
|
14
|
+
options[:context] && (options[:context] = authorization_context.merge(options[:context]))
|
15
|
+
|
16
|
+
policy = policy_for(record: target, allow_nil: true, **options)
|
17
|
+
policy ||= policy_for(record: implicit_authorization_target!, **options)
|
18
|
+
|
19
|
+
type ||= authorization_scope_type_for(policy, target)
|
20
|
+
name = as
|
21
|
+
|
22
|
+
Authorizer.scopify(target, policy, **{type: type, name: name, scope_options: scope_options})
|
23
|
+
end
|
24
|
+
|
25
|
+
# For backward compatibility
|
26
|
+
alias authorized authorized_scope
|
27
|
+
|
28
|
+
# Infer scope type for target if none provided.
|
29
|
+
# Raises an exception if type couldn't be inferred.
|
30
|
+
def authorization_scope_type_for(policy, target)
|
31
|
+
policy.resolve_scope_type(target)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy
|
4
|
+
module PerThreadCache # :nodoc:
|
5
|
+
CACHE_KEY = "action_policy.per_thread_cache"
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_writer :enabled
|
9
|
+
|
10
|
+
def enabled?() ; @enabled == true; end
|
11
|
+
|
12
|
+
def fetch(key)
|
13
|
+
return yield unless enabled?
|
14
|
+
|
15
|
+
store = (Thread.current[CACHE_KEY] ||= {})
|
16
|
+
|
17
|
+
return store[key] if store.key?(key)
|
18
|
+
|
19
|
+
store[key] = yield
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear_all
|
23
|
+
Thread.current[CACHE_KEY] = {}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Turn off by default in test env
|
28
|
+
self.enabled = !(ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test")
|
29
|
+
end
|
30
|
+
|
31
|
+
module Behaviours
|
32
|
+
# Per-thread memoization for policies.
|
33
|
+
#
|
34
|
+
# Used by `policy_for` to re-use policy object for records.
|
35
|
+
#
|
36
|
+
# NOTE: don't forget to clear thread cache with ActionPolicy::PerThreadCache.clear_all
|
37
|
+
module ThreadMemoized
|
38
|
+
class << self
|
39
|
+
def prepended(base)
|
40
|
+
base.prepend InstanceMethods
|
41
|
+
end
|
42
|
+
|
43
|
+
alias included prepended
|
44
|
+
end
|
45
|
+
|
46
|
+
module InstanceMethods # :nodoc:
|
47
|
+
def policy_for(record:, **opts)
|
48
|
+
__policy_thread_memoize__(record, **opts) { super(record: record, **opts) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def __policy_thread_memoize__(record, **options)
|
53
|
+
cache_key = policy_for_cache_key(record: record, **options)
|
54
|
+
|
55
|
+
ActionPolicy::PerThreadCache.fetch(cache_key) { yield }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy
|
4
|
+
module Ext
|
5
|
+
# Adds #_policy_cache_key method to Object,
|
6
|
+
# which just call #policy_cache_key or #cache_key
|
7
|
+
# or #object_id (if `use_object_id` parameter is set to true).
|
8
|
+
#
|
9
|
+
# For other core classes returns string representation.
|
10
|
+
#
|
11
|
+
# Raises ArgumentError otherwise.
|
12
|
+
module PolicyCacheKey # :nodoc: all
|
13
|
+
module ObjectExt
|
14
|
+
def _policy_cache_key(use_object_id: false)
|
15
|
+
return policy_cache_key if respond_to?(:policy_cache_key)
|
16
|
+
return cache_key_with_version if respond_to?(:cache_key_with_version)
|
17
|
+
return cache_key if respond_to?(:cache_key)
|
18
|
+
|
19
|
+
return object_id.to_s if use_object_id == true
|
20
|
+
|
21
|
+
raise ArgumentError, "object is not cacheable"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
refine Object do
|
26
|
+
include ObjectExt
|
27
|
+
end
|
28
|
+
|
29
|
+
refine NilClass do
|
30
|
+
def _policy_cache_key(*) ; ""; end
|
31
|
+
end
|
32
|
+
|
33
|
+
refine TrueClass do
|
34
|
+
def _policy_cache_key(*) ; "t"; end
|
35
|
+
end
|
36
|
+
|
37
|
+
refine FalseClass do
|
38
|
+
def _policy_cache_key(*) ; "f"; end
|
39
|
+
end
|
40
|
+
|
41
|
+
refine String do
|
42
|
+
def _policy_cache_key(*) ; self; end
|
43
|
+
end
|
44
|
+
|
45
|
+
refine Symbol do
|
46
|
+
def _policy_cache_key(*) ; to_s; end
|
47
|
+
end
|
48
|
+
|
49
|
+
if RUBY_PLATFORM.match?(/java/i)
|
50
|
+
refine Integer do
|
51
|
+
def _policy_cache_key(*) ; to_s; end
|
52
|
+
end
|
53
|
+
|
54
|
+
refine Float do
|
55
|
+
def _policy_cache_key(*) ; to_s; end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
refine Numeric do
|
59
|
+
def _policy_cache_key(*) ; to_s; end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
refine Time do
|
64
|
+
def _policy_cache_key(*) ; to_s; end
|
65
|
+
end
|
66
|
+
|
67
|
+
refine Module do
|
68
|
+
def _policy_cache_key(*) ; name; end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy
|
4
|
+
module Policy
|
5
|
+
# Adds rules aliases support and ability to specify
|
6
|
+
# the default rule.
|
7
|
+
#
|
8
|
+
# class ApplicationPolicy
|
9
|
+
# include ActionPolicy::Policy::Core
|
10
|
+
# include ActionPolicy::Policy::Aliases
|
11
|
+
#
|
12
|
+
# # define which rule to use if `authorize!` called with
|
13
|
+
# # unknown rule
|
14
|
+
# default_rule :manage?
|
15
|
+
#
|
16
|
+
# alias_rule :publish?, :unpublish?, to: :update?
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# Aliases are used only during `authorize!` call (and do not act like _real_ aliases).
|
20
|
+
#
|
21
|
+
# Aliases useful when combined with `CachedApply` (since we can cache only the target rule).
|
22
|
+
module Aliases
|
23
|
+
DEFAULT = :__default__
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def included(base)
|
27
|
+
base.extend ClassMethods
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def resolve_rule(activity)
|
32
|
+
self.class.lookup_alias(activity) ||
|
33
|
+
(activity if respond_to?(activity)) ||
|
34
|
+
self.class.lookup_default_rule ||
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods # :nodoc:
|
39
|
+
def default_rule(val)
|
40
|
+
rules_aliases[DEFAULT] = val
|
41
|
+
end
|
42
|
+
|
43
|
+
def alias_rule(*rules, to:)
|
44
|
+
rules.each do |rule|
|
45
|
+
rules_aliases[rule] = to
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def lookup_alias(rule) ; rules_aliases[rule]; end
|
50
|
+
|
51
|
+
def lookup_default_rule() ; rules_aliases[DEFAULT]; end
|
52
|
+
|
53
|
+
def rules_aliases
|
54
|
+
return @rules_aliases if instance_variable_defined?(:@rules_aliases)
|
55
|
+
|
56
|
+
@rules_aliases = if superclass.respond_to?(:rules_aliases)
|
57
|
+
superclass.rules_aliases.dup
|
58
|
+
else
|
59
|
+
{}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def method_added(name)
|
64
|
+
rules_aliases.delete(name) if public_method_defined?(name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy
|
4
|
+
class AuthorizationContextMissing < Error # :nodoc:
|
5
|
+
MESSAGE_TEMPLATE = "Missing policy authorization context: %s"
|
6
|
+
|
7
|
+
attr_reader :message
|
8
|
+
|
9
|
+
def initialize(id)
|
10
|
+
@message = MESSAGE_TEMPLATE % id
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Policy
|
15
|
+
# Authorization context could include multiple parameters.
|
16
|
+
#
|
17
|
+
# It is possible to provide more verificatio contexts, by specifying them in the policy and
|
18
|
+
# providing them at the authorization step.
|
19
|
+
#
|
20
|
+
# For example:
|
21
|
+
#
|
22
|
+
# class ApplicationPolicy < ActionPolicy::Base
|
23
|
+
# # Add user and account to the context; it's required to be passed
|
24
|
+
# # to a policy constructor and be not nil
|
25
|
+
# authorize :user, :account
|
26
|
+
#
|
27
|
+
# # you can skip non-nil check if you want
|
28
|
+
# # authorize :account, allow_nil: true
|
29
|
+
#
|
30
|
+
# def manage?
|
31
|
+
# # available as a simple accessor
|
32
|
+
# account.enabled?
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# ApplicantPolicy.new(user: user, account: account)
|
37
|
+
module Authorization
|
38
|
+
class << self
|
39
|
+
def included(base)
|
40
|
+
base.extend ClassMethods
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :authorization_context
|
45
|
+
|
46
|
+
def initialize(record = nil, **params)
|
47
|
+
super(record)
|
48
|
+
|
49
|
+
@authorization_context = {}
|
50
|
+
|
51
|
+
self.class.authorization_targets.each do |id, opts|
|
52
|
+
raise AuthorizationContextMissing, id unless params.key?(id) || opts[:optional]
|
53
|
+
|
54
|
+
val = params.fetch(id, nil)
|
55
|
+
|
56
|
+
raise AuthorizationContextMissing, id if val.nil? && opts[:allow_nil] != true
|
57
|
+
|
58
|
+
authorization_context[id] = instance_variable_set("@#{id}", val)
|
59
|
+
end
|
60
|
+
|
61
|
+
authorization_context.freeze
|
62
|
+
end
|
63
|
+
|
64
|
+
module ClassMethods # :nodoc:
|
65
|
+
def authorize(*ids, allow_nil: false, optional: false)
|
66
|
+
allow_nil ||= optional
|
67
|
+
|
68
|
+
ids.each do |id|
|
69
|
+
authorization_targets[id] = {allow_nil: allow_nil, optional: optional}
|
70
|
+
end
|
71
|
+
|
72
|
+
attr_reader(*ids)
|
73
|
+
end
|
74
|
+
|
75
|
+
def authorization_targets
|
76
|
+
return @authorization_targets if instance_variable_defined?(:@authorization_targets)
|
77
|
+
|
78
|
+
@authorization_targets = if superclass.respond_to?(:authorization_targets)
|
79
|
+
superclass.authorization_targets.dup
|
80
|
+
else
|
81
|
+
{}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_policy/version"
|
4
|
+
|
5
|
+
module ActionPolicy # :nodoc:
|
6
|
+
using RubyNext
|
7
|
+
|
8
|
+
# By default cache namespace (or prefix) contains major and minor version of the gem
|
9
|
+
CACHE_NAMESPACE = "acp:#{ActionPolicy::VERSION.split(".").take(2).join(".")}"
|
10
|
+
|
11
|
+
require "action_policy/ext/policy_cache_key"
|
12
|
+
|
13
|
+
using ActionPolicy::Ext::PolicyCacheKey
|
14
|
+
|
15
|
+
module Policy
|
16
|
+
# Provides long-lived cache through ActionPolicy.cache_store.
|
17
|
+
#
|
18
|
+
# NOTE: if cache_store is nil then we silently skip all the caching.
|
19
|
+
module Cache
|
20
|
+
class << self
|
21
|
+
def included(base)
|
22
|
+
base.extend ClassMethods
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def cache_namespace() ; ActionPolicy::CACHE_NAMESPACE; end
|
27
|
+
|
28
|
+
def cache_key(*parts)
|
29
|
+
[
|
30
|
+
cache_namespace,
|
31
|
+
*parts
|
32
|
+
].map { _1._policy_cache_key }.join("/")
|
33
|
+
end
|
34
|
+
|
35
|
+
def rule_cache_key(rule)
|
36
|
+
cache_key(
|
37
|
+
context_cache_key,
|
38
|
+
record,
|
39
|
+
self.class,
|
40
|
+
rule
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def context_cache_key
|
45
|
+
authorization_context.map { _2._policy_cache_key.to_s }.join("/")
|
46
|
+
end
|
47
|
+
|
48
|
+
def apply_with_cache(rule)
|
49
|
+
options = self.class.cached_rules.fetch(rule)
|
50
|
+
key = rule_cache_key(rule)
|
51
|
+
|
52
|
+
ActionPolicy.cache_store.then do |store|
|
53
|
+
@result = store.read(key)
|
54
|
+
unless result.nil?
|
55
|
+
result.cached!
|
56
|
+
next result.value
|
57
|
+
end
|
58
|
+
yield
|
59
|
+
store.write(key, result, options)
|
60
|
+
result.value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def apply(rule)
|
65
|
+
return super if ActionPolicy.cache_store.nil? ||
|
66
|
+
!self.class.cached_rules.key?(rule)
|
67
|
+
|
68
|
+
apply_with_cache(rule) { super }
|
69
|
+
end
|
70
|
+
|
71
|
+
def cache(*parts, **options)
|
72
|
+
key = cache_key(*parts)
|
73
|
+
ActionPolicy.cache_store.then do |store|
|
74
|
+
res = store.read(key)
|
75
|
+
next res unless res.nil?
|
76
|
+
res = yield
|
77
|
+
store.write(key, res, options)
|
78
|
+
res
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module ClassMethods # :nodoc:
|
83
|
+
def cache(*rules, **options)
|
84
|
+
rules.each do |rule|
|
85
|
+
cached_rules[rule] = options
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def cached_rules
|
90
|
+
return @cached_rules if instance_variable_defined?(:@cached_rules)
|
91
|
+
|
92
|
+
@cached_rules = if superclass.respond_to?(:cached_rules)
|
93
|
+
superclass.cached_rules.dup
|
94
|
+
else
|
95
|
+
{}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_policy/behaviours/policy_for"
|
4
|
+
require "action_policy/policy/execution_result"
|
5
|
+
require "action_policy/utils/suggest_message"
|
6
|
+
require "action_policy/utils/pretty_print"
|
7
|
+
|
8
|
+
unless "".respond_to?(:underscore)
|
9
|
+
require "action_policy/ext/string_underscore"
|
10
|
+
using ActionPolicy::Ext::StringUnderscore
|
11
|
+
end
|
12
|
+
|
13
|
+
module ActionPolicy
|
14
|
+
using RubyNext
|
15
|
+
|
16
|
+
# Raised when `resolve_rule` failed to find an approriate
|
17
|
+
# policy rule method for the activity
|
18
|
+
class UnknownRule < Error
|
19
|
+
include ActionPolicy::SuggestMessage
|
20
|
+
|
21
|
+
attr_reader :policy, :rule, :message
|
22
|
+
|
23
|
+
def initialize(policy, rule)
|
24
|
+
@policy = policy.class
|
25
|
+
@rule = rule
|
26
|
+
@message =
|
27
|
+
"Couldn't find rule '#{@rule}' for #{@policy}" \
|
28
|
+
"#{suggest(@rule, @policy.instance_methods - Object.instance_methods)}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Policy
|
33
|
+
# Core policy API
|
34
|
+
module Core
|
35
|
+
class << self
|
36
|
+
def included(base)
|
37
|
+
base.extend ClassMethods
|
38
|
+
|
39
|
+
# Generate a new class for each _policy chain_
|
40
|
+
# in order to extend it independently
|
41
|
+
base.module_eval do
|
42
|
+
@result_class = Class.new(ExecutionResult)
|
43
|
+
|
44
|
+
# we need to make this class _named_,
|
45
|
+
# 'cause anonymous classes couldn't be marshalled
|
46
|
+
base.const_set(:APR, @result_class)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module ClassMethods # :nodoc:
|
52
|
+
attr_writer :identifier
|
53
|
+
|
54
|
+
def result_class
|
55
|
+
return @result_class if instance_variable_defined?(:@result_class)
|
56
|
+
@result_class = superclass.result_class
|
57
|
+
end
|
58
|
+
|
59
|
+
def identifier
|
60
|
+
return @identifier if instance_variable_defined?(:@identifier)
|
61
|
+
|
62
|
+
@identifier = name.sub(/Policy$/, "").underscore.to_sym
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
include ActionPolicy::Behaviours::PolicyFor
|
67
|
+
|
68
|
+
attr_reader :record, :result
|
69
|
+
|
70
|
+
# NEXT_RELEASE: deprecate `record` arg, migrate to `record: nil`
|
71
|
+
def initialize(record = nil, *)
|
72
|
+
@record = record
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns a result of applying the specified rule (true of false).
|
76
|
+
# Unlike simply calling a predicate rule (`policy.manage?`),
|
77
|
+
# `apply` also calls pre-checks.
|
78
|
+
def apply(rule)
|
79
|
+
@result = self.class.result_class.new(self.class, rule)
|
80
|
+
|
81
|
+
catch :policy_fulfilled do
|
82
|
+
result.load __apply__(rule)
|
83
|
+
end
|
84
|
+
|
85
|
+
result.value
|
86
|
+
end
|
87
|
+
|
88
|
+
def deny!
|
89
|
+
result&.load false
|
90
|
+
throw :policy_fulfilled
|
91
|
+
end
|
92
|
+
|
93
|
+
def allow!
|
94
|
+
result&.load true
|
95
|
+
throw :policy_fulfilled
|
96
|
+
end
|
97
|
+
|
98
|
+
# This method performs the rule call.
|
99
|
+
# Override or extend it to provide custom functionality
|
100
|
+
# (such as caching, pre checks, etc.)
|
101
|
+
def __apply__(rule) ; public_send(rule); end
|
102
|
+
|
103
|
+
# Wrap code that could modify result
|
104
|
+
# to prevent the current result modification
|
105
|
+
def with_clean_result # :nodoc:
|
106
|
+
was_result = @result
|
107
|
+
yield
|
108
|
+
@result
|
109
|
+
ensure
|
110
|
+
@result = was_result
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns a result of applying the specified rule to the specified record.
|
114
|
+
# Under the hood a policy class for record is resolved
|
115
|
+
# (unless it's explicitly set through `with` option).
|
116
|
+
#
|
117
|
+
# If record is `nil` then we uses the current policy.
|
118
|
+
def allowed_to?(rule, record = :__undef__, **options)
|
119
|
+
if (record == :__undef__ || record == self.record) && options.empty?
|
120
|
+
__apply__(resolve_rule(rule))
|
121
|
+
else
|
122
|
+
policy_for(record: record, **options).then do |policy|
|
123
|
+
policy.apply(policy.resolve_rule(rule))
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# An alias for readability purposes
|
129
|
+
def check?(*args) ; allowed_to?(*args); end
|
130
|
+
|
131
|
+
# Returns a rule name (policy method name) for activity.
|
132
|
+
#
|
133
|
+
# By default, rule name is equal to activity name.
|
134
|
+
#
|
135
|
+
# Raises ActionPolicy::UknownRule when rule is not found in policy.
|
136
|
+
def resolve_rule(activity)
|
137
|
+
raise UnknownRule.new(self, activity) unless
|
138
|
+
respond_to?(activity)
|
139
|
+
activity
|
140
|
+
end
|
141
|
+
|
142
|
+
# Return annotated source code for the rule
|
143
|
+
# NOTE: require "method_source" and "unparser" gems to be installed.
|
144
|
+
# Otherwise returns empty string.
|
145
|
+
def inspect_rule(rule) ; PrettyPrint.print_method(self, rule); end
|
146
|
+
|
147
|
+
# Helper for printing the annotated rule source.
|
148
|
+
# Useful for debugging: type `pp :show?` within the context of the policy
|
149
|
+
# to preview the rule.
|
150
|
+
def pp(rule)
|
151
|
+
with_clean_result do
|
152
|
+
# We need result to exist for `allowed_to?` to work correctly
|
153
|
+
@result = self.class.result_class.new(self.class, rule)
|
154
|
+
header = "#{self.class.name}##{rule}"
|
155
|
+
source = inspect_rule(rule)
|
156
|
+
$stdout.puts "#{header}\n#{source}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|