action_policy 0.3.4 → 0.4.4

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +4 -1
  3. data/.github/bug_report_template.rb +175 -0
  4. data/.travis.yml +4 -4
  5. data/CHANGELOG.md +54 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +7 -10
  8. data/benchmarks/namespaced_lookup_cache.rb +8 -5
  9. data/benchmarks/pre_checks.rb +73 -0
  10. data/docs/README.md +2 -0
  11. data/docs/caching.md +22 -4
  12. data/docs/instrumentation.md +11 -0
  13. data/docs/lookup_chain.md +6 -1
  14. data/docs/namespaces.md +2 -2
  15. data/docs/quick_start.md +5 -3
  16. data/docs/rails.md +7 -0
  17. data/docs/testing.md +63 -0
  18. data/docs/writing_policies.md +1 -1
  19. data/gemfiles/rails42.gemfile +1 -0
  20. data/lib/action_policy.rb +1 -1
  21. data/lib/action_policy/behaviour.rb +4 -0
  22. data/lib/action_policy/behaviours/memoized.rb +3 -7
  23. data/lib/action_policy/behaviours/policy_for.rb +13 -4
  24. data/lib/action_policy/behaviours/scoping.rb +2 -0
  25. data/lib/action_policy/behaviours/thread_memoized.rb +3 -7
  26. data/lib/action_policy/ext/policy_cache_key.rb +8 -6
  27. data/lib/action_policy/ext/{symbol_classify.rb → symbol_camelize.rb} +6 -6
  28. data/lib/action_policy/lookup_chain.rb +19 -13
  29. data/lib/action_policy/policy/authorization.rb +7 -11
  30. data/lib/action_policy/policy/cache.rb +26 -4
  31. data/lib/action_policy/policy/core.rb +2 -2
  32. data/lib/action_policy/policy/pre_check.rb +2 -2
  33. data/lib/action_policy/policy/reasons.rb +2 -2
  34. data/lib/action_policy/rails/ext/active_record.rb +7 -0
  35. data/lib/action_policy/rails/policy/instrumentation.rb +9 -2
  36. data/lib/action_policy/rspec/dsl.rb +6 -6
  37. data/lib/action_policy/rspec/have_authorized_scope.rb +2 -2
  38. data/lib/action_policy/testing.rb +3 -3
  39. data/lib/action_policy/version.rb +1 -1
  40. data/lib/generators/action_policy/install/templates/application_policy.rb +1 -1
  41. metadata +5 -4
  42. data/.github/FUNDING.yml +0 -1
@@ -43,21 +43,17 @@ module ActionPolicy
43
43
 
44
44
  attr_reader :authorization_context
45
45
 
46
- def initialize(*args, **params)
47
- super(*args)
46
+ def initialize(record = nil, **params)
47
+ super(record)
48
48
 
49
49
  @authorization_context = {}
50
50
 
51
51
  self.class.authorization_targets.each do |id, opts|
52
- if opts[:optional] == true
53
- val = params.fetch(id, nil)
54
- else
55
- raise AuthorizationContextMissing, id unless params.key?(id)
52
+ raise AuthorizationContextMissing, id unless params.key?(id) || opts[:optional]
56
53
 
57
- val = params.fetch(id)
54
+ val = params.fetch(id, nil)
58
55
 
59
- raise AuthorizationContextMissing, id if val.nil? && opts[:allow_nil] != true
60
- end
56
+ raise AuthorizationContextMissing, id if val.nil? && opts[:allow_nil] != true
61
57
 
62
58
  authorization_context[id] = instance_variable_set("@#{id}", val)
63
59
  end
@@ -66,9 +62,9 @@ module ActionPolicy
66
62
  end
67
63
 
68
64
  module ClassMethods # :nodoc:
69
- def authorize(*ids, **opts)
65
+ def authorize(*ids, allow_nil: false, optional: false)
70
66
  ids.each do |id|
71
- authorization_targets[id] = opts
67
+ authorization_targets[id] = {allow_nil: allow_nil || optional, optional: optional}
72
68
  end
73
69
 
74
70
  attr_reader(*ids)
@@ -30,9 +30,20 @@ module ActionPolicy # :nodoc:
30
30
  ActionPolicy::CACHE_NAMESPACE
31
31
  end
32
32
 
33
- def cache_key(rule)
34
- "#{cache_namespace}/#{context_cache_key}/" \
35
- "#{record._policy_cache_key}/#{self.class.name}/#{rule}"
33
+ def cache_key(*parts)
34
+ [
35
+ cache_namespace,
36
+ *parts
37
+ ].map { |part| part._policy_cache_key }.join("/")
38
+ end
39
+
40
+ def rule_cache_key(rule)
41
+ cache_key(
42
+ context_cache_key,
43
+ record,
44
+ self.class,
45
+ rule
46
+ )
36
47
  end
37
48
 
38
49
  def context_cache_key
@@ -41,7 +52,7 @@ module ActionPolicy # :nodoc:
41
52
 
