action_policy 0.4.0 → 0.5.0

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +233 -171
  3. data/LICENSE.txt +1 -1
  4. data/README.md +7 -11
  5. data/lib/action_policy.rb +7 -1
  6. data/lib/action_policy/behaviour.rb +22 -16
  7. data/lib/action_policy/behaviours/policy_for.rb +10 -3
  8. data/lib/action_policy/behaviours/scoping.rb +2 -1
  9. data/lib/action_policy/behaviours/thread_memoized.rb +1 -3
  10. data/lib/action_policy/ext/module_namespace.rb +1 -6
  11. data/lib/action_policy/ext/policy_cache_key.rb +15 -33
  12. data/lib/action_policy/ext/{symbol_classify.rb → symbol_camelize.rb} +6 -6
  13. data/lib/action_policy/i18n.rb +1 -1
  14. data/lib/action_policy/lookup_chain.rb +41 -21
  15. data/lib/action_policy/policy/aliases.rb +7 -12
  16. data/lib/action_policy/policy/authorization.rb +14 -17
  17. data/lib/action_policy/policy/cache.rb +34 -18
  18. data/lib/action_policy/policy/core.rb +25 -12
  19. data/lib/action_policy/policy/defaults.rb +3 -9
  20. data/lib/action_policy/policy/execution_result.rb +3 -9
  21. data/lib/action_policy/policy/pre_check.rb +19 -58
  22. data/lib/action_policy/policy/reasons.rb +30 -20
  23. data/lib/action_policy/policy/scoping.rb +5 -6
  24. data/lib/action_policy/rails/controller.rb +6 -1
  25. data/lib/action_policy/rails/ext/active_record.rb +7 -0
  26. data/lib/action_policy/rails/policy/instrumentation.rb +1 -1
  27. data/lib/action_policy/rspec/be_authorized_to.rb +5 -9
  28. data/lib/action_policy/rspec/dsl.rb +3 -3
  29. data/lib/action_policy/rspec/have_authorized_scope.rb +5 -7
  30. data/lib/action_policy/testing.rb +1 -1
  31. data/lib/action_policy/utils/pretty_print.rb +21 -24
  32. data/lib/action_policy/utils/suggest_message.rb +1 -3
  33. data/lib/action_policy/version.rb +1 -1
  34. data/lib/generators/action_policy/install/templates/{application_policy.rb → application_policy.rb.tt} +1 -1
  35. data/lib/generators/action_policy/policy/policy_generator.rb +4 -1
  36. data/lib/generators/action_policy/policy/templates/{policy.rb → policy.rb.tt} +0 -0
  37. data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
  38. data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
  39. metadata +30 -119
  40. data/.gitattributes +0 -2
  41. data/.github/FUNDING.yml +0 -1
  42. data/.github/ISSUE_TEMPLATE.md +0 -18
  43. data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
  44. data/.gitignore +0 -15
  45. data/.rubocop.yml +0 -54
  46. data/.tidelift.yml +0 -6
  47. data/.travis.yml +0 -31
  48. data/Gemfile +0 -22
  49. data/Rakefile +0 -27
  50. data/action_policy.gemspec +0 -44
  51. data/benchmarks/namespaced_lookup_cache.rb +0 -71
  52. data/bin/console +0 -14
  53. data/bin/setup +0 -8
  54. data/docs/.nojekyll +0 -0
  55. data/docs/CNAME +0 -1
  56. data/docs/README.md +0 -77
  57. data/docs/_sidebar.md +0 -27
  58. data/docs/aliases.md +0 -122
  59. data/docs/assets/docsify-search.js +0 -364
  60. data/docs/assets/docsify.min.js +0 -3
  61. data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
  62. data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
  63. data/docs/assets/images/banner.png +0 -0
  64. data/docs/assets/images/cache.png +0 -0
  65. data/docs/assets/images/cache.svg +0 -70
  66. data/docs/assets/images/layer.png +0 -0
  67. data/docs/assets/images/layer.svg +0 -35
  68. data/docs/assets/prism-ruby.min.js +0 -1
  69. data/docs/assets/styles.css +0 -347
  70. data/docs/assets/vue.min.css +0 -1
  71. data/docs/authorization_context.md +0 -92
  72. data/docs/behaviour.md +0 -113
  73. data/docs/caching.md +0 -273
  74. data/docs/controller_action_aliases.md +0 -109
  75. data/docs/custom_lookup_chain.md +0 -48
  76. data/docs/custom_policy.md +0 -53
  77. data/docs/debugging.md +0 -55
  78. data/docs/decorators.md +0 -27
  79. data/docs/favicon.ico +0 -0
  80. data/docs/graphql.md +0 -302
  81. data/docs/i18n.md +0 -44
  82. data/docs/index.html +0 -43
  83. data/docs/instrumentation.md +0 -84
  84. data/docs/lookup_chain.md +0 -17
  85. data/docs/namespaces.md +0 -77
  86. data/docs/non_rails.md +0 -28
  87. data/docs/pre_checks.md +0 -57
  88. data/docs/pundit_migration.md +0 -80
  89. data/docs/quick_start.md +0 -118
  90. data/docs/rails.md +0 -120
  91. data/docs/reasons.md +0 -120
  92. data/docs/scoping.md +0 -255
  93. data/docs/testing.md +0 -333
  94. data/docs/writing_policies.md +0 -107
  95. data/gemfiles/jruby.gemfile +0 -8
  96. data/gemfiles/rails42.gemfile +0 -8
  97. data/gemfiles/rails6.gemfile +0 -8
  98. data/gemfiles/railsmaster.gemfile +0 -6
  99. data/lib/action_policy/ext/string_match.rb +0 -14
  100. data/lib/action_policy/ext/yield_self_then.rb +0 -25
