action_policy 0.5.6 → 0.6.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -1
  3. data/lib/.rbnext/1995.next/action_policy/utils/pretty_print.rb +2 -2
  4. data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +10 -4
  5. data/lib/.rbnext/2.7/action_policy/utils/pretty_print.rb +2 -2
  6. data/lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb +1 -1
  7. data/lib/.rbnext/3.0/action_policy/policy/aliases.rb +8 -0
  8. data/lib/.rbnext/3.0/action_policy/policy/core.rb +12 -5
  9. data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +18 -2
  10. data/lib/.rbnext/3.0/action_policy/rspec/be_an_alias_of.rb +70 -0
  11. data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +2 -2
  12. data/lib/.rbnext/{1995.next → 3.1}/action_policy/behaviours/policy_for.rb +10 -4
  13. data/lib/.rbnext/{1995.next → 3.1}/action_policy/behaviours/scoping.rb +0 -0
  14. data/lib/.rbnext/3.1/action_policy/ext/module_namespace.rb +32 -0
  15. data/lib/.rbnext/3.1/action_policy/ext/policy_cache_key.rb +72 -0
  16. data/lib/.rbnext/{1995.next → 3.1}/action_policy/policy/authorization.rb +0 -0
  17. data/lib/action_policy/behaviour.rb +2 -4
  18. data/lib/action_policy/behaviours/policy_for.rb +10 -4
  19. data/lib/action_policy/ext/module_namespace.rb +5 -1
  20. data/lib/action_policy/ext/policy_cache_key.rb +1 -1
  21. data/lib/action_policy/lookup_chain.rb +10 -2
  22. data/lib/action_policy/policy/authorization.rb +1 -1
  23. data/lib/action_policy/policy/core.rb +3 -3
  24. data/lib/action_policy/policy/reasons.rb +18 -2
  25. data/lib/action_policy/rails/controller.rb +5 -2
  26. data/lib/action_policy/rspec/be_an_alias_of.rb +70 -0
  27. data/lib/action_policy/rspec.rb +1 -0
  28. data/lib/action_policy/version.rb +1 -1
  29. metadata +17 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e859b71805cff1035904f6a6b6ec73e19f4722e894f88b9a7834a30569c4f587
4
- data.tar.gz: 2df569e93f84268bf3244d1615559716b16081d53df826dfe856cefb7dd0becc
3
+ metadata.gz: ea35d16fad09b0ec03201b12ff0ff17b8a189fba92c2391ca88d4140e8561570
4
+ data.tar.gz: 0decb11729d971bcf4fe3ef78b3fe461e42b61e5ed9c0b58fb6d3d9cdd2e8d48
5
5
  SHA512:
6
- metadata.gz: 6457503550706d1cc63e4e636c675f9d7a0f70184deb0217a8ad79206c897e00317eef14522a72c415b8e292ee0d52a9837c011e2793cec1eee55e8ed792f778
7
- data.tar.gz: 148141b42a80097b3b7369118027b77a8cd7a1412cef4b3cd75f5e80415c93cd60f1d1a5d3fd1ee0f30f44886475f59db408deffa3c40d060ff3319fef45c1b9
6
+ metadata.gz: 10048a5581c03d81ebb582c96bd7a168a800028d2d19783f44ce8837cb2747a1d80249195d0d4df090ebc34002f5ffde90edb5dc34e50765f61ed4c1782ac5ce
7
+ data.tar.gz: e369370e68ba73cd6645c1b90ea7ede7e4d3b446aa582a77c7a417ccb95065ef06e3461b303317895835004dfbdcf5bacff58664dda7eafe71e09a691cc22c0f
data/CHANGELOG.md CHANGED
@@ -2,7 +2,25 @@
2
2
 
3
3
  ## master
4
4
 