42
53
  def apply_with_cache(rule)
43
54
  options = self.class.cached_rules.fetch(rule)
44
- key = cache_key(rule)
55
+ key = rule_cache_key(rule)
45
56
 
46
57
  ActionPolicy.cache_store.then do |store|
47
58
  @result = store.read(key)
@@ -62,6 +73,17 @@ module ActionPolicy # :nodoc:
62
73
  apply_with_cache(rule) { super }
63
74
  end
64
75
 
76
+ def cache(*parts, **options)
77
+ key = cache_key(*parts)
78
+ ActionPolicy.cache_store.then do |store|
79
+ res = store.read(key)
80
+ next res unless res.nil?
81
+ res = yield
82
+ store.write(key, res, **options)
83
+ res
84
+ end
85
+ end
86
+
65
87
  module ClassMethods # :nodoc:
66
88
  def cache(*rules, **options)
67
89
  rules.each do |rule|
@@ -66,7 +66,7 @@ module ActionPolicy
66
66
  attr_reader :record, :result
67
67
 
68
68
  # NEXT_RELEASE: deprecate `record` arg, migrate to `record: nil`
69
- def initialize(record = nil, _opts = nil)
69
+ def initialize(record = nil, *)
70
70
  @record = record
71
71
  end
72
72
 
@@ -101,7 +101,7 @@ module ActionPolicy
101
101
  #
102
102
  # If record is `nil` then we uses the current policy.
103
103
  def allowed_to?(rule, record = :__undef__, **options)
104
- if record == :__undef__ && options.empty?
104
+ if (record == :__undef__ || record == self.record) && options.empty?
105
105
  __apply__(rule)
106
106
  else
107
107
  policy_for(record: record, **options).apply(rule)
@@ -168,7 +168,7 @@ module ActionPolicy
168
168
  check = pre_checks.find { |c| c.name == name }
169
169
  raise "Pre-check already defined: #{name}" unless check.nil?
170
170
 
171
- pre_checks << Check.new(self, name, options)
171
+ pre_checks << Check.new(self, name, **options)
172
172
  end
173
173
  end
174
174
 
@@ -181,7 +181,7 @@ module ActionPolicy
181
181
  next pre_checks.delete(check) if options.empty?
182
182
 
183
183
  # otherwise duplicate and apply skip options
184
- pre_checks[pre_checks.index(check)] = check.dup.tap { |c| c.skip! options }
184
+ pre_checks[pre_checks.index(check)] = check.dup.tap { |c| c.skip!(**options) }
185
185
  end
186
186
  end
187
187
 
@@ -179,7 +179,7 @@ module ActionPolicy
179
179
 
180
180
  def allowed_to?(rule, record = :__undef__, **options)
181
181
  res =
182
- if record == :__undef__
182
+ if (record == :__undef__ || record == self.record) && options.empty?
183
183
  policy = self
184
184
  with_clean_result { apply(rule) }
185
185
  else
@@ -189,7 +189,7 @@ module ActionPolicy
189
189
  policy.result
190
190
  end
191
191
 
192
- result.reasons.add(policy, rule, res.details) if res.fail?
192
+ result&.reasons&.add(policy, rule, res.details) if res.fail?
193
193
 
194
194
  res.clear_details
195
195
 
@@ -7,4 +7,11 @@ ActiveRecord::Relation.include(Module.new do
7
7
  def policy_cache_key
8
8
  object_id
9
9
  end
10
+
11
+ # Explicitly define the policy_class method to avoid
12
+ # making Relation immutable (by initializing `arel` in `#respond_to_missing?).
13
+ # See https://github.com/palkan/action_policy/issues/101
14
+ def policy_class
15
+ nil
16
+ end
10
17
  end)
@@ -6,12 +6,19 @@ module ActionPolicy # :nodoc:
6
6
  # Add ActiveSupport::Notifications support.
7
7
  #
8
8
  # Fires `action_policy.apply_rule` event on every `#apply` call.
9
+ # Fires `action_policy.init` event on every policy initialization.
9
10
  module Instrumentation
10
- EVENT_NAME = "action_policy.apply_rule"
11
+ INIT_EVENT_NAME = "action_policy.init"
12
+ APPLY_EVENT_NAME = "action_policy.apply_rule"
13
+
14
+ def initialize(*)
15
+ event = {policy: self.class.name}
16
+ ActiveSupport::Notifications.instrument(INIT_EVENT_NAME, event) { super }
17
+ end
11
18
 
12
19
  def apply(rule)
13
20
  event = {policy: self.class.name, rule: rule.to_s}
14
- ActiveSupport::Notifications.instrument(EVENT_NAME, event) do
21
+ ActiveSupport::Notifications.instrument(APPLY_EVENT_NAME, event) do
15
22
  res = super
16
23
  event[:cached] = result.cached?
17
24
  event[:value] = result.value
@@ -13,18 +13,18 @@ module ActionPolicy
13
13
 
14
14
  ["", "f", "x"].each do |prefix|
