action_policy 0.3.4 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
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"