@@ -46,23 +46,18 @@ module ActionPolicy
46
46
  end
47
47
  end
48
48
 
49
- def lookup_alias(rule)
50
- rules_aliases[rule]
51
- end
49
+ def lookup_alias(rule) = rules_aliases[rule]
52
50
 
53
- def lookup_default_rule
54
- rules_aliases[DEFAULT]
55
- end
51
+ def lookup_default_rule() = rules_aliases[DEFAULT]
56
52
 
57
53
  def rules_aliases
58
54
  return @rules_aliases if instance_variable_defined?(:@rules_aliases)
59
55
 
60
- @rules_aliases =
61
- if superclass.respond_to?(:rules_aliases)
62
- superclass.rules_aliases.dup
63
- else
64
- {}
65
- end
56
+ if superclass.respond_to?(:rules_aliases)
57
+ superclass.rules_aliases.dup
58
+ else
59
+ {}
60
+ end => @rules_aliases
66
61
  end
67
62
 
68
63
  def method_added(name)
@@ -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,11 @@ 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)
66
+ allow_nil ||= optional
67
+
70
68
  ids.each do |id|
71
- authorization_targets[id] = opts
69
+ authorization_targets[id] = {allow_nil, optional}
72
70
  end
73
71
 
74
72
  attr_reader(*ids)
@@ -77,12 +75,11 @@ module ActionPolicy
77
75
  def authorization_targets
78
76
  return @authorization_targets if instance_variable_defined?(:@authorization_targets)
79
77
 
80
- @authorization_targets =
81
- if superclass.respond_to?(:authorization_targets)
82
- superclass.authorization_targets.dup
83
- else
84
- {}
85
- end
78
+ if superclass.respond_to?(:authorization_targets)
79
+ superclass.authorization_targets.dup
80
+ else
81
+ {}
82
+ end => @authorization_targets
86
83
  end
87
84
  end
88
85
  end
@@ -3,16 +3,13 @@
3
3
  require "action_policy/version"
4
4
 
5
5
  module ActionPolicy # :nodoc:
6
+ using RubyNext
7
+
6
8
  # By default cache namespace (or prefix) contains major and minor version of the gem
7
9
  CACHE_NAMESPACE = "acp:#{ActionPolicy::VERSION.split(".").take(2).join(".")}"
