action_policy 0.7.3 → 0.7.5

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb +1 -1
  4. data/lib/.rbnext/3.0/action_policy/policy/cache.rb +1 -1
  5. data/lib/.rbnext/3.0/action_policy/policy/execution_result.rb +1 -1
  6. data/lib/.rbnext/3.0/action_policy/policy/pre_check.rb +3 -5
  7. data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +2 -0
  8. data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +2 -2
  9. data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +2 -2
  10. data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +1 -1
  11. data/lib/.rbnext/3.1/action_policy/behaviours/policy_for.rb +1 -1
  12. data/lib/.rbnext/3.2/action_policy/behaviours/policy_for.rb +1 -1
  13. data/lib/.rbnext/3.2/action_policy/rspec/be_authorized_to.rb +2 -2
  14. data/lib/.rbnext/3.2/action_policy/rspec/have_authorized_scope.rb +2 -2
  15. data/lib/.rbnext/{2.7 → 3.4}/action_policy/behaviours/policy_for.rb +4 -4
  16. data/lib/.rbnext/{2.7 → 3.4}/action_policy/i18n.rb +1 -1
  17. data/lib/.rbnext/{2.7 → 3.4}/action_policy/policy/cache.rb +3 -3
  18. data/lib/.rbnext/{2.7 → 3.4}/action_policy/policy/pre_check.rb +4 -6
  19. data/lib/.rbnext/{2.7 → 3.4}/action_policy/rspec/be_authorized_to.rb +6 -6
  20. data/lib/.rbnext/{2.7 → 3.4}/action_policy/rspec/have_authorized_scope.rb +5 -5
  21. data/lib/.rbnext/{2.7 → 3.4}/action_policy/utils/pretty_print.rb +4 -4
  22. data/lib/action_policy/behaviour.rb +6 -4
  23. data/lib/action_policy/behaviours/policy_for.rb +1 -1
  24. data/lib/action_policy/i18n.rb +1 -1
  25. data/lib/action_policy/policy/cache.rb +1 -1
  26. data/lib/action_policy/policy/execution_result.rb +1 -1
  27. data/lib/action_policy/policy/pre_check.rb +3 -5
  28. data/lib/action_policy/policy/reasons.rb +2 -0
  29. data/lib/action_policy/rails/controller.rb +4 -1
  30. data/lib/action_policy/rspec/be_authorized_to.rb +2 -2
  31. data/lib/action_policy/rspec/have_authorized_scope.rb +2 -2
  32. data/lib/action_policy/utils/pretty_print.rb +1 -1
  33. data/lib/action_policy/version.rb +1 -1
  34. data/lib/ruby_lsp/action_policy/addon.rb +170 -0
  35. metadata +15 -14
  36. /data/lib/.rbnext/{2.7 → 3.0}/action_policy/rails/scope_matchers/action_controller_params.rb +0 -0
  37. /data/lib/.rbnext/{2.7 → 3.0}/action_policy/rails/scope_matchers/active_record.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21363e4337fbe0caea24309750d79d94bb9fa6c4ae0a9d0697b0e686519fa763
4
- data.tar.gz: 8ac572534b621240640723ea5c6ec942a73b91e14f2d40e534889c2dc7ba9251
3
+ metadata.gz: d3680162e91000d7fb369fcfa17710f557c9a12435c5bf00c6fb367a5045b970
4
+ data.tar.gz: 000760f640032e5a0b44e9ea7918908a32c2568cefeffc747fa4ea45c46949bf
5
5
  SHA512:
6
- metadata.gz: c1dd33d739cfdf1143b7cc2402c2e8bf7ebf1f1613fa7c650c72fddf0bd4fd4b5c924c920ff143ad582ab68524836f0d0c32641372c5999fdd95f6d9113078dc
7
- data.tar.gz: 5fb9dbd9b1f19fb6bed19ddbeb99be9bd35495b2d0f89bd3fb0cd8fed59a0513c4a627fdd25291b56dcfc7cf9b60f64d351bb2b658d97351f9a7b5db2d1c4128
6
+ metadata.gz: f03892d33e150921d3ec4bdfe1038b017301825474597037585c45e0f780affdfd72ed5171c577cee007af69a8542d4172cb8266526a97895b9c3461b4b2abb3
7
+ data.tar.gz: e5d9a4259d5dcd8cac8a0cab28b6d85f3aedc46050c51057aa9d458b4c0b8597428bed1a6c57473f3836500b1696cfb1f4a4c1aeac868c2771bb443dc59f10fa
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.7.5 (2025-05-09) 🎇
6
+
7
+ - Ensure `result.value` is true or false. ([@palkan][])
8
+
9
+ ## 0.7.4 (2025-03-12)
10
+
11
+ - Let authorize! return the policy record. ([@sedubois][])
12
+
13
+ - Enable `allowance_to` as a helper method by default. ([@stephannv][])
14
+
15
+ - Allow the `:through` option of `authorize` to be passed a Proc. ([@brendon][])
16
+
5
17
  ## 0.7.3 (2024-12-18)
