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.
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