8
10
 
9
- require "action_policy/ext/yield_self_then"
10
11
  require "action_policy/ext/policy_cache_key"
11
12
 
12
- unless "".respond_to?(:then)
13
- require "action_policy/ext/yield_self_then"
14
- using ActionPolicy::Ext::YieldSelfThen
15
- end
16
13
  using ActionPolicy::Ext::PolicyCacheKey
17
14
 
18
15
  module Policy
@@ -26,22 +23,31 @@ module ActionPolicy # :nodoc:
26
23
  end
27
24
  end
28
25
 
29
- def cache_namespace
30
- ActionPolicy::CACHE_NAMESPACE
26
+ def cache_namespace() = ActionPolicy::CACHE_NAMESPACE
27
+
28
+ def cache_key(*parts)
29
+ [
30
+ cache_namespace,
31
+ *parts
32
+ ].map { _1._policy_cache_key }.join("/")
31
33
  end
32
34
 
33
- def cache_key(rule)
34
- "#{cache_namespace}/#{context_cache_key}/" \
35
- "#{record._policy_cache_key}/#{self.class.name}/#{rule}"
35
+ def rule_cache_key(rule)
36
+ cache_key(
37
+ context_cache_key,
38
+ record,
39
+ self.class,
40
+ rule
41
+ )
36
42
  end
37
43
 
38
44
  def context_cache_key
39
- authorization_context.map { |_k, v| v._policy_cache_key.to_s }.join("/")
45
+ authorization_context.map { _2._policy_cache_key.to_s }.join("/")
40
46
  end
41
47
 
42
48
  def apply_with_cache(rule)
43
49
  options = self.class.cached_rules.fetch(rule)
44
- key = cache_key(rule)
50
+ key = rule_cache_key(rule)
45
51
 
46
52
  ActionPolicy.cache_store.then do |store|
47
53
  @result = store.read(key)
@@ -62,6 +68,17 @@ module ActionPolicy # :nodoc:
62
68
  apply_with_cache(rule) { super }
63
69
  end
64
70
 
71
+ def cache(*parts, **options)
72
+ key = cache_key(*parts)
73
+ ActionPolicy.cache_store.then do |store|
74
+ res = store.read(key)
75
+ next res unless res.nil?
76
+ res = yield
77
+ store.write(key, res, options)
78
+ res
79
+ end
80
+ end
81
+
65
82
  module ClassMethods # :nodoc:
66
83
  def cache(*rules, **options)
67
84
  rules.each do |rule|
@@ -72,12 +89,11 @@ module ActionPolicy # :nodoc:
72
89
  def cached_rules
73
90
  return @cached_rules if instance_variable_defined?(:@cached_rules)
74
91
 
75
- @cached_rules =
76
- if superclass.respond_to?(:cached_rules)
77
- superclass.cached_rules.dup
78
- else
79
- {}
80
- end
92
+ if superclass.respond_to?(:cached_rules)
93
+ superclass.cached_rules.dup
94
+ else
95
+ {}
96
+ end => @cached_rules
81
97
  end
82
98
  end
83
99
  end
@@ -11,6 +11,8 @@ unless "".respond_to?(:underscore)
11
11
  end
12
12
 
13
13
  module ActionPolicy
14
+ using RubyNext
15
+
14
16
  # Raised when `resolve_rule` failed to find an approriate
15
17
  # policy rule method for the activity
16
18
  class UnknownRule < Error
@@ -75,15 +77,28 @@ module ActionPolicy
75
77
  # `apply` also calls pre-checks.
76
78
  def apply(rule)
77
79
  @result = self.class.result_class.new(self.class, rule)
78
- @result.load __apply__(rule)
80
+
81
+ catch :policy_fulfilled do
82
+ result.load __apply__(rule)
83
+ end
84
+
85
+ result.value
86
+ end
87
+
88
+ def deny!
89
+ result&.load false
90
+ throw :policy_fulfilled
91
+ end
92
+
93
+ def allow!
94
+ result&.load true
95
+ throw :policy_fulfilled
79
96
  end
