action_policy 0.6.7 → 0.6.9
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/CHANGELOG.md +9 -0
- data/LICENSE.txt +1 -1
- data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +1 -1
- data/lib/.rbnext/2.7/action_policy/rails/scope_matchers/action_controller_params.rb +5 -3
- data/lib/.rbnext/2.7/action_policy/rails/scope_matchers/active_record.rb +13 -11
- data/lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb +1 -1
- data/lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb +9 -3
- data/lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb +10 -10
- data/lib/.rbnext/3.0/action_policy/policy/core.rb +1 -1
- data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +1 -1
- data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +9 -3
- data/lib/.rbnext/3.0/action_policy/utils/suggest_message.rb +1 -1
- data/lib/.rbnext/3.1/action_policy/behaviours/policy_for.rb +1 -1
- data/lib/.rbnext/3.1/action_policy/ext/policy_cache_key.rb +10 -10
- data/lib/.rbnext/3.2/action_policy/behaviours/policy_for.rb +68 -0
- data/lib/.rbnext/3.2/action_policy/ext/policy_cache_key.rb +72 -0
- data/lib/.rbnext/3.2/action_policy/lookup_chain.rb +145 -0
- data/lib/.rbnext/3.2/action_policy/policy/core.rb +168 -0
- data/lib/.rbnext/3.2/action_policy/rspec/be_authorized_to.rb +96 -0
- data/lib/.rbnext/3.2/action_policy/rspec/have_authorized_scope.rb +130 -0
- data/lib/.rbnext/3.2/action_policy/utils/suggest_message.rb +19 -0
- data/lib/action_policy/rails/scope_matchers/action_controller_params.rb +5 -3
- data/lib/action_policy/rails/scope_matchers/active_record.rb +13 -11
- data/lib/action_policy/railtie.rb +4 -15
- data/lib/action_policy/rspec/have_authorized_scope.rb +8 -2
- data/lib/action_policy/test_helper.rb +5 -2
- data/lib/action_policy/testing.rb +17 -10
- data/lib/action_policy/version.rb +1 -1
- metadata +15 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae3b29268276a0c189e7a2ce8d83e9a68f0de8a1da555de29549d6d7712bb6da
|
4
|
+
data.tar.gz: ff91808a4bf73e284ed2cc973de4e5d4eb76e9ffe18f4f987461e8fa531f9f5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e914fd53642a316240ff3f4ef8e51076080bd98780fa5116678a1bc87d7ad135a34ba5ab8eff3e125d73b993044b026cb232605cf21b9ec4cb8dbbf5b2cc2f71
|
7
|
+
data.tar.gz: 4be9b544ab2a48d7e3a4a94896b417159c17b69745980a460e1927e2d8aef35b946e5a980c16fd78f83138c413afe290d510a3942f7f2deae5c10f132b8934f2
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,14 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.6.9 (2024-04-19)
|
6
|
+
|
7
|
+
- Add `.with_context` modifier to the `#have_authorized_scope` matcher. ([@killondark][])
|
8
|
+
|
9
|
+
## 0.6.8 (2024-01-17)
|
10
|
+
|
11
|
+
- Do not preload Rails base classes, use load hooks everywhere. ([@palkan][])
|
12
|
+
|
5
13
|
## 0.6.7 (2023-09-13)
|
6
14
|
|
7
15
|
- Fix loading Rails extensions during eager load. ([@palkan][])
|
@@ -505,3 +513,4 @@ This value is now stored in a cache (if any) instead of just the call result (`t
|
|
505
513
|
[@skojin]: https://github.com/skojin
|
506
514
|
[@tomdalling]: https://github.com/tomdalling
|
507
515
|
[@matsales28]: https://github.com/matsales28
|
516
|
+
[@killondark]: https://github.com/killondark
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2018-
|
3
|
+
Copyright (c) 2018-2024 Vladimir Dementyev
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -57,7 +57,7 @@ module ActionPolicy
|
|
57
57
|
)
|
58
58
|
end
|
59
59
|
|
60
|
-
def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **)
|
60
|
+
def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **__kwrest__)
|
61
61
|
record_key = record._policy_cache_key(use_object_id: true)
|
62
62
|
context_key = context.values.map { |_1| _1._policy_cache_key(use_object_id: true) }.join(".")
|
63
63
|
|
@@ -12,8 +12,10 @@ module ActionPolicy
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
# Register params scope matcher
|
16
|
-
ActionPolicy::Base.scope_matcher :action_controller_params, ActionController::Parameters
|
17
|
-
|
18
15
|
# Add alias to base policy
|
19
16
|
ActionPolicy::Base.extend ActionPolicy::ScopeMatchers::ActionControllerParams
|
17
|
+
|
18
|
+
ActiveSupport.on_load(:action_controller) do
|
19
|
+
# Register params scope matcher
|
20
|
+
ActionPolicy::Base.scope_matcher :action_controller_params, ActionController::Parameters
|
21
|
+
end
|
@@ -12,18 +12,20 @@ module ActionPolicy
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
# Register relation scope matcher
|
16
|
-
ActionPolicy::Base.scope_matcher :active_record_relation, ActiveRecord::Relation
|
17
|
-
|
18
15
|
# Add alias to base policy
|
19
16
|
ActionPolicy::Base.extend ActionPolicy::ScopeMatchers::ActiveRecord
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
18
|
+
ActiveSupport.on_load(:active_record) do
|
19
|
+
# Register relation scope matcher
|
20
|
+
ActionPolicy::Base.scope_matcher :active_record_relation, ActiveRecord::Relation
|
21
|
+
|
22
|
+
ActiveRecord::Relation.include(Module.new do
|
23
|
+
def policy_name
|
24
|
+
if model.respond_to?(:policy_name)
|
25
|
+
model.policy_name.to_s
|
26
|
+
else
|
27
|
+
"#{model}Policy"
|
28
|
+
end
|
27
29
|
end
|
28
|
-
end
|
29
|
-
end
|
30
|
+
end)
|
31
|
+
end
|
@@ -21,7 +21,7 @@ module ActionPolicy
|
|
21
21
|
#
|
22
22
|
class HaveAuthorizedScope < ::RSpec::Matchers::BuiltIn::BaseMatcher
|
23
23
|
attr_reader :type, :name, :policy, :scope_options, :actual_scopes,
|
24
|
-
:target_expectations
|
24
|
+
:target_expectations, :context
|
25
25
|
|
26
26
|
def initialize(type)
|
27
27
|
@type = type
|
@@ -49,6 +49,11 @@ module ActionPolicy
|
|
49
49
|
self
|
50
50
|
end
|
51
51
|
|
52
|
+
def with_context(context)
|
53
|
+
@context = context
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
52
57
|
def match(_expected, actual)
|
53
58
|
raise "This matcher only supports block expectations" unless actual.is_a?(Proc)
|
54
59
|
|
@@ -56,7 +61,7 @@ module ActionPolicy
|
|
56
61
|
|
57
62
|
@actual_scopes = ActionPolicy::Testing::AuthorizeTracker.scopings
|
58
63
|
|
59
|
-
matching_scopes = actual_scopes.select { |_1| _1.matches?(policy, type, name, scope_options) }
|
64
|
+
matching_scopes = actual_scopes.select { |_1| _1.matches?(policy, type, name, scope_options, context) }
|
60
65
|
|
61
66
|
return false if matching_scopes.empty?
|
62
67
|
|
@@ -71,7 +76,7 @@ module ActionPolicy
|
|
71
76
|
true
|
72
77
|
end
|
73
78
|
|
74
|
-
def does_not_match?(*)
|
79
|
+
def does_not_match?(*__rest__)
|
75
80
|
raise "This matcher doesn't support negation"
|
76
81
|
end
|
77
82
|
|
@@ -80,6 +85,7 @@ module ActionPolicy
|
|
80
85
|
def failure_message
|
81
86
|
"expected a scoping named :#{name} for type :#{type} " \
|
82
87
|
"#{scope_options_message} " \
|
88
|
+
"#{context ? "and context #{context.inspect} " : ""}" \
|
83
89
|
"from #{policy} to have been applied, " \
|
84
90
|
"but #{actual_scopes_message}"
|
85
91
|
end
|
@@ -27,45 +27,45 @@ module ActionPolicy
|
|
27
27
|
end
|
28
28
|
|
29
29
|
refine NilClass do
|
30
|
-
def _policy_cache_key(*) ; ""; end
|
30
|
+
def _policy_cache_key(*__rest__) ; ""; end
|
31
31
|
end
|
32
32
|
|
33
33
|
refine TrueClass do
|
34
|
-
def _policy_cache_key(*) ; "t"; end
|
34
|
+
def _policy_cache_key(*__rest__) ; "t"; end
|
35
35
|
end
|
36
36
|
|
37
37
|
refine FalseClass do
|
38
|
-
def _policy_cache_key(*) ; "f"; end
|
38
|
+
def _policy_cache_key(*__rest__) ; "f"; end
|
39
39
|
end
|
40
40
|
|
41
41
|
refine String do
|
42
|
-
def _policy_cache_key(*) ; self; end
|
42
|
+
def _policy_cache_key(*__rest__) ; self; end
|
43
43
|
end
|
44
44
|
|
45
45
|
refine Symbol do
|
46
|
-
def _policy_cache_key(*) ; to_s; end
|
46
|
+
def _policy_cache_key(*__rest__) ; to_s; end
|
47
47
|
end
|
48
48
|
|
49
49
|
if RUBY_PLATFORM.match?(/java/i)
|
50
50
|
refine Integer do
|
51
|
-
def _policy_cache_key(*) ; to_s; end
|
51
|
+
def _policy_cache_key(*__rest__) ; to_s; end
|
52
52
|
end
|
53
53
|
|
54
54
|
refine Float do
|
55
|
-
def _policy_cache_key(*) ; to_s; end
|
55
|
+
def _policy_cache_key(*__rest__) ; to_s; end
|
56
56
|
end
|
57
57
|
else
|
58
58
|
refine Numeric do
|
59
|
-
def _policy_cache_key(*) ; to_s; end
|
59
|
+
def _policy_cache_key(*__rest__) ; to_s; end
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
63
|
refine Time do
|
64
|
-
def _policy_cache_key(*) ; to_s; end
|
64
|
+
def _policy_cache_key(*__rest__) ; to_s; end
|
65
65
|
end
|
66
66
|
|
67
67
|
refine Module do
|
68
|
-
def _policy_cache_key(*) ; name; end
|
68
|
+
def _policy_cache_key(*__rest__) ; name; end
|
69
69
|
end
|
70
70
|
end
|
71
71
|
end
|
@@ -21,7 +21,7 @@ module ActionPolicy
|
|
21
21
|
#
|
22
22
|
class HaveAuthorizedScope < ::RSpec::Matchers::BuiltIn::BaseMatcher
|
23
23
|
attr_reader :type, :name, :policy, :scope_options, :actual_scopes,
|
24
|
-
:target_expectations
|
24
|
+
:target_expectations, :context
|
25
25
|
|
26
26
|
def initialize(type)
|
27
27
|
@type = type
|
@@ -49,6 +49,11 @@ module ActionPolicy
|
|
49
49
|
self
|
50
50
|
end
|
51
51
|
|
52
|
+
def with_context(context)
|
53
|
+
@context = context
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
52
57
|
def match(_expected, actual)
|
53
58
|
raise "This matcher only supports block expectations" unless actual.is_a?(Proc)
|
54
59
|
|
@@ -56,7 +61,7 @@ module ActionPolicy
|
|
56
61
|
|
57
62
|
@actual_scopes = ActionPolicy::Testing::AuthorizeTracker.scopings
|
58
63
|
|
59
|
-
matching_scopes = actual_scopes.select { _1.matches?(policy, type, name, scope_options) }
|
64
|
+
matching_scopes = actual_scopes.select { _1.matches?(policy, type, name, scope_options, context) }
|
60
65
|
|
61
66
|
return false if matching_scopes.empty?
|
62
67
|
|
@@ -71,7 +76,7 @@ module ActionPolicy
|
|
71
76
|
true
|
72
77
|
end
|
73
78
|
|
74
|
-
def does_not_match?(*)
|
79
|
+
def does_not_match?(*__rest__)
|
75
80
|
raise "This matcher doesn't support negation"
|
76
81
|
end
|
77
82
|
|
@@ -80,6 +85,7 @@ module ActionPolicy
|
|
80
85
|
def failure_message
|
81
86
|
"expected a scoping named :#{name} for type :#{type} " \
|
82
87
|
"#{scope_options_message} " \
|
88
|
+
"#{context ? "and context #{context.inspect} " : ""}" \
|
83
89
|
"from #{policy} to have been applied, " \
|
84
90
|
"but #{actual_scopes_message}"
|
85
91
|
end
|
@@ -57,7 +57,7 @@ module ActionPolicy
|
|
57
57
|
)
|
58
58
|
end
|
59
59
|
|
60
|
-
def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **)
|
60
|
+
def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **__kwrest__)
|
61
61
|
record_key = record._policy_cache_key(use_object_id: true)
|
62
62
|
context_key = context.values.map { _1._policy_cache_key(use_object_id: true) }.join(".")
|
63
63
|
|
@@ -27,45 +27,45 @@ module ActionPolicy
|
|
27
27
|
end
|
28
28
|
|
29
29
|
refine NilClass do
|
30
|
-
def _policy_cache_key(*) = ""
|
30
|
+
def _policy_cache_key(*__rest__) = ""
|
31
31
|
end
|
32
32
|
|
33
33
|
refine TrueClass do
|
34
|
-
def _policy_cache_key(*) = "t"
|
34
|
+
def _policy_cache_key(*__rest__) = "t"
|
35
35
|
end
|
36
36
|
|
37
37
|
refine FalseClass do
|
38
|
-
def _policy_cache_key(*) = "f"
|
38
|
+
def _policy_cache_key(*__rest__) = "f"
|
39
39
|
end
|
40
40
|
|
41
41
|
refine String do
|
42
|
-
def _policy_cache_key(*) = self
|
42
|
+
def _policy_cache_key(*__rest__) = self
|
43
43
|
end
|
44
44
|
|
45
45
|
refine Symbol do
|
46
|
-
def _policy_cache_key(*) = to_s
|
46
|
+
def _policy_cache_key(*__rest__) = to_s
|
47
47
|
end
|
48
48
|
|
49
49
|
if RUBY_PLATFORM.match?(/java/i)
|
50
50
|
refine Integer do
|
51
|
-
def _policy_cache_key(*) = to_s
|
51
|
+
def _policy_cache_key(*__rest__) = to_s
|
52
52
|
end
|
53
53
|
|
54
54
|
refine Float do
|
55
|
-
def _policy_cache_key(*) = to_s
|
55
|
+
def _policy_cache_key(*__rest__) = to_s
|
56
56
|
end
|
57
57
|
else
|
58
58
|
refine Numeric do
|
59
|
-
def _policy_cache_key(*) = to_s
|
59
|
+
def _policy_cache_key(*__rest__) = to_s
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
63
|
refine Time do
|
64
|
-
def _policy_cache_key(*) = to_s
|
64
|
+
def _policy_cache_key(*__rest__) = to_s
|
65
65
|
end
|
66
66
|
|
67
67
|
refine Module do
|
68
|
-
def _policy_cache_key(*) = name
|
68
|
+
def _policy_cache_key(*__rest__) = name
|
69
69
|
end
|
70
70
|
end
|
71
71
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy
|
4
|
+
module Behaviours
|
5
|
+
# Adds `policy_for` method
|
6
|
+
module PolicyFor
|
7
|
+
require "action_policy/ext/policy_cache_key"
|
8
|
+
using ActionPolicy::Ext::PolicyCacheKey
|
9
|
+
|
10
|
+
# Returns policy instance for the record.
|
11
|
+
def policy_for(record:, with: nil, namespace: authorization_namespace, context: nil, allow_nil: false, default: default_authorization_policy_class, strict_namespace: authorization_strict_namespace)
|
12
|
+
context = context ? authorization_context.merge(context) : authorization_context
|
13
|
+
|
14
|
+
policy_class = with || ::ActionPolicy.lookup(
|
15
|
+
record,
|
16
|
+
namespace:, context:, allow_nil:, default:, strict_namespace:
|
17
|
+
)
|
18
|
+
policy_class&.new(record, **context)
|
19
|
+
end
|
20
|
+
|
21
|
+
def authorization_context
|
22
|
+
Kernel.raise NotImplementedError, "Please, define `authorization_context` method!"
|
23
|
+
end
|
24
|
+
|
25
|
+
def authorization_namespace
|
26
|
+
# override to provide specific authorization namespace
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_authorization_policy_class
|
30
|
+
# override to provide a policy class use when no policy found
|
31
|
+
end
|
32
|
+
|
33
|
+
def authorization_strict_namespace
|
34
|
+
# override to provide strict namespace lookup option
|
35
|
+
end
|
36
|
+
|
37
|
+
# Override this method to provide implicit authorization target
|
38
|
+
# that would be used in case `record` is not specified in
|
39
|
+
# `authorize!` and `allowed_to?` call.
|
40
|
+
#
|
41
|
+
# It is also used to infer a policy for scoping (in `authorized_scope` method).
|
42
|
+
def implicit_authorization_target
|
43
|
+
# no-op
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return implicit authorization target or raises an exception if it's nil
|
47
|
+
def implicit_authorization_target!
|
48
|
+
implicit_authorization_target || Kernel.raise(
|
49
|
+
NotFound,
|
50
|
+
[
|
51
|
+
self,
|
52
|
+
"Couldn't find implicit authorization target " \
|
53
|
+
"for #{self.class}. " \
|
54
|
+
"Please, provide policy class explicitly using `with` option or " \
|
55
|
+
"define the `implicit_authorization_target` method."
|
56
|
+
]
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **__kwrest__)
|
61
|
+
record_key = record._policy_cache_key(use_object_id: true)
|
62
|
+
context_key = context.values.map { _1._policy_cache_key(use_object_id: true) }.join(".")
|
63
|
+
|
64
|
+
"#{namespace}/#{with}/#{context_key}/#{record_key}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
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
|
+
import_methods ObjectExt
|
27
|
+
end
|
28
|
+
|
29
|
+
refine NilClass do
|
30
|
+
def _policy_cache_key(*__rest__) = ""
|
31
|
+
end
|
32
|
+
|
33
|
+
refine TrueClass do
|
34
|
+
def _policy_cache_key(*__rest__) = "t"
|
35
|
+
end
|
36
|
+
|
37
|
+
refine FalseClass do
|
38
|
+
def _policy_cache_key(*__rest__) = "f"
|
39
|
+
end
|
40
|
+
|
41
|
+
refine String do
|
42
|
+
def _policy_cache_key(*__rest__) = self
|
43
|
+
end
|
44
|
+
|
45
|
+
refine Symbol do
|
46
|
+
def _policy_cache_key(*__rest__) = to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
if RUBY_PLATFORM.match?(/java/i)
|
50
|
+
refine Integer do
|
51
|
+
def _policy_cache_key(*__rest__) = to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
refine Float do
|
55
|
+
def _policy_cache_key(*__rest__) = to_s
|
56
|
+
end
|
57
|
+
else
|
58
|
+
refine Numeric do
|
59
|
+
def _policy_cache_key(*__rest__) = to_s
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
refine Time do
|
64
|
+
def _policy_cache_key(*__rest__) = to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
refine Module do
|
68
|
+
def _policy_cache_key(*__rest__) = name
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy
|
4
|
+
# LookupChain contains _resolvers_ to determine a policy
|
5
|
+
# for a record (with additional options).
|
6
|
+
#
|
7
|
+
# You can modify the `LookupChain.chain` (for example, to add
|
8
|
+
# custom resolvers).
|
9
|
+
module LookupChain
|
10
|
+
unless "".respond_to?(:safe_constantize)
|
11
|
+
require "action_policy/ext/string_constantize"
|
12
|
+
using ActionPolicy::Ext::StringConstantize
|
13
|
+
end
|
14
|
+
|
15
|
+
require "action_policy/ext/symbol_camelize"
|
16
|
+
using ActionPolicy::Ext::SymbolCamelize
|
17
|
+
|
18
|
+
require "action_policy/ext/module_namespace"
|
19
|
+
using ActionPolicy::Ext::ModuleNamespace
|
20
|
+
|
21
|
+
# Cache namespace resolving result for policies.
|
22
|
+
# @see benchmarks/namespaced_lookup_cache.rb
|
23
|
+
class NamespaceCache
|
24
|
+
class << self
|
25
|
+
attr_reader :store
|
26
|
+
|
27
|
+
def put_if_absent(scope, namespace, policy)
|
28
|
+
local_store = store[scope][namespace]
|
29
|
+
return local_store[policy] if local_store[policy]
|
30
|
+
local_store[policy] ||= yield
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch(namespace, policy, strict:, &block)
|
34
|
+
return yield unless LookupChain.namespace_cache_enabled?
|
35
|
+
|
36
|
+
if strict
|
37
|
+
put_if_absent(:strict, namespace, policy, &block)
|
38
|
+
else
|
39
|
+
put_if_absent(:flexible, namespace, policy, &block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def clear
|
44
|
+
hash = Hash.new { |h, k| h[k] = {} }
|
45
|
+
@store = {strict: hash, flexible: hash.clone}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
clear
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self
|
53
|
+
attr_accessor :chain, :namespace_cache_enabled
|
54
|
+
|
55
|
+
alias_method :namespace_cache_enabled?, :namespace_cache_enabled
|
56
|
+
|
57
|
+
def call(record, **opts)
|
58
|
+
chain.each do |probe|
|
59
|
+
val = probe.call(record, **opts)
|
60
|
+
return val unless val.nil?
|
61
|
+
end
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def lookup_within_namespace(policy_name, namespace, strict: false)
|
68
|
+
NamespaceCache.fetch(namespace&.name || "Kernel", policy_name, strict: strict) do
|
69
|
+
mod = namespace
|
70
|
+
policy_class = nil
|
71
|
+
|
72
|
+
loop do
|
73
|
+
policy_class = [mod&.name, policy_name].compact.join("::").safe_constantize
|
74
|
+
break policy_class if policy_class || mod.nil?
|
75
|
+
|
76
|
+
mod = mod.namespace
|
77
|
+
end
|
78
|
+
|
79
|
+
next policy_class if !strict || namespace.nil? || policy_class.nil?
|
80
|
+
|
81
|
+
# If we're in the strict mode and the namespace boundary is provided,
|
82
|
+
# we must check that the found policy satisfies it
|
83
|
+
policy_class if policy_class.name.start_with?("#{namespace.name}::")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def policy_class_name_for(record)
|
88
|
+
return record.policy_name.to_s if record.respond_to?(:policy_name)
|
89
|
+
|
90
|
+
record_class = record.is_a?(Module) ? record : record.class
|
91
|
+
|
92
|
+
if record_class.respond_to?(:policy_name)
|
93
|
+
record_class.policy_name.to_s
|
94
|
+
else
|
95
|
+
"#{record_class}Policy"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Enable namespace cache by default or
|
101
|
+
# if RACK_ENV provided and equal to "production"
|
102
|
+
self.namespace_cache_enabled =
|
103
|
+
(!ENV["RACK_ENV"].nil?) ? ENV["RACK_ENV"] == "production" : true
|
104
|
+
|
105
|
+
# By self `policy_class` method
|
106
|
+
INSTANCE_POLICY_CLASS = ->(record, **__kwrest__) {
|
107
|
+
record.policy_class if record.respond_to?(:policy_class)
|
108
|
+
}
|
109
|
+
|
110
|
+
# By record's class `policy_class` method
|
111
|
+
CLASS_POLICY_CLASS = ->(record, **__kwrest__) {
|
112
|
+
record.class.policy_class if record.class.respond_to?(:policy_class)
|
113
|
+
}
|
114
|
+
|
115
|
+
# Infer from class name
|
116
|
+
INFER_FROM_CLASS = ->(record, namespace: nil, strict_namespace: false, **__kwrest__) {
|
117
|
+
policy_name = policy_class_name_for(record)
|
118
|
+
lookup_within_namespace(policy_name, namespace, strict: strict_namespace)
|
119
|
+
}
|
120
|
+
|
121
|
+
# Infer from passed symbol
|
122
|
+
SYMBOL_LOOKUP = ->(record, namespace: nil, strict_namespace: false, **__kwrest__) {
|
123
|
+
next unless record.is_a?(Symbol)
|
124
|
+
|
125
|
+
policy_name = "#{record.camelize}Policy"
|
126
|
+
lookup_within_namespace(policy_name, namespace, strict: strict_namespace)
|
127
|
+
}
|
128
|
+
|
129
|
+
# (Optional) Infer using String#classify if available
|
130
|
+
CLASSIFY_SYMBOL_LOOKUP = ->(record, namespace: nil, strict_namespace: false, **__kwrest__) {
|
131
|
+
next unless record.is_a?(Symbol)
|
132
|
+
|
133
|
+
policy_name = "#{record.to_s.classify}Policy"
|
134
|
+
lookup_within_namespace(policy_name, namespace, strict: strict_namespace)
|
135
|
+
}
|
136
|
+
|
137
|
+
self.chain = [
|
138
|
+
SYMBOL_LOOKUP,
|
139
|
+
(CLASSIFY_SYMBOL_LOOKUP if String.method_defined?(:classify)),
|
140
|
+
INSTANCE_POLICY_CLASS,
|
141
|
+
CLASS_POLICY_CLASS,
|
142
|
+
INFER_FROM_CLASS
|
143
|
+
].compact
|
144
|
+
end
|
145
|
+
end
|