15
15
  class_eval <<~CODE, __FILE__, __LINE__ + 1
16
- def #{prefix}succeed(msg = "succeeds", *args, **kwargs)
16
+ def #{prefix}succeed(msg = "succeeds", *args, **kwargs, &block)
17
17
  the_caller = caller
18
18
  #{prefix}context(msg, *args, **kwargs) do
19
- instance_eval(&Proc.new) if block_given?
19
+ instance_eval(&block) if block_given?
20
20
  find_and_eval_shared("examples", "action_policy:policy_rule_example", the_caller.first, true, the_caller)
21
21
  end
22
22
  end
23
23
 
24
- def #{prefix}failed(msg = "fails", *args, **kwargs)
24
+ def #{prefix}failed(msg = "fails", *args, **kwargs, &block)
25
25
  the_caller = caller
26
26
  #{prefix}context(msg, *args, **kwargs) do
27
- instance_eval(&Proc.new) if block_given?
27
+ instance_eval(&block) if block_given?
28
28
  find_and_eval_shared("examples", "action_policy:policy_rule_example", the_caller.first, false, the_caller)
29
29
  end
30
30
  end
@@ -68,7 +68,7 @@ if defined?(::RSpec)
68
68
 
69
69
  ::RSpec.shared_examples_for "action_policy:policy_rule_example" do |success, the_caller|
70
70
  if success
71
- specify do
71
+ specify "is allowed" do
72
72
  next if subject.success?
73
73
  raise(
74
74
  RSpec::Expectations::ExpectationNotMetError,
@@ -77,7 +77,7 @@ if defined?(::RSpec)
77
77
  )
78
78
  end
79
79
  else
80
- specify do
80
+ specify "is denied" do
81
81
  next if subject.fail?
82
82
  raise(
83
83
  RSpec::Expectations::ExpectationNotMetError,
@@ -44,8 +44,8 @@ module ActionPolicy
44
44
  self
45
45
  end
46
46
 
47
- def with_target
48
- @target_expectations = Proc.new
47
+ def with_target(&block)
48
+ @target_expectations = block
49
49
  self
50
50
  end
51
51
 
@@ -15,7 +15,7 @@ module ActionPolicy
15
15
 
16
16
  def matches?(policy_class, actual_rule, target)
17
17
  policy_class == policy.class &&
18
- target == policy.record &&
18
+ (target.is_a?(Class) ? target == policy.record : target === policy.record) &&
19
19
  rule == actual_rule
20
20
  end
21
21
 
@@ -107,8 +107,8 @@ module ActionPolicy
107
107
  super
108
108
  end
109
109
 
110
- def scopify(*args)
111
- AuthorizeTracker.track_scope(*args)
110
+ def scopify(*args, **kwargs)
111
+ AuthorizeTracker.track_scope(*args, **kwargs)
112
112
  super
113
113
  end
114
114
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionPolicy
4
- VERSION = "0.3.4"
4
+ VERSION = "0.4.4"
5
5
  end
@@ -5,7 +5,7 @@ class ApplicationPolicy < ActionPolicy::Base
5
5
  #
6
6
  # authorize :account, optional: true
7
7
  #
8
- # Read more about authoriztion context: https://actionpolicy.evilmartians.io/#/authorization_context
8
+ # Read more about authorization context: https://actionpolicy.evilmartians.io/#/authorization_context
9
9
 
10
10
  private
11
11
 
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.3.4
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-27 00:00:00.000000000 Z
11
+ date: 2020-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ammeter
@@ -158,9 +158,9 @@ extensions: []
158
158
  extra_rdoc_files: []
159
159
  files:
160
160
  - ".gitattributes"
161
- - ".github/FUNDING.yml"
162
161
  - ".github/ISSUE_TEMPLATE.md"
163
162
  - ".github/PULL_REQUEST_TEMPLATE.md"
163
+ - ".github/bug_report_template.rb"
164
164
  - ".gitignore"
165
165
  - ".rubocop.yml"
166
166
  - ".tidelift.yml"
@@ -172,6 +172,7 @@ files:
172
172
  - Rakefile
173
173
  - action_policy.gemspec
174
174
  - benchmarks/namespaced_lookup_cache.rb
175
+ - benchmarks/pre_checks.rb
175
176
  - bin/console
176
177
  - bin/setup
177
178
  - docs/.nojekyll
@@ -235,7 +236,7 @@ files:
235
236
  - lib/action_policy/ext/string_constantize.rb
236
237
  - lib/action_policy/ext/string_match.rb
237
238
  - lib/action_policy/ext/string_underscore.rb
238
- - lib/action_policy/ext/symbol_classify.rb
239
+ - lib/action_policy/ext/symbol_camelize.rb
239
240
  - lib/action_policy/ext/yield_self_then.rb
240
241
  - lib/action_policy/i18n.rb
241
242
  - lib/action_policy/lookup_chain.rb
@@ -1 +0,0 @@
1
- tidelift: "rubygems/action_policy"