80
97
 
81
98
  # This method performs the rule call.
82
99
  # Override or extend it to provide custom functionality
83
100
  # (such as caching, pre checks, etc.)
84
- def __apply__(rule)
85
- public_send(rule)
86
- end
101
+ def __apply__(rule) = public_send(rule)
87
102
 
88
103
  # Wrap code that could modify result
89
104
  # to prevent the current result modification
@@ -102,16 +117,16 @@ module ActionPolicy
102
117
  # If record is `nil` then we uses the current policy.
103
118
  def allowed_to?(rule, record = :__undef__, **options)
104
119
  if (record == :__undef__ || record == self.record) && options.empty?
105
- __apply__(rule)
120
+ __apply__(resolve_rule(rule))
106
121
  else
107
- policy_for(record: record, **options).apply(rule)
122
+ policy_for(record: record, **options).then do |policy|
123
+ policy.apply(policy.resolve_rule(rule))
124
+ end
108
125
  end
109
126
  end
110
127
 
111
128
  # An alias for readability purposes
112
- def check?(*args)
113
- allowed_to?(*args)
114
- end
129
+ def check?(*args) = allowed_to?(*args)
115
130
 
116
131
  # Returns a rule name (policy method name) for activity.
117
132
  #
@@ -127,9 +142,7 @@ module ActionPolicy
127
142
  # Return annotated source code for the rule
128
143
  # NOTE: require "method_source" and "unparser" gems to be installed.
129
144
  # Otherwise returns empty string.
130
- def inspect_rule(rule)
131
- PrettyPrint.print_method(self, rule)
132
- end
145
+ def inspect_rule(rule) = PrettyPrint.print_method(self, rule)
133
146
 
134
147
  # Helper for printing the annotated rule source.
135
148
  # Useful for debugging: type `pp :show?` within the context of the policy
@@ -21,17 +21,11 @@ module ActionPolicy
21
21
  base.authorize :user
22
22
  end
23
23
 
24
- def index?
25
- false
26
- end
24
+ def index?() = false
27
25
 
28
- def create?
29
- false
30
- end
26
+ def create?() = false
31
27
 
32
- def manage?
33
- false
34
- end
28
+ def manage?() = false
35
29
  end
36
30
  end
37
31
  end
@@ -19,21 +19,15 @@ module ActionPolicy
19
19
  @value = value
20
20
  end
21
21
 
22
- def success?
23
- @value == true
24
- end
22
+ def success?() = @value == true
25
23
 
26
- def fail?
27
- @value == false
28
- end
24
+ def fail?() = @value == false
29
25
 
30
26
  def cached!
31
27
  @cached = true
32
28
  end
33
29
 
34
- def cached?
35
- @cached == true
36
- end
30
+ def cached?() = @cached == true
37
31
 
38
32
  def inspect
39
33
  "<#{policy}##{rule}: #{@value}>"
@@ -31,40 +31,12 @@ module ActionPolicy
31
31
  #
32
32
  # Implements filtering logic.
33
33
  class Check
34
- # Wrapper over check result
35
- class Result
36
- DENY = :__deny__
37
- ALLOW = :__allow__
38
-
39
- KINDS = {
40
- DENY => false,
41
- ALLOW => true
42
- }.freeze
43
-
44
- attr_reader :fulfilled
45
-
46
- def initialize(val)
47
- @fulfilled = KINDS.key?(val)
48
- @value = val
49
- end
50
-
51
- alias fulfilled? fulfilled
52
-
53
- def denied?
54
- @value == DENY
55
- end
56
-
57
- def value
58
- KINDS.fetch(@value)
59
- end
60
- end
61
-
62
34
  attr_reader :name, :policy_class
63
35
 
64
36
  def initialize(policy, name, except: nil, only: nil)
65
37
  if !except.nil? && !only.nil?
66
38
  raise ArgumentError,
67
- "Only one of `except` and `only` may be specified for pre-check"
39
+ "Only one of `except` and `only` may be specified for pre-check"
68
40
  end
