action_policy 0.6.7 → 0.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/LICENSE.txt +1 -1
  4. data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +1 -1
  5. data/lib/.rbnext/2.7/action_policy/rails/scope_matchers/action_controller_params.rb +5 -3
  6. data/lib/.rbnext/2.7/action_policy/rails/scope_matchers/active_record.rb +13 -11
  7. data/lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb +1 -1
  8. data/lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb +9 -3
  9. data/lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb +10 -10
  10. data/lib/.rbnext/3.0/action_policy/policy/core.rb +1 -1
  11. data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +1 -1
  12. data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +9 -3
  13. data/lib/.rbnext/3.0/action_policy/utils/suggest_message.rb +1 -1
  14. data/lib/.rbnext/3.1/action_policy/behaviours/policy_for.rb +1 -1
  15. data/lib/.rbnext/3.1/action_policy/ext/policy_cache_key.rb +10 -10
  16. data/lib/.rbnext/3.2/action_policy/behaviours/policy_for.rb +68 -0
  17. data/lib/.rbnext/3.2/action_policy/ext/policy_cache_key.rb +72 -0
  18. data/lib/.rbnext/3.2/action_policy/lookup_chain.rb +145 -0
  19. data/lib/.rbnext/3.2/action_policy/policy/core.rb +168 -0
  20. data/lib/.rbnext/3.2/action_policy/rspec/be_authorized_to.rb +96 -0
  21. data/lib/.rbnext/3.2/action_policy/rspec/have_authorized_scope.rb +130 -0
  22. data/lib/.rbnext/3.2/action_policy/utils/suggest_message.rb +19 -0
  23. data/lib/action_policy/rails/scope_matchers/action_controller_params.rb +5 -3
  24. data/lib/action_policy/rails/scope_matchers/active_record.rb +13 -11
  25. data/lib/action_policy/railtie.rb +4 -15
  26. data/lib/action_policy/rspec/have_authorized_scope.rb +8 -2
  27. data/lib/action_policy/test_helper.rb +5 -2
  28. data/lib/action_policy/testing.rb +17 -10
  29. data/lib/action_policy/version.rb +1 -1
  30. metadata +15 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df57cf926c648dc59ed1a1afc44b352eb998f5bf03a9405127105611e641ffdb
4
- data.tar.gz: 51a8e81c6479bf06894da3a2f8fa83c98ed25da678e73bf2462f029a75c55ec4
3
+ metadata.gz: ae3b29268276a0c189e7a2ce8d83e9a68f0de8a1da555de29549d6d7712bb6da
4
+ data.tar.gz: ff91808a4bf73e284ed2cc973de4e5d4eb76e9ffe18f4f987461e8fa531f9f5b
5
5
  SHA512:
6
- metadata.gz: 6c55e23ffeff3a1d9800512759105e509abb6b51dabee1a984c56f04daa04e661ef37e056e2fee64b86a17bff8fab8167b557d8f5fe6bd9118febf0b116f7b3a
7
- data.tar.gz: 8c1459872bb8daa8bca216c1ee342b9c8c4b4b6fb5654301765ec4d2c7f8ff6612ab784269a89920c29f575ee9ecd79b2a470a415f92246db70c0b9d7a3631b1
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-2023 Vladimir Dementyev
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
- ActiveRecord::Relation.include(Module.new do
22
- def policy_name
23
- if model.respond_to?(:policy_name)
24
- model.policy_name.to_s
25
- else
26
- "#{model}Policy"
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
@@ -54,7 +54,7 @@ module ActionPolicy
54
54
  actual_calls.any? { |_1| _1.matches?(policy, rule, target, context) }
55
55
  end
56
56
 
57
- def does_not_match?(*)
57
+ def does_not_match?(*__rest__)
58
58
  raise "This matcher doesn't support negation"
59
59
  end
60
60
 
@@ -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
@@ -75,7 +75,7 @@ module ActionPolicy
75
75
  attr_reader :record, :result
76
76
 
77
77
  # NEXT_RELEASE: deprecate `record` arg, migrate to `record: nil`
78
- def initialize(record = nil, *)
78
+ def initialize(record = nil, *__rest__)
79
79
  @record = record
80
80
  end
81
81
 
@@ -54,7 +54,7 @@ module ActionPolicy
54
54
  actual_calls.any? { _1.matches?(policy, rule, target, context) }
55
55
  end
56
56
 
57
- def does_not_match?(*)
57
+ def does_not_match?(*__rest__)
58
58
  raise "This matcher doesn't support negation"
59
59
  end
60
60
 
@@ -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
@@ -13,7 +13,7 @@ module ActionPolicy
13
13
  suggestion ? "\nDid you mean? #{suggestion}" : ""
14
14
  end
15
15
  else
16
- def suggest(*) ; ""; end
16
+ def suggest(*__rest__) ; ""; end
17
17
  end
18
18
  end
19
19
  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