5
- ## 0.5.6 (2021-03-03)
5
+ ## 0.6.1 (2022-05-23)
6
+
7
+ - Fix policy lookup when a namespaced record is passed and the strict mode is used. ([@palkan][])
8
+ - Expose `#authorized_scope` as helper. ([@palkan][])
9
+ - [Fixes [#207](https://github.com/palkan/action_policy/issues/207)] refinement#include deprecation warning on Ruby 3.1
10
+
11
+ ## 0.6.0 (2021-09-02)
12
+
13
+ - Drop Ruby 2.5 support.
14
+ - [Closes [#186](https://github.com/palkan/action_policy/issues/186)] Add `inline_reasons: true` option to `allowed_to?` to avoid wrapping reasons. ([@palkan][])
15
+ - [Fixes [#173](https://github.com/palkan/action_policy/issues/173)] Explicit context were not merged with implicit one within policy classes. ([@palkan][])
16
+ - Add `strict_namespace:` option to policy_for behaviour ([@kevynlebouille][])
17
+ - Prevent possible side effects in policy lookup ([@tomdalling][])
18
+
19
+ ## 0.5.7 (2021-03-03)
20
+
21
+ The previous release had incorrect dependencies (due to the missing transpiled files).
22
+
23
+ ## ~~0.5.6 (2021-03-03)~~
6
24
 
7
25
  - Add `ActionPolicy.enforce_predicate_rules_naming` config to catch rule missing question mark ([@skojin][])
8
26
 
@@ -445,3 +463,4 @@ This value is now stored in a cache (if any) instead of just the call result (`t
445
463
  [@Be-ngt-oH]: https://github.com/Be-ngt-oH
446
464
  [@pirj]: https://github.com/pirj
447
465
  [@skojin]: https://github.com/skojin
466
+ [@tomdalling]: https://github.com/tomdalling
@@ -146,8 +146,8 @@ module ActionPolicy
146
146
 
147
147
  def colorize(val)
148
148
  return val unless $stdout.isatty
149
- return TRUE if val.eql?(true)
150
- return FALSE if val.eql?(false)
149
+ return TRUE if val.eql?(true) # rubocop:disable Lint/DeprecatedConstants
150
+ return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
151
151
  val
152
152
  end
153
153
  end
@@ -8,16 +8,18 @@ module ActionPolicy
8
8
  using ActionPolicy::Ext::PolicyCacheKey
9
9
 
10
10
  # Returns policy instance for the record.
11
- def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
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
+
12
14
  policy_class = with || ::ActionPolicy.lookup(
13
15
  record,
14
- namespace: namespace, context: context, allow_nil: allow_nil, default: default
16
+ namespace: namespace, context: context, allow_nil: allow_nil, default: default, strict_namespace: strict_namespace
15
17
  )
16
18
  policy_class&.new(record, **context)
17
19
  end
18
20
 
19
21
  def authorization_context
20
- raise NotImplementedError, "Please, define `authorization_context` method!"
22
+ Kernel.raise NotImplementedError, "Please, define `authorization_context` method!"
21
23
  end
22
24
 
23
25
  def authorization_namespace
@@ -28,6 +30,10 @@ module ActionPolicy
28
30
  # override to provide a policy class use when no policy found
29
31
  end
30
32
 
33
+ def authorization_strict_namespace
34
+ # override to provide strict namespace lookup option
35
+ end
36
+
31
37
  # Override this method to provide implicit authorization target
32
38
  # that would be used in case `record` is not specified in
33
39
  # `authorize!` and `allowed_to?` call.
@@ -39,7 +45,7 @@ module ActionPolicy
39
45
 
40
46
  # Return implicit authorization target or raises an exception if it's nil
41
47
  def implicit_authorization_target!
42
- implicit_authorization_target || raise(
48
+ implicit_authorization_target || Kernel.raise(
43
49
  NotFound,
44
50
  [
45
51
  self,
@@ -146,8 +146,8 @@ module ActionPolicy
146
146
 
147
147
  def colorize(val)
148
148
  return val unless $stdout.isatty
149
- return TRUE if val.eql?(true)
150
- return FALSE if val.eql?(false)
149
+ return TRUE if val.eql?(true) # rubocop:disable Lint/DeprecatedConstants
150
+ return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
151
151
  val
152
152
  end
153
153
  end
@@ -23,7 +23,7 @@ module ActionPolicy
23
23
  end
24
24
 
25
25
  refine Object do
26
- include ObjectExt
26
+ ::RubyNext::Core.import_methods(ObjectExt, binding)
27
27
  end
28
28
 
29
29
  refine NilClass do
@@ -31,10 +31,18 @@ module ActionPolicy
31
31
  def resolve_rule(activity)
32
32
  self.class.lookup_alias(activity) ||
33
33
  (activity if respond_to?(activity)) ||
34
+ (check_rule_naming(activity) if ActionPolicy.enforce_predicate_rules_naming) ||
34
35
  self.class.lookup_default_rule ||
35
36
  super
36
37
  end
37
38
 
39
+ private def check_rule_naming(activity)
40
+ unless activity[-1] == "?"
41
+ raise NonPredicateRule.new(self, activity)
42
+ end
43
+ nil
44
+ end
45
+
38
46
  module ClassMethods # :nodoc:
39
47
  def default_rule(val)
40
48
  rules_aliases[DEFAULT] = val
@@ -23,12 +23,19 @@ module ActionPolicy
23
23
  def initialize(policy, rule)
24
24
  @policy = policy.class
25
25
  @rule = rule
26
- @message =
27
- "Couldn't find rule '#{@rule}' for #{@policy}" \
26
+ @message = "Couldn't find rule '#{@rule}' for #{@policy}" \
28
27
  "#{suggest(@rule, @policy.instance_methods - Object.instance_methods)}"
29
28
  end
30
29
  end
31
30
 
31
+ class NonPredicateRule < UnknownRule
32
+ def initialize(policy, rule)
33
+ @policy = policy.class
34
+ @rule = rule
35
+ @message = "The rule '#{@rule}' of '#{@policy}' must ends with ? (question mark)\nDid you mean? #{@rule}?"
36
+ end
37
+ end
38
+
32
39
  module Policy
33
40
  # Core policy API
34
41
  module Core
@@ -79,7 +86,7 @@ module ActionPolicy
79
86
  @result = self.class.result_class.new(self.class, rule)
80
87
 
81
88
  catch :policy_fulfilled do
82
- result.load __apply__(rule)
89
+ result.load __apply__(resolve_rule(rule))
83
90
  end
84
91
 
85
92
  result.value
@@ -126,13 +133,13 @@ module ActionPolicy
126
133
  end
127
134
 
128
135
  # An alias for readability purposes
129
- def check?(*args) ; allowed_to?(*args); end
136
+ def check?(*args, **hargs) ; allowed_to?(*args, **hargs); end
130
137
 
131
138
  # Returns a rule name (policy method name) for activity.
132
139
  #
133
140
  # By default, rule name is equal to activity name.
134
141
  #
135
- # Raises ActionPolicy::UknownRule when rule is not found in policy.
142
+ # Raises ActionPolicy::UnknownRule when rule is not found in policy.
136
143
  def resolve_rule(activity)
137
144
  raise UnknownRule.new(self, activity) unless
138
145
  respond_to?(activity)
@@ -31,6 +31,20 @@ module ActionPolicy
31
31
 
32
32
  def present?() ; !empty?; end
33
33
 
34
+ def merge(other)
35
+ other.reasons.each do |policy_class, rules|
36
+ reasons[policy_class] ||= []
37
+
38
+ rules.each do |rule|
39
+ if rule.is_a?(::Hash)
40
+ add_detailed_reason(reasons[policy_class], rule)
41
+ else
42
+ add_non_detailed_reason(reasons[policy_class], rule)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
34
48
  private
35
49
 
36
50
  def add_non_detailed_reason(store, rule)
@@ -182,7 +196,7 @@ module ActionPolicy
182
196
  result.details ||= {}
183
197
  end
184
198
 
185
- def allowed_to?(rule, record = :__undef__, **options)
199
+ def allowed_to?(rule, record = :__undef__, inline_reasons: false, **options)
186
200
  res =
187
201
  if (record == :__undef__ || record == self.record) && options.empty?
188
202
  rule = resolve_rule(rule)
@@ -196,7 +210,9 @@ module ActionPolicy
196
210
  policy.result
197
211
  end
198
212
 
199
- result&.reasons&.add(policy, rule, res.details) if res.fail?
213
+ if res.fail? && result&.reasons
214
+ inline_reasons ? result.reasons.merge(res.reasons) : result.reasons.add(policy, rule, res.details)
215
+ end
200
216
 
201
217
  res.clear_details
202
218
 
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_policy/testing"
4
+
5
+ module ActionPolicy
6
+ module RSpec
7
+ # Policy rule alias matcher `be_an_alias_of`.
8
+ #
9
+ # Verifies that for given policy a policy rule has an alias.
10
+ #
11
+ # Example:
12
+ #
13
+ # # in policy specs
14
+ # subject(:policy) { described_class.new(record, user: user) }
15
+ #
16
+ # let(:user) { build_stubbed(:user) }
17
+ # let(:record) { build_stubbed(:post) }
18
+ #
19
+ # describe "#show?" do
20
+ # it "is an alias of :index? policy rule" do
21
+ # expect(:show?).to be_an_alias_of(policy, :index?)
22
+ # end
23
+ # end
24
+ #
25
+ # # negated version
26
+ # describe "#show?" do
27
+ # it "is not an alias of :index? policy rule" do
28
+ # expect(:show?).to_not be_an_alias_of(policy, :index?)
29
+ # end
30
+ # end
31
+ #
32
+ class BeAnAliasOf < ::RSpec::Matchers::BuiltIn::BaseMatcher
33
+ attr_reader :policy, :rule, :actual
34
+
35
+ def initialize(policy, rule)
36
+ @policy = policy
37
+ @rule = rule
38
+ end
39
+
40
+ def match(_expected, actual)
41
+ policy.resolve_rule(actual) == rule
42
+ end
43
+
44
+ def does_not_match?(actual)
45
+ @actual = actual
46
+ policy.resolve_rule(actual) != rule
47
+ end
48
+
49
+ def supports_block_expectations?() ; false; end
50
+
51
+ def failure_message
52
+ "expected #{policy}##{actual} " \
53
+ "to be an alias of #{policy}##{rule}"
54
+ end
55
+
56
+ def failure_message_when_negated
57
+ "expected #{policy}##{actual} " \
58
+ "to not be an alias of #{policy}##{rule}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ RSpec.configure do |config|
65
+ config.include(Module.new do
66
+ def be_an_alias_of(policy, rule)
67
+ ActionPolicy::RSpec::BeAnAliasOf.new(policy, rule)
68
+ end
69
+ end)
70
+ end
@@ -146,8 +146,8 @@ module ActionPolicy
146
146
 
147
147
  def colorize(val)
148
148
  return val unless $stdout.isatty
149
- return TRUE if val.eql?(true)
150
- return FALSE if val.eql?(false)
149
+ return TRUE if val.eql?(true) # rubocop:disable Lint/DeprecatedConstants
150
+ return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
151
151
  val
152
152
  end
153
153
  end
@@ -8,16 +8,18 @@ module ActionPolicy
8
8
  using ActionPolicy::Ext::PolicyCacheKey
9
9
 
10
10
  # Returns policy instance for the record.
11
- def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
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
+
12
14
  policy_class = with || ::ActionPolicy.lookup(
13
15
  record,
14
- namespace: namespace, context: context, allow_nil: allow_nil, default: default
16
+ namespace: namespace, context: context, allow_nil: allow_nil, default: default, strict_namespace: strict_namespace
15
17
  )
16
18
  policy_class&.new(record, **context)
17
19
  end
18
20
 
19
21
  def authorization_context
20
- raise NotImplementedError, "Please, define `authorization_context` method!"
22
+ Kernel.raise NotImplementedError, "Please, define `authorization_context` method!"
21
23
  end
22
24
 
23
25
  def authorization_namespace
@@ -28,6 +30,10 @@ module ActionPolicy
28
30
  # override to provide a policy class use when no policy found
29
31
  end
30
32
 
33
+ def authorization_strict_namespace
34
+ # override to provide strict namespace lookup option
35
+ end
36
+
31
37
  # Override this method to provide implicit authorization target
32
38
  # that would be used in case `record` is not specified in
33
39
  # `authorize!` and `allowed_to?` call.
@@ -39,7 +45,7 @@ module ActionPolicy
39
45
 
40
46
  # Return implicit authorization target or raises an exception if it's nil
41
47
  def implicit_authorization_target!
42
- implicit_authorization_target || raise(
48
+ implicit_authorization_target || Kernel.raise(
43
49
  NotFound,
44
50
  [
45
51
  self,
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPolicy
4
+ module Ext
5
+ # Add Module#namespace method
6
+ module ModuleNamespace # :nodoc: all
7
+ unless "".respond_to?(:safe_constantize)
8
+ require "action_policy/ext/string_constantize"
9
+ using ActionPolicy::Ext::StringConstantize
10
+ end
11
+
12
+ module Ext
13
+ def namespace
14
+ return unless name&.match?(/[^^]::/)
15
+
16
+ name.sub(/::[^:]+$/, "").safe_constantize
17
+ end
18
+ end
19
+
20
+ # See https://github.com/jruby/jruby/issues/5220
21
+ ::Module.include(Ext) if RUBY_PLATFORM.match?(/java/i)
22
+
23
+ refine Module do
24
+ if RUBY_VERSION <= "2.7.0"
25
+ include Ext
26
+ else
27
+ ::RubyNext::Core.import_methods(Ext, binding)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ 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
+ ::RubyNext::Core.import_methods(ObjectExt, binding)
27
+ end
28
+
29
+ refine NilClass do
30
+ def _policy_cache_key(*) = ""
31
+ end
32
+
33
+ refine TrueClass do
34
+ def _policy_cache_key(*) = "t"
35
+ end
36
+
37
+ refine FalseClass do
38
+ def _policy_cache_key(*) = "f"
39
+ end
40
+
41
+ refine String do
42
+ def _policy_cache_key(*) = self
43
+ end
44
+
45
+ refine Symbol do
46
+ def _policy_cache_key(*) = to_s
47
+ end
48
+
49
+ if RUBY_PLATFORM.match?(/java/i)
50
+ refine Integer do
51
+ def _policy_cache_key(*) = to_s
52
+ end
53
+
54
+ refine Float do
55
+ def _policy_cache_key(*) = to_s
56
+ end
57
+ else
58
+ refine Numeric do
59
+ def _policy_cache_key(*) = to_s
60
+ end
61
+ end
62
+
63
+ refine Time do
64
+ def _policy_cache_key(*) = to_s
65
+ end
66
+
67
+ refine Module do
68
+ def _policy_cache_key(*) = name
69
+ end
70
+ end
71
+ end
72
+ end
@@ -74,10 +74,8 @@ module ActionPolicy
74
74
  end
75
75
 
76
76
  def lookup_authorization_policy(record, **options) # :nodoc:
77
- record = implicit_authorization_target! if record == :__undef__
78
- raise ArgumentError, "Record must be specified" if record.nil?
79
-
80
- options[:context] && (options[:context] = authorization_context.merge(options[:context]))
77
+ record = implicit_authorization_target! if :__undef__ == record # rubocop:disable Style/YodaCondition See https://github.com/palkan/action_policy/pull/180
78
+ Kernel.raise ArgumentError, "Record must be specified" if record.nil?
81
79
 
82
80
  policy_for(record: record, **options)
83
81
  end
@@ -8,16 +8,18 @@ module ActionPolicy
8
8
  using ActionPolicy::Ext::PolicyCacheKey
9
9
 
10
10
  # Returns policy instance for the record.
11
- def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
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
+
12
14
  policy_class = with || ::ActionPolicy.lookup(
13
15
  record,
14
- namespace:, context:, allow_nil:, default:
16
+ namespace:, context:, allow_nil:, default:, strict_namespace:
15
17
  )
16
18
  policy_class&.new(record, **context)
17
19
  end
18
20
 
19
21
  def authorization_context
20
- raise NotImplementedError, "Please, define `authorization_context` method!"
22
+ Kernel.raise NotImplementedError, "Please, define `authorization_context` method!"
21
23
  end
22
24
 
23
25
  def authorization_namespace
@@ -28,6 +30,10 @@ module ActionPolicy
28
30
  # override to provide a policy class use when no policy found
29
31
  end
30
32
 
33
+ def authorization_strict_namespace
34
+ # override to provide strict namespace lookup option
35
+ end
36
+
31
37
  # Override this method to provide implicit authorization target
32
38
  # that would be used in case `record` is not specified in
33
39
  # `authorize!` and `allowed_to?` call.
@@ -39,7 +45,7 @@ module ActionPolicy
39
45
 
40
46
  # Return implicit authorization target or raises an exception if it's nil
41
47
  def implicit_authorization_target!
42
- implicit_authorization_target || raise(
48
+ implicit_authorization_target || Kernel.raise(
43
49
  NotFound,
44
50
  [
45
51
  self,
@@ -21,7 +21,11 @@ module ActionPolicy
21
21
  ::Module.include(Ext) if RUBY_PLATFORM.match?(/java/i)
22
22
 
23
23
  refine Module do
24
- include Ext
24
+ if RUBY_VERSION <= "2.7.0"
25
+ include Ext
26
+ else
27
+ import_methods Ext
28
+ end
25
29
  end
26
30
  end
27
31
  end
@@ -23,7 +23,7 @@ module ActionPolicy
23
23
  end
24
24
 
25
25
  refine Object do
26
- include ObjectExt
26
+ import_methods ObjectExt
27
27
  end
28
28
 
29
29
  refine NilClass do
@@ -67,12 +67,20 @@ module ActionPolicy
67
67
  def lookup_within_namespace(policy_name, namespace, strict: false)
68
68
  NamespaceCache.fetch(namespace&.name || "Kernel", policy_name, strict: strict) do
69
69
  mod = namespace
70
+ policy_class = nil
71
+
70
72
  loop do
71
- policy = [mod&.name, policy_name].compact.join("::").safe_constantize
72
- break policy if policy || mod.nil? || strict
73
+ policy_class = [mod&.name, policy_name].compact.join("::").safe_constantize
74
+ break policy_class if policy_class || mod.nil?
73
75
 
74
76
  mod = mod.namespace
75
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}::")
76
84
  end
77
85
  end
78
86
 
@@ -66,7 +66,7 @@ module ActionPolicy
66
66
  allow_nil ||= optional
67
67
 
68
68
  ids.each do |id|
69
- authorization_targets[id] = {allow_nil, optional}
69
+ authorization_targets[id] = {allow_nil:, optional:}
70
70
  end
71
71
 
72
72
  attr_reader(*ids)
@@ -86,7 +86,7 @@ module ActionPolicy
86
86
  @result = self.class.result_class.new(self.class, rule)
87
87
 
88
88
  catch :policy_fulfilled do
89
- result.load __apply__(rule)
89
+ result.load __apply__(resolve_rule(rule))
90
90
  end
91
91
 
92
92
  result.value
@@ -133,13 +133,13 @@ module ActionPolicy
133
133
  end
134
134
 
135
135
  # An alias for readability purposes
136
- def check?(*args) = allowed_to?(*args)
136
+ def check?(*args, **hargs) = allowed_to?(*args, **hargs)
137
137
 
138
138
  # Returns a rule name (policy method name) for activity.
139
139
  #
140
140
  # By default, rule name is equal to activity name.
141
141
  #
142
- # Raises ActionPolicy::UknownRule when rule is not found in policy.
142
+ # Raises ActionPolicy::UnknownRule when rule is not found in policy.
143
143
  def resolve_rule(activity)
144
144
  raise UnknownRule.new(self, activity) unless
145
145
  respond_to?(activity)
@@ -31,6 +31,20 @@ module ActionPolicy
31
31
 
32
32
  def present?() = !empty?
33
33
 
34
+ def merge(other)
35
+ other.reasons.each do |policy_class, rules|
36
+ reasons[policy_class] ||= []
37
+
38
+ rules.each do |rule|
39
+ if rule.is_a?(::Hash)
40
+ add_detailed_reason(reasons[policy_class], rule)
41
+ else
42
+ add_non_detailed_reason(reasons[policy_class], rule)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
34
48
  private
35
49
 
36
50
  def add_non_detailed_reason(store, rule)
@@ -182,7 +196,7 @@ module ActionPolicy
182
196
  result.details ||= {}
183
197
  end
184
198
 
185
- def allowed_to?(rule, record = :__undef__, **options)
199
+ def allowed_to?(rule, record = :__undef__, inline_reasons: false, **options)
186
200
  res =
187
201
  if (record == :__undef__ || record == self.record) && options.empty?
188
202
  rule = resolve_rule(rule)
@@ -196,7 +210,9 @@ module ActionPolicy
196
210
  policy.result
197
211
  end
198
212
 
199
- result&.reasons&.add(policy, rule, res.details) if res.fail?
213
+ if res.fail? && result&.reasons
214
+ inline_reasons ? result.reasons.merge(res.reasons) : result.reasons.add(policy, rule, res.details)
215
+ end
200
216
 
201
217
  res.clear_details
202
218
 
@@ -23,7 +23,10 @@ module ActionPolicy
23
23
  include ActionPolicy::Behaviours::Namespaced
24
24
 
25
25
  included do
26
- helper_method :allowed_to? if respond_to?(:helper_method)
26
+ if respond_to?(:helper_method)
27
+ helper_method :allowed_to?
28
+ helper_method :authorized_scope
29
+ end
27
30
 
28
31
  attr_writer :authorize_count
29
32
  attr_reader :verify_authorized_skipped
@@ -57,7 +60,7 @@ module ActionPolicy
57
60
  end
58
61
 
59
62
  def verify_authorized
60
- raise UnauthorizedAction.new(controller_path, action_name) if
63
+ Kernel.raise UnauthorizedAction.new(controller_path, action_name) if
61
64
  authorize_count.zero? && !verify_authorized_skipped
62
65
  end
63
66
 
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_policy/testing"
4
+
5
+ module ActionPolicy
6
+ module RSpec
7
+ # Policy rule alias matcher `be_an_alias_of`.
8
+ #
9
+ # Verifies that for given policy a policy rule has an alias.
10
+ #
11
+ # Example:
12
+ #
13
+ # # in policy specs
14
+ # subject(:policy) { described_class.new(record, user: user) }
15
+ #
16
+ # let(:user) { build_stubbed(:user) }
17
+ # let(:record) { build_stubbed(:post) }
18
+ #
19
+ # describe "#show?" do
20
+ # it "is an alias of :index? policy rule" do
21
+ # expect(:show?).to be_an_alias_of(policy, :index?)
22
+ # end
23
+ # end
24
+ #
25
+ # # negated version
26
+ # describe "#show?" do
27
+ # it "is not an alias of :index? policy rule" do
28
+ # expect(:show?).to_not be_an_alias_of(policy, :index?)
29
+ # end
30
+ # end
31
+ #
32
+ class BeAnAliasOf < ::RSpec::Matchers::BuiltIn::BaseMatcher
33
+ attr_reader :policy, :rule, :actual
34
+
35
+ def initialize(policy, rule)
36
+ @policy = policy
37
+ @rule = rule
38
+ end
39
+
40
+ def match(_expected, actual)
41
+ policy.resolve_rule(actual) == rule
42
+ end
43
+
44
+ def does_not_match?(actual)
45
+ @actual = actual
46
+ policy.resolve_rule(actual) != rule
47
+ end
48
+
49
+ def supports_block_expectations?() = false
50
+
51
+ def failure_message
52
+ "expected #{policy}##{actual} " \
53
+ "to be an alias of #{policy}##{rule}"
54
+ end
55
+
56
+ def failure_message_when_negated
57
+ "expected #{policy}##{actual} " \
58
+ "to not be an alias of #{policy}##{rule}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ RSpec.configure do |config|
65
+ config.include(Module.new do
66
+ def be_an_alias_of(policy, rule)
67
+ ActionPolicy::RSpec::BeAnAliasOf.new(policy, rule)
68
+ end
69
+ end)
70
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "action_policy/rspec/be_an_alias_of"
3
4
  require "action_policy/rspec/be_authorized_to"
4
5
  require "action_policy/rspec/have_authorized_scope"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionPolicy
4
- VERSION = "0.5.6"
4
+ VERSION = "0.6.1"
5
5
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_policy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.6
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-03 00:00:00.000000000 Z
11
+ date: 2022-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: ruby-next
14
+ name: ruby-next-core
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.11.0
19
+ version: 0.14.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.11.0
26
+ version: 0.14.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: ammeter
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -133,9 +133,6 @@ files:
133
133
  - LICENSE.txt
134
134
  - README.md
135
135
  - config/rubocop-rspec.yml
136
- - lib/.rbnext/1995.next/action_policy/behaviours/policy_for.rb
137
- - lib/.rbnext/1995.next/action_policy/behaviours/scoping.rb
138
- - lib/.rbnext/1995.next/action_policy/policy/authorization.rb
139
136
  - lib/.rbnext/1995.next/action_policy/utils/pretty_print.rb
140
137
  - lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb
141
138
  - lib/.rbnext/2.7/action_policy/i18n.rb
@@ -153,10 +150,16 @@ files:
153
150
  - lib/.rbnext/3.0/action_policy/policy/execution_result.rb
154
151
  - lib/.rbnext/3.0/action_policy/policy/pre_check.rb
155
152
  - lib/.rbnext/3.0/action_policy/policy/reasons.rb
153
+ - lib/.rbnext/3.0/action_policy/rspec/be_an_alias_of.rb
156
154
  - lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb
157
155
  - lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb
158
156
  - lib/.rbnext/3.0/action_policy/utils/pretty_print.rb
159
157
  - lib/.rbnext/3.0/action_policy/utils/suggest_message.rb
158
+ - lib/.rbnext/3.1/action_policy/behaviours/policy_for.rb
159
+ - lib/.rbnext/3.1/action_policy/behaviours/scoping.rb
160
+ - lib/.rbnext/3.1/action_policy/ext/module_namespace.rb
161
+ - lib/.rbnext/3.1/action_policy/ext/policy_cache_key.rb
162
+ - lib/.rbnext/3.1/action_policy/policy/authorization.rb
160
163
  - lib/action_policy.rb
161
164
  - lib/action_policy/authorizer.rb
162
165
  - lib/action_policy/base.rb
@@ -194,6 +197,7 @@ files:
194
197
  - lib/action_policy/rails/scope_matchers/active_record.rb
195
198
  - lib/action_policy/railtie.rb
196
199
  - lib/action_policy/rspec.rb
200
+ - lib/action_policy/rspec/be_an_alias_of.rb
197
201
  - lib/action_policy/rspec/be_authorized_to.rb
198
202
  - lib/action_policy/rspec/dsl.rb
199
203
  - lib/action_policy/rspec/have_authorized_scope.rb
@@ -222,7 +226,7 @@ metadata:
222
226
  documentation_uri: https://actionpolicy.evilmartians.io/
223
227
  homepage_uri: https://actionpolicy.evilmartians.io/
224
228
  source_code_uri: http://github.com/palkan/action_policy
225
- post_install_message:
229
+ post_install_message:
226
230
  rdoc_options: []
227
231
  require_paths:
228
232
  - lib
@@ -230,15 +234,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
230
234
  requirements:
231
235
  - - ">="
232
236
  - !ruby/object:Gem::Version
233
- version: 2.5.0
237
+ version: 2.6.0
234
238
  required_rubygems_version: !ruby/object:Gem::Requirement
235
239
  requirements:
236
240
  - - ">="
237
241
  - !ruby/object:Gem::Version
238
242
  version: '0'
239
243
  requirements: []
240
- rubygems_version: 3.0.6
241
- signing_key:
244
+ rubygems_version: 3.3.7
245
+ signing_key:
242
246
  specification_version: 4
243
247
  summary: Authorization framework for Ruby/Rails application
244
248
  test_files: []