69
41
 
70
42
  @policy_class = policy
@@ -80,23 +52,17 @@ module ActionPolicy
80
52
  filter.call(rule)
81
53
  end
82
54
 
83
- def call(policy)
84
- Result.new(policy.send(name)).tap do |res|
85
- # add denial reason if Reasons included
86
- policy.result.reasons.add(policy, name) if
87
- res.denied? && policy.result.respond_to?(:reasons)
88
- end
89
- end
55
+ def call(policy) = policy.send(name)
90
56
 
91
57
  def skip!(except: nil, only: nil)
92
58
  if !except.nil? && !only.nil?
93
59
  raise ArgumentError,
94
- "Only one of `except` and `only` may be specified when skipping pre-check"
60
+ "Only one of `except` and `only` may be specified when skipping pre-check"
95
61
  end
96
62
 
97
63
  if except.nil? && only.nil?
98
64
  raise ArgumentError,
99
- "At least one of `except` and `only` must be specified when skipping pre-check"
65
+ "At least one of `except` and `only` must be specified when skipping pre-check"
100
66
  end
101
67
 
102
68
  if except
@@ -116,7 +82,12 @@ module ActionPolicy
116
82
  # rubocop: enable
117
83
 
118
84
  def dup
119
- self.class.new(policy_class, name, except: blacklist&.dup, only: whitelist&.dup)
85
+ self.class.new(
86
+ policy_class,
87
+ name,
88
+ except: blacklist&.dup,
89
+ only: whitelist&.dup
90
+ )
120
91
  end
121
92
 
122
93
  private
@@ -142,21 +113,12 @@ module ActionPolicy
142
113
  def run_pre_checks(rule)
143
114
  self.class.pre_checks.each do |check|
144
115
  next unless check.applicable?(rule)
145
- res = check.call(self)
146
- return res.value if res.fulfilled?
116
+ check.call(self)
147
117
  end
148
118
 
149
119
  yield if block_given?
150
120
  end
151
121
 
152
- def deny!
153
- Check::Result::DENY
154
- end
155
-
156
- def allow!
157
- Check::Result::ALLOW
158
- end
159
-
160
122
  def __apply__(rule)
161
123
  run_pre_checks(rule) { super }
162
124
  end
@@ -165,7 +127,7 @@ module ActionPolicy
165
127
  def pre_check(*names, **options)
166
128
  names.each do |name|
167
129
  # do not allow pre-check override
168
- check = pre_checks.find { |c| c.name == name }
130
+ check = pre_checks.find { _1.name == name }
169
131
  raise "Pre-check already defined: #{name}" unless check.nil?
170
132
 
171
133
  pre_checks << Check.new(self, name, **options)
@@ -174,26 +136,25 @@ module ActionPolicy
174
136
 
175
137
  def skip_pre_check(*names, **options)
176
138
  names.each do |name|
177
- check = pre_checks.find { |c| c.name == name }
139
+ check = pre_checks.find { _1.name == name }
178
140
  raise "Pre-check not found: #{name}" if check.nil?
179
141
 
180
142
  # when no options provided we remove this check completely
181
143
  next pre_checks.delete(check) if options.empty?
182
144
 
183
145
  # otherwise duplicate and apply skip options
184
- pre_checks[pre_checks.index(check)] = check.dup.tap { |c| c.skip!(**options) }
146
+ pre_checks[pre_checks.index(check)] = check.dup.tap { _1.skip!(**options) }
185
147
  end
186
148
  end
187
149
 
188
150
  def pre_checks
189
151
  return @pre_checks if instance_variable_defined?(:@pre_checks)
190
152
 
191
- @pre_checks =
192
- if superclass.respond_to?(:pre_checks)
193
- superclass.pre_checks.dup
194
- else
195
- []
196
- end
153
+ if superclass.respond_to?(:pre_checks)
154
+ superclass.pre_checks.dup
155
+ else
156
+ []
157
+ end => @pre_checks
197
158
  end
198
159
  end
199
160
  end