6
18
 
7
19
  - Fix keeping the result object in concurrent (Fiber-ed) execution environments. ([@palkan][])
@@ -539,3 +551,5 @@ This value is now stored in a cache (if any) instead of just the call result (`t
539
551
  [@matsales28]: https://github.com/matsales28
540
552
  [@killondark]: https://github.com/killondark
541
553
  [@Spone]: https://github.com/Spone
554
+ [@stephannv]: https://github.com/stephannv
555
+ [@sedubois]: https://github.com/sedubois
@@ -61,7 +61,7 @@ module ActionPolicy
61
61
 
62
62
  def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **__kwrest__)
63
63
  record_key = record._policy_cache_key(use_object_id: true)
64
- context_key = context.values.map { _1._policy_cache_key(use_object_id: true) }.join(".")
64
+ context_key = context.values.map { it = _1;it._policy_cache_key(use_object_id: true) }.join(".")
65
65
 
66
66
  "#{namespace}/#{with}/#{context_key}/#{record_key}"
67
67
  end
@@ -29,7 +29,7 @@ module ActionPolicy # :nodoc:
29
29
  [
30
30
  cache_namespace,
31
31
  *parts
32
- ].map { _1._policy_cache_key }.join("/")
32
+ ].map { it = _1;it._policy_cache_key }.join("/")
33
33
  end
34
34
 
35
35
  def rule_cache_key(rule)
@@ -16,7 +16,7 @@ module ActionPolicy
16
16
 
17
17
  # Populate the final value
18
18
  def load(value)
19
- @value = value
19
+ @value = !!value
20
20
  end
21
21
 
22
22
  def success?() ; @value == true; end
@@ -78,8 +78,6 @@ module ActionPolicy
78
78
 
79
79
  rebuild_filter
80
80
  end
81
- # rubocop: enable
82
- # rubocop: enable
83
81
 
84
82
  def dup
85
83
  self.class.new(
@@ -127,7 +125,7 @@ module ActionPolicy
127
125
  def pre_check(*names, **options)
128
126
  names.each do |name|
129
127
  # do not allow pre-check override
130
- check = pre_checks.find { _1.name == name }
128
+ check = pre_checks.find { it = _1;it.name == name }
131
129
  raise "Pre-check already defined: #{name}" unless check.nil?
132
130
 
133
131
  pre_checks << Check.new(self, name, **options)
@@ -136,14 +134,14 @@ module ActionPolicy
136
134
 
137
135
  def skip_pre_check(*names, **options)
138
136
  names.each do |name|
139
- check = pre_checks.find { _1.name == name }
137
+ check = pre_checks.find { it = _1;it.name == name }
140
138
  raise "Pre-check not found: #{name}" if check.nil?
141
139
 
142
140
  # when no options provided we remove this check completely
143
141
  next pre_checks.delete(check) if options.empty?
144
142
 
145
143
  # otherwise duplicate and apply skip options
146
- pre_checks[pre_checks.index(check)] = check.dup.tap { _1.skip!(**options) }
144
+ pre_checks[pre_checks.index(check)] = check.dup.tap { it = _1;it.skip!(**options) }
147
145
  end
148
146
  end
149
147
 
@@ -27,6 +27,8 @@ module ActionPolicy
27
27
  # { policy_identifier => [rules, ...] }
28
28
  def details() ; reasons.transform_keys(&:identifier); end
29
29
 
30
+ alias_method :to_h, :details
31
+
30
32
  def empty?() ; reasons.empty?; end
31
33
 
32
34
  def present?() ; !empty?; end
@@ -51,7 +51,7 @@ module ActionPolicy
51
51
 
52
52
  @actual_calls = ActionPolicy::Testing::AuthorizeTracker.calls
53
53
 
54
- actual_calls.any? { _1.matches?(policy, rule, target, context) }
54
+ actual_calls.any? { it = _1;it.matches?(policy, rule, target, context) }
55
55
  end
56
56
 
57
57
  def does_not_match?(*__rest__)
@@ -78,7 +78,7 @@ module ActionPolicy
78
78
 
79
79
  def formatted_calls
80
80
  actual_calls.map do
81
- " - #{_1.inspect}"
81
+ it = _1;" - #{it.inspect}"
82
82
  end.join("\n")
83
83
  end
84
84
 
@@ -61,7 +61,7 @@ module ActionPolicy
61
61
 
62
62
  @actual_scopes = ActionPolicy::Testing::AuthorizeTracker.scopings
63
63
 
64
- matching_scopes = actual_scopes.select { _1.matches?(policy, type, name, scope_options, context) }
64
+ matching_scopes = actual_scopes.select { it = _1;it.matches?(policy, type, name, scope_options, context) }
65
65
 
66
66
  return false if matching_scopes.empty?
67
67
 
@@ -114,7 +114,7 @@ module ActionPolicy
114
114
 
115
115
  def formatted_scopings
116
116
  actual_scopes.map do
117
- " - #{_1.inspect}"
117
+ it = _1;" - #{it.inspect}"
118
118
  end.join("\n")
119
119
  end
120
120
  end
@@ -119,7 +119,7 @@ module ActionPolicy
119
119
 
120
120
  # Some lines should not be evaled
121
121
  def ignore_exp?(exp)
122
- PrettyPrint.ignore_expressions.any? { exp.match?(_1) }
122
+ PrettyPrint.ignore_expressions.any? { it = _1;exp.match?(it) }
123
123
  end
124
124
  end
125
125
 
@@ -61,7 +61,7 @@ module ActionPolicy
61
61
 
62
62
  def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **__kwrest__)
63
63
  record_key = record._policy_cache_key(use_object_id: true)
64
- context_key = context.values.map { _1._policy_cache_key(use_object_id: true) }.join(".")
64
+ context_key = context.values.map { it = _1;it._policy_cache_key(use_object_id: true) }.join(".")
65
65
 
66
66
  "#{namespace}/#{with}/#{context_key}/#{record_key}"
67
67
  end
@@ -61,7 +61,7 @@ module ActionPolicy
61
61
 
62
62
  def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **__kwrest__)
63
63
  record_key = record._policy_cache_key(use_object_id: true)
64
- context_key = context.values.map { _1._policy_cache_key(use_object_id: true) }.join(".")
64
+ context_key = context.values.map { it = _1;it._policy_cache_key(use_object_id: true) }.join(".")
65
65
 
66
66
  "#{namespace}/#{with}/#{context_key}/#{record_key}"
67
67
  end
@@ -51,7 +51,7 @@ module ActionPolicy
51
51
 
52
52
  @actual_calls = ActionPolicy::Testing::AuthorizeTracker.calls
53
53
 
54
- actual_calls.any? { _1.matches?(policy, rule, target, context) }
54
+ actual_calls.any? { it = _1;it.matches?(policy, rule, target, context) }
55
55
  end
56
56
 
57
57
  def does_not_match?(*__rest__)
@@ -78,7 +78,7 @@ module ActionPolicy
78
78
 
79
79
  def formatted_calls
80
80
  actual_calls.map do
81
- " - #{_1.inspect}"
81
+ it = _1;" - #{it.inspect}"
82
82
  end.join("\n")
83
83
  end
84
84
 
@@ -61,7 +61,7 @@ module ActionPolicy
61
61
 
62
62
  @actual_scopes = ActionPolicy::Testing::AuthorizeTracker.scopings
63
63
 
64
- matching_scopes = actual_scopes.select { _1.matches?(policy, type, name, scope_options, context) }
64
+ matching_scopes = actual_scopes.select { it = _1;it.matches?(policy, type, name, scope_options, context) }
65
65
 
66
66
  return false if matching_scopes.empty?
67
67
 
@@ -114,7 +114,7 @@ module ActionPolicy
114
114
 
115
115
  def formatted_scopings
116
116
  actual_scopes.map do
117
- " - #{_1.inspect}"
117
+ it = _1;" - #{it.inspect}"
118
118
  end.join("\n")
119
119
  end
120
120
  end
@@ -13,12 +13,12 @@ module ActionPolicy
13
13
 
14
14
  policy_class = with || ::ActionPolicy.lookup(
15
15
  record,
16
- namespace: namespace, context: context, allow_nil: allow_nil, default: default, strict_namespace: strict_namespace
16
+ namespace:, context:, allow_nil:, default:, strict_namespace:
17
17
  )
18
18
  policy_class&.new(record, **context)
19
19
  end
20
20
 
21
- def authorization_context ; @authorization_context ||= build_authorization_context; end
21
+ def authorization_context = @authorization_context ||= build_authorization_context
22
22
 
23
23
  def build_authorization_context
24
24
  Kernel.raise NotImplementedError, "Please, define `build_authorization_context` method!"
@@ -59,9 +59,9 @@ module ActionPolicy
59
59
  )
60
60
  end
61
61
 
62
- def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **__kwrest__)
62
+ def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **)
63
63
  record_key = record._policy_cache_key(use_object_id: true)
64
- context_key = context.values.map { |_1| _1._policy_cache_key(use_object_id: true) }.join(".")
64
+ context_key = context.values.map { it = _1;it._policy_cache_key(use_object_id: true) }.join(".")
65
65
 
66
66
  "#{namespace}/#{with}/#{context_key}/#{record_key}"
67
67
  end
@@ -21,7 +21,7 @@ module ActionPolicy
21
21
  private
22
22
 
23
23
  def candidates_for(policy_class, rule)
24
- policy_hierarchy = policy_class.ancestors.select { |_1| _1.respond_to?(:identifier) }
24
+ policy_hierarchy = policy_class.ancestors.select { it = _1;it.respond_to?(:identifier) }
25
25
  [
26
26
  *policy_hierarchy.map { |klass| :"policy.#{klass.identifier}.#{rule}" },
27
27
  :"policy.#{rule}",
@@ -23,13 +23,13 @@ module ActionPolicy # :nodoc:
23
23
  end
24
24
  end
25
25
 
26
- def cache_namespace() ; ActionPolicy::CACHE_NAMESPACE; end
26
+ def cache_namespace() = ActionPolicy::CACHE_NAMESPACE
27
27
 
28
28
  def cache_key(*parts)
29
29
  [
30
30
  cache_namespace,
31
31
  *parts
32
- ].map { |_1| _1._policy_cache_key }.join("/")
32
+ ].map { it = _1;it._policy_cache_key }.join("/")
33
33
  end
34
34
 
35
35
  def rule_cache_key(rule)
@@ -42,7 +42,7 @@ module ActionPolicy # :nodoc:
42
42
  end
43
43
 
44
44
  def context_cache_key
45
- authorization_context.map { |_1, _2| _2._policy_cache_key.to_s }.join("/")
45
+ authorization_context.map { _2._policy_cache_key.to_s }.join("/")
46
46
  end
47
47
 
48
48
  def apply_with_cache(rule)
@@ -52,7 +52,7 @@ module ActionPolicy
52
52
  filter.call(rule)
53
53
  end
54
54
 
55
- def call(policy) ; policy.send(name); end
55
+ def call(policy) = policy.send(name)
56
56
 
57
57
  def skip!(except: nil, only: nil)
58
58
  if !except.nil? && !only.nil?
@@ -78,8 +78,6 @@ module ActionPolicy
78
78
 
79
79
  rebuild_filter
80
80
  end
81
- # rubocop: enable
82
- # rubocop: enable
83
81
 
84
82
  def dup
85
83
  self.class.new(
@@ -127,7 +125,7 @@ module ActionPolicy
127
125
  def pre_check(*names, **options)
128
126
  names.each do |name|
129
127
  # do not allow pre-check override
130
- check = pre_checks.find { |_1| _1.name == name }
128
+ check = pre_checks.find { it = _1;it.name == name }
131
129
  raise "Pre-check already defined: #{name}" unless check.nil?
132
130
 
133
131
  pre_checks << Check.new(self, name, **options)
@@ -136,14 +134,14 @@ module ActionPolicy
136
134
 
137
135
  def skip_pre_check(*names, **options)
138
136
  names.each do |name|
139
- check = pre_checks.find { |_1| _1.name == name }
137
+ check = pre_checks.find { it = _1;it.name == name }
140
138
  raise "Pre-check not found: #{name}" if check.nil?
141
139
 
142
140
  # when no options provided we remove this check completely
143
141
  next pre_checks.delete(check) if options.empty?
144
142
 
145
143
  # otherwise duplicate and apply skip options
146
- pre_checks[pre_checks.index(check)] = check.dup.tap { |_1| _1.skip!(**options) }
144
+ pre_checks[pre_checks.index(check)] = check.dup.tap { it = _1;it.skip!(**options) }
147
145
  end
148
146
  end
149
147
 
@@ -51,14 +51,14 @@ module ActionPolicy
51
51
 
52
52
  @actual_calls = ActionPolicy::Testing::AuthorizeTracker.calls
53
53
 
54
- actual_calls.any? { |_1| _1.matches?(policy, rule, target, context) }
54
+ actual_calls.any? { it = _1;it.matches?(policy, rule, target, context) }
55
55
  end
56
56
 
57
- def does_not_match?(*__rest__)
57
+ def does_not_match?(*)
58
58
  raise "This matcher doesn't support negation"
59
59
  end
60
60
 
61
- def supports_block_expectations?() ; true; end
61
+ def supports_block_expectations?() = true
62
62
 
63
63
  def failure_message
64
64
  "expected #{formatted_record} " \
@@ -77,12 +77,12 @@ module ActionPolicy
77
77
  end
78
78
 
79
79
  def formatted_calls
80
- actual_calls.map do |_1|
81
- " - #{_1.inspect}"
80
+ actual_calls.map do
81
+ it = _1;" - #{it.inspect}"
82
82
  end.join("\n")
83
83
  end
84
84
 
85
- def formatted_record(record = target) ; ::RSpec::Support::ObjectFormatter.format(record); end
85
+ def formatted_record(record = target) = ::RSpec::Support::ObjectFormatter.format(record)
86
86
  end
87
87
  end
88
88
  end
@@ -61,7 +61,7 @@ module ActionPolicy
61
61
 
62
62
  @actual_scopes = ActionPolicy::Testing::AuthorizeTracker.scopings
63
63
 
64
- matching_scopes = actual_scopes.select { |_1| _1.matches?(policy, type, name, scope_options, context) }
64
+ matching_scopes = actual_scopes.select { it = _1;it.matches?(policy, type, name, scope_options, context) }
65
65
 
66
66
  return false if matching_scopes.empty?
67
67
 
@@ -76,11 +76,11 @@ module ActionPolicy
76
76
  true
77
77
  end
78
78
 
79
- def does_not_match?(*__rest__)
79
+ def does_not_match?(*)
80
80
  raise "This matcher doesn't support negation"
81
81
  end
82
82
 
83
- def supports_block_expectations?() ; true; end
83
+ def supports_block_expectations?() = true
84
84
 
85
85
  def failure_message
86
86
  "expected a scoping named :#{name} for type :#{type} " \
@@ -113,8 +113,8 @@ module ActionPolicy
113
113
  end
114
114
 
115
115
  def formatted_scopings
116
- actual_scopes.map do |_1|
117
- " - #{_1.inspect}"
116
+ actual_scopes.map do
117
+ it = _1;" - #{it.inspect}"
118
118
  end.join("\n")
119
119
  end
120
120
  end
@@ -119,7 +119,7 @@ module ActionPolicy
119
119
 
120
120
  # Some lines should not be evaled
121
121
  def ignore_exp?(exp)
122
- PrettyPrint.ignore_expressions.any? { |_1| exp.match?(_1) }
122
+ PrettyPrint.ignore_expressions.any? { it = _1;exp.match?(it) }
123
123
  end
124
124
  end
125
125
 
@@ -127,7 +127,7 @@ module ActionPolicy
127
127
  attr_accessor :ignore_expressions
128
128
 
129
129
  if defined?(::Prism) && defined?(::MethodSource)
130
- def available?() ; true; end
130
+ def available?() = true
131
131
 
132
132
  def print_method(object, method_name)
133
133
  ast = Prism.parse(object.method(method_name).source)
@@ -135,9 +135,9 @@ module ActionPolicy
135
135
  Visitor.new(object).collect(ast)
136
136
  end
137
137
  else
138
- def available?() ; false; end
138
+ def available?() = false
139
139
 
140
- def print_method(_, _) ; ""; end
140
+ def print_method(_, _) = ""
141
141
  end
142
142
 
143
143
  def colorize(val)
@@ -33,11 +33,13 @@ module ActionPolicy
33
33
  # Policy is inferred from record
34
34
  # (unless explicitly specified through `with` option).
35
35
  #
36
+ # @return the policy record
36
37
  # Raises `ActionPolicy::Unauthorized` if check failed.
37
38
  def authorize!(record = :__undef__, to:, **options)
38
39
  policy = lookup_authorization_policy(record, **options)
39
40
 
40
41
  Authorizer.call(policy, authorization_rule_for(policy, to))
42
+ policy.record
41
43
  end
42
44
 
43
45
  # Checks that an activity is allowed for the current context (e.g. user).
@@ -62,8 +64,8 @@ module ActionPolicy
62
64
 
63
65
  private def build_authorization_context
64
66
  self.class.authorization_targets
65
- .each_with_object({}) do |(key, meth), obj|
66
- obj[key] = send(meth)
67
+ .each_with_object({}) do |(key, method_or_proc), obj|
68
+ obj[key] = method_or_proc.is_a?(Proc) ? method_or_proc.call : send(method_or_proc)
67
69
  end
68
70
  end
69
71
 
@@ -103,8 +105,8 @@ module ActionPolicy
103
105
  # authorize :user
104
106
  # end
105
107
  def authorize(key, through: nil)
106
- meth = through || key
107
- authorization_targets[key] = meth
108
+ method_or_proc = through || key
109
+ authorization_targets[key] = method_or_proc
108
110
  end
109
111
 
110
112
  def authorization_targets
@@ -61,7 +61,7 @@ module ActionPolicy
61
61
 
62
62
  def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **)
63
63
  record_key = record._policy_cache_key(use_object_id: true)
64
- context_key = context.values.map { _1._policy_cache_key(use_object_id: true) }.join(".")
64
+ context_key = context.values.map { it._policy_cache_key(use_object_id: true) }.join(".")
65
65
 
66
66
  "#{namespace}/#{with}/#{context_key}/#{record_key}"
67
67
  end
@@ -21,7 +21,7 @@ module ActionPolicy
21
21
  private
22
22
 
23
23
  def candidates_for(policy_class, rule)
24
- policy_hierarchy = policy_class.ancestors.select { _1.respond_to?(:identifier) }
24
+ policy_hierarchy = policy_class.ancestors.select { it.respond_to?(:identifier) }
25
25
  [
26
26
  *policy_hierarchy.map { |klass| :"policy.#{klass.identifier}.#{rule}" },
27
27
  :"policy.#{rule}",
@@ -29,7 +29,7 @@ module ActionPolicy # :nodoc:
29
29
  [
30
30
  cache_namespace,
31
31
  *parts
32
- ].map { _1._policy_cache_key }.join("/")
32
+ ].map { it._policy_cache_key }.join("/")
33
33
  end
34
34
 
35
35
  def rule_cache_key(rule)
@@ -16,7 +16,7 @@ module ActionPolicy
16
16
 
17
17
  # Populate the final value
18
18
  def load(value)
19
- @value = value
19
+ @value = !!value
20
20
  end
21
21
 
22
22
  def success?() = @value == true
@@ -78,8 +78,6 @@ module ActionPolicy
78
78
 
79
79
  rebuild_filter
80
80
  end
81
- # rubocop: enable
82
- # rubocop: enable
83
81
 
84
82
  def dup
85
83
  self.class.new(
@@ -127,7 +125,7 @@ module ActionPolicy
127
125
  def pre_check(*names, **options)
128
126
  names.each do |name|
129
127
  # do not allow pre-check override
130
- check = pre_checks.find { _1.name == name }
128
+ check = pre_checks.find { it.name == name }
131
129
  raise "Pre-check already defined: #{name}" unless check.nil?
132
130
 
133
131
  pre_checks << Check.new(self, name, **options)
@@ -136,14 +134,14 @@ module ActionPolicy
136
134
 
137
135
  def skip_pre_check(*names, **options)
138
136
  names.each do |name|
139
- check = pre_checks.find { _1.name == name }
137
+ check = pre_checks.find { it.name == name }
140
138
  raise "Pre-check not found: #{name}" if check.nil?
141
139
 
142
140
  # when no options provided we remove this check completely
143
141
  next pre_checks.delete(check) if options.empty?
144
142
 
145
143
  # otherwise duplicate and apply skip options
146
- pre_checks[pre_checks.index(check)] = check.dup.tap { _1.skip!(**options) }
144
+ pre_checks[pre_checks.index(check)] = check.dup.tap { it.skip!(**options) }
147
145
  end
148
146
  end
149
147
 
@@ -27,6 +27,8 @@ module ActionPolicy
27
27
  # { policy_identifier => [rules, ...] }
28
28
  def details() = reasons.transform_keys(&:identifier)
29
29
 
30
+ alias_method :to_h, :details
31
+
30
32
  def empty?() = reasons.empty?
31
33
 
32
34
  def present?() = !empty?
@@ -26,6 +26,7 @@ module ActionPolicy
26
26
  if respond_to?(:helper_method)
27
27
  helper_method :allowed_to?
28
28
  helper_method :authorized_scope
29
+ helper_method :allowance_to
29
30
  end
30
31
 
31
32
  attr_writer :authorize_count
@@ -44,13 +45,15 @@ module ActionPolicy
44
45
  # If record is not provided, tries to infer the resource class
45
46
  # from controller name (i.e. `controller_name.classify.safe_constantize`).
46
47
  #
48
+ # @return the policy record
47
49
  # Raises `ActionPolicy::Unauthorized` if check failed.
48
50
  def authorize!(record = :__undef__, to: nil, **options)
49
51
  to ||= :"#{action_name}?"
50
52
 
51
- super
53
+ policy_record = super
52
54
 
53
55
  self.authorize_count += 1
56
+ policy_record
54
57
  end
55
58
 
56
59
  # Tries to infer the resource class from controller name
@@ -51,7 +51,7 @@ module ActionPolicy
51
51
 
52
52
  @actual_calls = ActionPolicy::Testing::AuthorizeTracker.calls
53
53
 
54
- actual_calls.any? { _1.matches?(policy, rule, target, context) }
54
+ actual_calls.any? { it.matches?(policy, rule, target, context) }
55
55
  end
56
56
 
57
57
  def does_not_match?(*)
@@ -78,7 +78,7 @@ module ActionPolicy
78
78
 
79
79
  def formatted_calls
80
80
  actual_calls.map do
81
- " - #{_1.inspect}"
81
+ " - #{it.inspect}"
82
82
  end.join("\n")
83
83
  end
84
84
 
@@ -61,7 +61,7 @@ module ActionPolicy
61
61
 
62
62
  @actual_scopes = ActionPolicy::Testing::AuthorizeTracker.scopings
63
63
 
64
- matching_scopes = actual_scopes.select { _1.matches?(policy, type, name, scope_options, context) }
64
+ matching_scopes = actual_scopes.select { it.matches?(policy, type, name, scope_options, context) }
65
65
 
66
66
  return false if matching_scopes.empty?
67
67
 
@@ -114,7 +114,7 @@ module ActionPolicy
114
114
 
115
115
  def formatted_scopings
116
116
  actual_scopes.map do
117
- " - #{_1.inspect}"
117
+ " - #{it.inspect}"
118
118
  end.join("\n")
119
119
  end
120
120
  end
@@ -119,7 +119,7 @@ module ActionPolicy
119
119
 
120
120
  # Some lines should not be evaled
121
121
  def ignore_exp?(exp)
122
- PrettyPrint.ignore_expressions.any? { exp.match?(_1) }
122
+ PrettyPrint.ignore_expressions.any? { exp.match?(it) }
123
123
  end
124
124
  end
125
125
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionPolicy
4
- VERSION = "0.7.3"
4
+ VERSION = "0.7.5"
5
5
  end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ # From https://gist.github.com/skryukov/35539d57b51f38235faaace2c1a2c1a1
4
+
5
+ require "active_support/inflector"
6
+
7
+ module RubyLsp
8
+ module ActionPolicy
9
+ class Addon < ::RubyLsp::Addon
10
+ def name
11
+ "ActionPolicy"
12
+ end
13
+
14
+ def activate(global_state, outgoing_queue)
15
+ require "action_policy"
16
+ warn "[ActionPolicy] Activating Ruby LSP addon v#{::ActionPolicy::VERSION}"
17
+ end
18
+
19
+ def deactivate
20
+ end
21
+
22
+ def create_definition_listener(response_builder, node_context, uri, dispatcher)
23
+ Definition.new(response_builder, node_context, uri, dispatcher)
24
+ end
25
+ end
26
+
27
+ class Definition
28
+ include Requests::Support::Common
29
+ include ActiveSupport::Inflector
30
+
31
+ POLICY_SUPERCLASSES = ["ApplicationPolicy", "ActionPolicy::Base"].freeze
32
+
33
+ def initialize(response_builder, uri, node_context, dispatcher)
34
+ @response_builder = response_builder
35
+ @node_context = node_context
36
+ @uri = uri
37
+ @path = uri.to_standardized_path
38
+ @policy_rules_cache = {}
39
+
40
+ dispatcher.register(self, :on_symbol_node_enter)
41
+ end
42
+
43
+ def on_symbol_node_enter(node)
44
+ return unless in_authorize_call?
45
+
46
+ target = @node_context.call_node
47
+ # authorization target is the first argument (if explicit)
48
+ policy_class = find_policy_class(target.arguments&.child_node&.first)
49
+ return unless policy_class
50
+
51
+ policy_path = find_policy_file(policy_class)
52
+ return unless policy_path
53
+
54
+ ensure_policy_rules_cached(policy_path)
55
+ add_definition(policy_path, node.value)
56
+ end
57
+
58
+ private
59
+
60
+ def in_authorize_call?
61
+ call = @node_context.call_node
62
+ call.is_a?(Prism::CallNode) && call.message == "authorize!"
63
+ end
64
+
65
+ def find_policy_class(target)
66
+ content = File.read(@path)
67
+ document = Prism.parse(content)
68
+ class_node = find_containing_class(document.value)
69
+ return derive_policy_from_target(target) unless class_node
70
+
71
+ class_name = class_node.constant_path.slice
72
+ return unless class_name.end_with?("Controller", "Channel")
73
+
74
+ resource_name = class_name
75
+ .delete_suffix("Controller")
76
+ .delete_suffix("Channel")
77
+ .singularize
78
+ "#{resource_name}Policy"
79
+ end
80
+
81
+ def find_containing_class(root)
82
+ return unless root.respond_to?(:statements)
83
+
84
+ root.statements.body.find do |node|
85
+ node.is_a?(Prism::ClassNode) &&
86
+ node.constant_path.slice.end_with?("Controller", "Channel")
87
+ end
88
+ end
89
+
90
+ def derive_policy_from_target(target)
91
+ target_name = case target
92
+ when Prism::InstanceVariableReadNode
93
+ target.name.to_s[1..].classify
94
+ when Prism::ConstantReadNode
95
+ target.name.to_s
96
+ when NilClass
97
+ return
98
+ else
99
+ target.slice
100
+ end
101
+
102
+ target_name.end_with?("Policy") ? target_name : "#{target_name}Policy"
103
+ end
104
+
105
+ def find_policy_file(policy_class)
106
+ file_path = policy_class.gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase
107
+ root_path = Dir.pwd
108
+
109
+ [
110
+ File.join(root_path, "app/policies/#{file_path}.rb"),
111
+ File.join(root_path, "app/policies/#{file_path}_policy.rb"),
112
+ *Dir.glob(File.join(root_path, "app/policies/**/#{file_path}.rb")),
113
+ *Dir.glob(File.join(root_path, "app/policies/**/#{file_path}_policy.rb"))
114
+ ].find { |path| File.exist?(path) }
115
+ end
116
+
117
+ def ensure_policy_rules_cached(policy_path)
118
+ return if @policy_rules_cache[policy_path]
119
+
120
+ content = File.read(policy_path)
121
+ document = Prism.parse(content)
122
+
123
+ document.value.statements.body.each do |stmt|
124
+ if stmt.is_a?(Prism::ClassNode)
125
+ @policy_rules_cache[policy_path] = extract_rules(stmt)
126
+ break
127
+ end
128
+ end
129
+ end
130
+
131
+ def extract_rules(node)
132
+ return {} unless node.body
133
+
134
+ rules = {}
135
+ private_section = false
136
+
137
+ node.body.child_nodes.each do |stmt|
138
+ case stmt
139
+ when Prism::CallNode
140
+ if stmt.message == "private"
141
+ private_section = true
142
+ elsif stmt.message == "alias_rule" && stmt.arguments&.arguments
143
+ stmt.arguments.arguments
144
+ .select { |arg| arg.is_a?(Prism::SymbolNode) }
145
+ .each { |arg| rules[arg.value] ||= stmt.location.start_line }
146
+ end
147
+ when Prism::DefNode
148
+ next if private_section
149
+ rules[stmt.name.to_s] = stmt.location.start_line
150
+ end
151
+ end
152
+ rules
153
+ end
154
+
155
+ def add_definition(policy_path, action)
156
+ rules = @policy_rules_cache[policy_path]
157
+ line_number = rules&.[](action.to_s)
158
+ return unless line_number
159
+
160
+ @response_builder << Interface::Location.new(
161
+ uri: URI::Generic.from_path(path: policy_path).to_s,
162
+ range: Interface::Range.new(
163
+ start: Interface::Position.new(line: line_number - 1, character: 0),
164
+ end: Interface::Position.new(line: line_number - 1, character: 0)
165
+ )
166
+ )
167
+ end
168
+ end
169
+ end
170
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_policy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.7.5
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: 2024-12-18 00:00:00.000000000 Z
11
+ date: 2025-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-next-core
@@ -133,15 +133,6 @@ files:
133
133
  - LICENSE.txt
134
134
  - README.md
135
135
  - config/rubocop-rspec.yml
136
- - lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb
137
- - lib/.rbnext/2.7/action_policy/i18n.rb
138
- - lib/.rbnext/2.7/action_policy/policy/cache.rb
139
- - lib/.rbnext/2.7/action_policy/policy/pre_check.rb
140
- - lib/.rbnext/2.7/action_policy/rails/scope_matchers/action_controller_params.rb
141
- - lib/.rbnext/2.7/action_policy/rails/scope_matchers/active_record.rb
142
- - lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb
143
- - lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb
144
- - lib/.rbnext/2.7/action_policy/utils/pretty_print.rb
145
136
  - lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb
146
137
  - lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb
147
138
  - lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb
@@ -152,6 +143,8 @@ files:
152
143
  - lib/.rbnext/3.0/action_policy/policy/execution_result.rb
153
144
  - lib/.rbnext/3.0/action_policy/policy/pre_check.rb
154
145
  - lib/.rbnext/3.0/action_policy/policy/reasons.rb
146
+ - lib/.rbnext/3.0/action_policy/rails/scope_matchers/action_controller_params.rb
147
+ - lib/.rbnext/3.0/action_policy/rails/scope_matchers/active_record.rb
155
148
  - lib/.rbnext/3.0/action_policy/rspec/be_an_alias_of.rb
156
149
  - lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb
157
150
  - lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb
@@ -169,6 +162,13 @@ files:
169
162
  - lib/.rbnext/3.2/action_policy/rspec/be_authorized_to.rb
170
163
  - lib/.rbnext/3.2/action_policy/rspec/have_authorized_scope.rb
171
164
  - lib/.rbnext/3.2/action_policy/utils/suggest_message.rb
165
+ - lib/.rbnext/3.4/action_policy/behaviours/policy_for.rb
166
+ - lib/.rbnext/3.4/action_policy/i18n.rb
167
+ - lib/.rbnext/3.4/action_policy/policy/cache.rb
168
+ - lib/.rbnext/3.4/action_policy/policy/pre_check.rb
169
+ - lib/.rbnext/3.4/action_policy/rspec/be_authorized_to.rb
170
+ - lib/.rbnext/3.4/action_policy/rspec/have_authorized_scope.rb
171
+ - lib/.rbnext/3.4/action_policy/utils/pretty_print.rb
172
172
  - lib/action_policy.rb
173
173
  - lib/action_policy/authorizer.rb
174
174
  - lib/action_policy/base.rb
@@ -225,6 +225,7 @@ files:
225
225
  - lib/generators/rspec/templates/policy_spec.rb.tt
226
226
  - lib/generators/test_unit/policy_generator.rb
227
227
  - lib/generators/test_unit/templates/policy_test.rb.tt
228
+ - lib/ruby_lsp/action_policy/addon.rb
228
229
  homepage: https://github.com/palkan/action_policy
229
230
  licenses:
230
231
  - MIT
@@ -234,7 +235,7 @@ metadata:
234
235
  documentation_uri: https://actionpolicy.evilmartians.io/
235
236
  homepage_uri: https://actionpolicy.evilmartians.io/
236
237
  source_code_uri: http://github.com/palkan/action_policy
237
- post_install_message:
238
+ post_install_message:
238
239
  rdoc_options: []
239
240
  require_paths:
240
241
  - lib
@@ -250,7 +251,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
250
251
  version: '0'
251
252
  requirements: []
252
253
  rubygems_version: 3.4.19
253
- signing_key:
254
+ signing_key:
254
255
  specification_version: 4
255
256
  summary: Authorization framework for Ruby/Rails application
256
257
  test_files: []