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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE.md +4 -1
- data/.github/bug_report_template.rb +175 -0
- data/.travis.yml +4 -4
- data/CHANGELOG.md +54 -0
- data/LICENSE.txt +1 -1
- data/README.md +7 -10
- data/benchmarks/namespaced_lookup_cache.rb +8 -5
- data/benchmarks/pre_checks.rb +73 -0
- data/docs/README.md +2 -0
- data/docs/caching.md +22 -4
- data/docs/instrumentation.md +11 -0
- data/docs/lookup_chain.md +6 -1
- data/docs/namespaces.md +2 -2
- data/docs/quick_start.md +5 -3
- data/docs/rails.md +7 -0
- data/docs/testing.md +63 -0
- data/docs/writing_policies.md +1 -1
- data/gemfiles/rails42.gemfile +1 -0
- data/lib/action_policy.rb +1 -1
- data/lib/action_policy/behaviour.rb +4 -0
- data/lib/action_policy/behaviours/memoized.rb +3 -7
- data/lib/action_policy/behaviours/policy_for.rb +13 -4
- data/lib/action_policy/behaviours/scoping.rb +2 -0
- data/lib/action_policy/behaviours/thread_memoized.rb +3 -7
- data/lib/action_policy/ext/policy_cache_key.rb +8 -6
- data/lib/action_policy/ext/{symbol_classify.rb → symbol_camelize.rb} +6 -6
- data/lib/action_policy/lookup_chain.rb +19 -13
- data/lib/action_policy/policy/authorization.rb +7 -11
- data/lib/action_policy/policy/cache.rb +26 -4
- data/lib/action_policy/policy/core.rb +2 -2
- data/lib/action_policy/policy/pre_check.rb +2 -2
- data/lib/action_policy/policy/reasons.rb +2 -2
- data/lib/action_policy/rails/ext/active_record.rb +7 -0
- data/lib/action_policy/rails/policy/instrumentation.rb +9 -2
- data/lib/action_policy/rspec/dsl.rb +6 -6
- data/lib/action_policy/rspec/have_authorized_scope.rb +2 -2
- data/lib/action_policy/testing.rb +3 -3
- data/lib/action_policy/version.rb +1 -1
- data/lib/generators/action_policy/install/templates/application_policy.rb +1 -1
- metadata +5 -4
- data/.github/FUNDING.yml +0 -1
data/docs/README.md
CHANGED
@@ -65,6 +65,8 @@ Learn more about the motivation behind the Action Policy and its features by wat
|
|
65
65
|
|
66
66
|
## Resources
|
67
67
|
|
68
|
+
- RubyRussia, 2019 "Welcome, or access denied?" talk ([video](https://www.youtube.com/watch?v=y15a2g7v8i0) [RU], [slides](https://speakerdeck.com/palkan/rubyrussia-2019-welcome-or-access-denied))
|
69
|
+
|
68
70
|
- Seattle.rb, 2019 "A Denial!" talk [[slides](https://speakerdeck.com/palkan/seattle-dot-rb-2019-a-denial)]
|
69
71
|
|
70
72
|
- RailsConf, 2018 "Access Denied" talk [[video](https://www.youtube.com/watch?v=NVwx0DARDis), [slides](https://speakerdeck.com/palkan/railsconf-2018-access-denied-the-missing-guide-to-authorization-in-rails)]
|
data/docs/caching.md
CHANGED
@@ -162,11 +162,11 @@ Rails.application.configure do |config|
|
|
162
162
|
end
|
163
163
|
```
|
164
164
|
|
165
|
-
Cache store must provide at least a `#read(key)` and
|
165
|
+
Cache store must provide at least a `#read(key)` and `#write(key, value, **options)` methods.
|
166
166
|
|
167
167
|
**NOTE:** cache store also should take care of serialiation/deserialization since the `value` is `ExecutionResult` instance (which contains also some additional information, e.g. failure reasons). Rails cache store supports serialization/deserialization out-of-the-box.
|
168
168
|
|
169
|
-
By default, Action Policy builds a cache key using the following scheme:
|
169
|
+
By default, Action Policy builds a cache key using the following scheme (defined in `#rule_cache_key(rule)` method):
|
170
170
|
|
171
171
|
```ruby
|
172
172
|
"#{cache_namespace}/#{context_cache_key}" \
|
@@ -175,11 +175,29 @@ By default, Action Policy builds a cache key using the following scheme:
|
|
175
175
|
|
176
176
|
Where `cache_namespace` is equal to `"acp:#{MAJOR_GEM_VERSION}.#{MINOR_GEM_VERSION}"`, and `context_cache_key` is a concatenation of all authorization contexts cache keys (in the same order as they are defined in the policy class).
|
177
177
|
|
178
|
-
If any object does not respond to `#policy_cache_key`, we fallback to `#cache_key
|
178
|
+
If any object does not respond to `#policy_cache_key`, we fallback to `#cache_key` (or `#cache_key_with_version` for modern Rails versions). If `#cache_key` is not defined, an `ArgumentError` is raised.
|
179
179
|
|
180
180
|
**NOTE:** if your `#cache_key` method is performance-heavy (e.g. like the `ActiveRecord::Relation`'s one), we recommend to explicitly define the `#policy_cache_key` method on the corresponding class to avoid unnecessary load. See also [action_policy#55](https://github.com/palkan/action_policy/issues/55).
|
181
181
|
|
182
|
-
You can define your own `
|
182
|
+
You can define your own `rule_cache_key` / `cache_namespace` / `context_cache_key` methods for policy class to override this logic.
|
183
|
+
|
184
|
+
You can also use the `#cache` instance method to cache arbitrary values in you policies:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
class ApplicationPolicy < ActionPolicy::Base
|
188
|
+
# Suppose that a user has many roles each having an array of permissions
|
189
|
+
def permissions
|
190
|
+
cache(user) { user.roles.pluck(:permissions).flatten.uniq }
|
191
|
+
end
|
192
|
+
|
193
|
+
# You can pass multiple cache key "parts"
|
194
|
+
def account_permissions(account)
|
195
|
+
cache(user, account) { user.account_roles.where(account: account).pluck(:permissions).flatten.uniq }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
```
|
199
|
+
|
200
|
+
**NOTE:** `#cache` method uses the same cache key generation logic as rules caching (described above).
|
183
201
|
|
184
202
|
#### Invalidation
|
185
203
|
|
data/docs/instrumentation.md
CHANGED
@@ -47,6 +47,17 @@ permitted to do that).
|
|
47
47
|
|
48
48
|
The `action_policy.apply_rule` might have a large number of failures, 'cause it also tracks the usage of non-raising applications (i.e. `allowed_to?`).
|
49
49
|
|
50
|
+
### `action_policy.init`
|
51
|
+
|
52
|
+
This event is triggered every time a new policy object is initialized.
|
53
|
+
|
54
|
+
The event contains the following information:
|
55
|
+
|
56
|
+
- `:policy` – policy class name.
|
57
|
+
|
58
|
+
This event is useful if you want to track the number of initialized policies per _action_ (for example, when you want to ensure that
|
59
|
+
the [memoization](caching.md) works as expected).
|
60
|
+
|
50
61
|
## Turn off instrumentation
|
51
62
|
|
52
63
|
Instrumentation is enabled by default. To turn it off add to your configuration:
|
data/docs/lookup_chain.md
CHANGED
@@ -2,7 +2,12 @@
|
|
2
2
|
|
3
3
|
Action Policy tries to automatically infer policy class from the target using the following _probes_:
|
4
4
|
|
5
|
-
1. If the target is a `Symbol
|
5
|
+
1. If the target is a `Symbol`:
|
6
|
+
|
7
|
+
a) Try `"#{target.to_s.camelize}Policy"` as a `policy_name` (see below);
|
8
|
+
|
9
|
+
b) If `String#classify` is available, e.g. when using Rails' ActiveSupport, try `"#{target.to_s.classify}Policy"`;
|
10
|
+
|
6
11
|
2. If the target responds to `policy_class`, then use it;
|
7
12
|
3. If the target's class responds to `policy_class`, then use it;
|
8
13
|
4. If the target or the target's class responds to `policy_name`, then use it (the `policy_name` should end with `Policy` as it's not appended automatically);
|
data/docs/namespaces.md
CHANGED
@@ -6,7 +6,7 @@ Consider an example:
|
|
6
6
|
|
7
7
|
```ruby
|
8
8
|
module Admin
|
9
|
-
class UsersController <
|
9
|
+
class UsersController < ApplicationController
|
10
10
|
def index
|
11
11
|
# uses Admin::UserPolicy if any, otherwise fallbacks to UserPolicy
|
12
12
|
authorize!
|
@@ -20,7 +20,7 @@ Module nesting is also supported:
|
|
20
20
|
```ruby
|
21
21
|
module Admin
|
22
22
|
module Client
|
23
|
-
class UsersController <
|
23
|
+
class UsersController < ApplicationController
|
24
24
|
def index
|
25
25
|
# lookup for Admin::Client::UserPolicy -> Admin::UserPolicy -> UserPolicy
|
26
26
|
authorize!
|
data/docs/quick_start.md
CHANGED
@@ -34,9 +34,11 @@ class ApplicationPolicy < ActionPolicy::Base
|
|
34
34
|
end
|
35
35
|
```
|
36
36
|
|
37
|
-
You could use the following command to generate it
|
37
|
+
You could use the following command to generate it when using Rails:
|
38
38
|
|
39
|
-
|
39
|
+
```sh
|
40
|
+
rails generate action_policy:install
|
41
|
+
```
|
40
42
|
|
41
43
|
**NOTE:** it is not necessary to inherit from `ActionPolicy::Base`; instead, you can [construct basic policy](custom_policy.md) choosing only the components you need.
|
42
44
|
|
@@ -96,7 +98,7 @@ There is also an `allowed_to?` method which returns `true` or `false` and could
|
|
96
98
|
<% @posts.each do |post| %>
|
97
99
|
<li><%= post.title %>
|
98
100
|
<% if allowed_to?(:edit?, post) %>
|
99
|
-
|
101
|
+
<%= link_to "Edit", post %>
|
100
102
|
<% end %>
|
101
103
|
</li>
|
102
104
|
<% end %>
|
data/docs/rails.md
CHANGED
@@ -6,6 +6,13 @@ In most cases, you do not have to do anything except writing policy files and ad
|
|
6
6
|
|
7
7
|
**NOTE:** both controllers and channels extensions are built on top of the Action Policy [behaviour](./behaviour.md) mixin.
|
8
8
|
|
9
|
+
## Generators
|
10
|
+
|
11
|
+
Action Policy provides a couple of useful Rails generators:
|
12
|
+
|
13
|
+
- `rails g action_policy:install` — adds `app/policies/application_policy.rb` file
|
14
|
+
- `rails g action_policy:policy MODEL_NAME` — adds a policy file and a policy test file for a given model (also creates an `application_policy.rb` if it's missing)
|
15
|
+
|
9
16
|
## Controllers integration
|
10
17
|
|
11
18
|
Action Policy assumes that you have a `current_user` method which specifies the current authenticated subject (`user`).
|
data/docs/testing.md
CHANGED
@@ -112,6 +112,8 @@ OR
|
|
112
112
|
|
113
113
|
### Testing scopes
|
114
114
|
|
115
|
+
#### Active Record relation example
|
116
|
+
|
115
117
|
There is no single rule on how to test scopes, 'cause it dependes on the _nature_ of the scope.
|
116
118
|
|
117
119
|
Here's an example of RSpec tests for Active Record scoping rules:
|
@@ -156,6 +158,35 @@ describe PostPolicy do
|
|
156
158
|
end
|
157
159
|
```
|
158
160
|
|
161
|
+
#### Action Controller params example
|
162
|
+
|
163
|
+
Here's an example of RSpec tests for Action Controller parameters scoping rules:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
describe PostPolicy do
|
167
|
+
describe "params scope" do
|
168
|
+
let(:user) { build_stubbed :user }
|
169
|
+
let(:context) { {user: user} }
|
170
|
+
|
171
|
+
let(:params) { {name: "a", password: "b"} }
|
172
|
+
let(:target) { ActionController::Parameters.new(params) }
|
173
|
+
|
174
|
+
# it's easier to asses the hash representation, not the AC::Params object
|
175
|
+
subject { policy.apply_scope(target, type: :action_controller_params).to_h }
|
176
|
+
|
177
|
+
context "as user" do
|
178
|
+
it { is_expected.to eq({name: "a"}) }
|
179
|
+
end
|
180
|
+
|
181
|
+
context "as manager" do
|
182
|
+
before { user.update!(role: :manager) }
|
183
|
+
|
184
|
+
it { is_expected.to eq({name: "a", password: "b"}) }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
159
190
|
## Testing authorization
|
160
191
|
|
161
192
|
To test the act of authorization you have to make sure that the `authorize!` method is called with the appropriate arguments.
|
@@ -230,6 +261,12 @@ end
|
|
230
261
|
|
231
262
|
If you omit `.with(PostPolicy)` then the inferred policy for the target (`post`) would be used.
|
232
263
|
|
264
|
+
RSpec composed matchers are available as target:
|
265
|
+
|
266
|
+
```ruby
|
267
|
+
expect { subject }.to be_authorized_to(:show?, an_instance_of(Post))
|
268
|
+
```
|
269
|
+
|
233
270
|
## Testing scoping
|
234
271
|
|
235
272
|
Action Policy provides a way to test that a correct scoping has been applied during the code execution.
|
@@ -325,3 +362,29 @@ expect { subject }.to have_authorized_scope(:scope)
|
|
325
362
|
expect(target).to eq(User.all)
|
326
363
|
}
|
327
364
|
```
|
365
|
+
|
366
|
+
|
367
|
+
## Testing views
|
368
|
+
|
369
|
+
When you test views that call policies methods as `allowed_to?`, your may have `Missing policy authorization context: user` error.
|
370
|
+
You may need to stub `current_user` to resolve the issue.
|
371
|
+
|
372
|
+
Consider an RSpec example:
|
373
|
+
|
374
|
+
```ruby
|
375
|
+
describe "users/index.html.slim" do
|
376
|
+
let(:user) { build_stubbed :user }
|
377
|
+
let(:users) { create_list(:user, 2) }
|
378
|
+
|
379
|
+
before do
|
380
|
+
allow(controller).to receive(:current_user).and_return(user)
|
381
|
+
|
382
|
+
assign :users, users
|
383
|
+
render
|
384
|
+
end
|
385
|
+
|
386
|
+
describe "displays user#index correctly" do
|
387
|
+
it { expect(rendered).to have_link(users.first.email, href: edit_user_path(users.first)) }
|
388
|
+
end
|
389
|
+
end
|
390
|
+
```
|
data/docs/writing_policies.md
CHANGED
@@ -56,7 +56,7 @@ You can also specify all the usual options (such as `with`).
|
|
56
56
|
|
57
57
|
There is also a `check?` method which is just an "alias"\* for `allowed_to?` added for better readability:
|
58
58
|
|
59
|
-
```
|
59
|
+
```ruby
|
60
60
|
class PostPolicy < ApplicationPolicy
|
61
61
|
def show?
|
62
62
|
user.admin? || check?(:publicly_visible?)
|
data/gemfiles/rails42.gemfile
CHANGED
data/lib/action_policy.rb
CHANGED
@@ -38,6 +38,8 @@ module ActionPolicy
|
|
38
38
|
record = implicit_authorization_target! if record == :__undef__
|
39
39
|
raise ArgumentError, "Record must be specified" if record.nil?
|
40
40
|
|
41
|
+
options[:context] && (options[:context] = authorization_context.merge(options[:context]))
|
42
|
+
|
41
43
|
policy = policy_for(record: record, **options)
|
42
44
|
|
43
45
|
Authorizer.call(policy, authorization_rule_for(policy, to))
|
@@ -50,6 +52,8 @@ module ActionPolicy
|
|
50
52
|
record = implicit_authorization_target! if record == :__undef__
|
51
53
|
raise ArgumentError, "Record must be specified" if record.nil?
|
52
54
|
|
55
|
+
options[:context] && (options[:context] = authorization_context.merge(options[:context]))
|
56
|
+
|
53
57
|
policy = policy_for(record: record, **options)
|
54
58
|
|
55
59
|
policy.apply(authorization_rule_for(policy, rule))
|
@@ -19,9 +19,6 @@ module ActionPolicy
|
|
19
19
|
#
|
20
20
|
# policy.equal?(policy_for(record, with: CustomPolicy)) #=> false
|
21
21
|
module Memoized
|
22
|
-
require "action_policy/ext/policy_cache_key"
|
23
|
-
using ActionPolicy::Ext::PolicyCacheKey
|
24
|
-
|
25
22
|
class << self
|
26
23
|
def prepended(base)
|
27
24
|
base.prepend InstanceMethods
|
@@ -32,13 +29,12 @@ module ActionPolicy
|
|
32
29
|
|
33
30
|
module InstanceMethods # :nodoc:
|
34
31
|
def policy_for(record:, **opts)
|
35
|
-
__policy_memoize__(record, opts) { super(record: record, **opts) }
|
32
|
+
__policy_memoize__(record, **opts) { super(record: record, **opts) }
|
36
33
|
end
|
37
34
|
end
|
38
35
|
|
39
|
-
def __policy_memoize__(record,
|
40
|
-
|
41
|
-
cache_key = "#{namespace}/#{with}/#{record_key}"
|
36
|
+
def __policy_memoize__(record, **options)
|
37
|
+
cache_key = policy_for_cache_key(record: record, **options)
|
42
38
|
|
43
39
|
return __policies_cache__[cache_key] if
|
44
40
|
__policies_cache__.key?(cache_key)
|
@@ -4,11 +4,13 @@ module ActionPolicy
|
|
4
4
|
module Behaviours
|
5
5
|
# Adds `policy_for` method
|
6
6
|
module PolicyFor
|
7
|
+
require "action_policy/ext/policy_cache_key"
|
8
|
+
using ActionPolicy::Ext::PolicyCacheKey
|
9
|
+
|
7
10
|
# Returns policy instance for the record.
|
8
|
-
def policy_for(record:, with: nil, namespace:
|
9
|
-
namespace
|
10
|
-
policy_class
|
11
|
-
policy_class&.new(record, authorization_context.tap { |ctx| ctx.merge!(context) if context })
|
11
|
+
def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false)
|
12
|
+
policy_class = with || ::ActionPolicy.lookup(record, namespace: namespace, context: context, allow_nil: allow_nil)
|
13
|
+
policy_class&.new(record, **context)
|
12
14
|
end
|
13
15
|
|
14
16
|
def authorization_context
|
@@ -41,6 +43,13 @@ module ActionPolicy
|
|
41
43
|
]
|
42
44
|
)
|
43
45
|
end
|
46
|
+
|
47
|
+
def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **)
|
48
|
+
record_key = record._policy_cache_key(use_object_id: true)
|
49
|
+
context_key = context.values.map { |v| v._policy_cache_key(use_object_id: true) }.join(".")
|
50
|
+
|
51
|
+
"#{namespace}/#{with}/#{context_key}/#{record_key}"
|
52
|
+
end
|
44
53
|
end
|
45
54
|
end
|
46
55
|
end
|
@@ -11,6 +11,8 @@ module ActionPolicy
|
|
11
11
|
# - secondly, try to infer policy class from `target` (non-raising lookup)
|
12
12
|
# - use `implicit_authorization_target` if none of the above works.
|
13
13
|
def authorized_scope(target, type: nil, as: :default, scope_options: nil, **options)
|
14
|
+
options[:context] && (options[:context] = authorization_context.merge(options[:context]))
|
15
|
+
|
14
16
|
policy = policy_for(record: target, allow_nil: true, **options)
|
15
17
|
policy ||= policy_for(record: implicit_authorization_target!, **options)
|
16
18
|
|
@@ -37,9 +37,6 @@ module ActionPolicy
|
|
37
37
|
#
|
38
38
|
# NOTE: don't forget to clear thread cache with ActionPolicy::PerThreadCache.clear_all
|
39
39
|
module ThreadMemoized
|
40
|
-
require "action_policy/ext/policy_cache_key"
|
41
|
-
using ActionPolicy::Ext::PolicyCacheKey
|
42
|
-
|
43
40
|
class << self
|
44
41
|
def prepended(base)
|
45
42
|
base.prepend InstanceMethods
|
@@ -50,13 +47,12 @@ module ActionPolicy
|
|
50
47
|
|
51
48
|
module InstanceMethods # :nodoc:
|
52
49
|
def policy_for(record:, **opts)
|
53
|
-
__policy_thread_memoize__(record, opts) { super(record: record, **opts) }
|
50
|
+
__policy_thread_memoize__(record, **opts) { super(record: record, **opts) }
|
54
51
|
end
|
55
52
|
end
|
56
53
|
|
57
|
-
def __policy_thread_memoize__(record,
|
58
|
-
|
59
|
-
cache_key = "#{namespace}/#{with}/#{record_key}"
|
54
|
+
def __policy_thread_memoize__(record, **options)
|
55
|
+
cache_key = policy_for_cache_key(record: record, **options)
|
60
56
|
|
61
57
|
ActionPolicy::PerThreadCache.fetch(cache_key) { yield }
|
62
58
|
end
|
@@ -13,19 +13,15 @@ module ActionPolicy
|
|
13
13
|
module ObjectExt
|
14
14
|
def _policy_cache_key(use_object_id: false)
|
15
15
|
return policy_cache_key if respond_to?(:policy_cache_key)
|
16
|
+
return cache_key_with_version if respond_to?(:cache_key_with_version)
|
16
17
|
return cache_key if respond_to?(:cache_key)
|
17
18
|
|
18
|
-
return object_id if use_object_id == true
|
19
|
+
return object_id.to_s if use_object_id == true
|
19
20
|
|
20
21
|
raise ArgumentError, "object is not cacheable"
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
|
-
# JRuby doesn't support _global_ modules refinements (see https://github.com/jruby/jruby/issues/5220)
|
25
|
-
# Fallback to monkey-patching.
|
26
|
-
# TODO: remove after 9.2.7.0 (See https://github.com/jruby/jruby/pull/5627)
|
27
|
-
::Object.include(ObjectExt) if RUBY_PLATFORM =~ /java/i
|
28
|
-
|
29
25
|
refine Object do
|
30
26
|
include ObjectExt
|
31
27
|
end
|
@@ -85,6 +81,12 @@ module ActionPolicy
|
|
85
81
|
to_s
|
86
82
|
end
|
87
83
|
end
|
84
|
+
|
85
|
+
refine Module do
|
86
|
+
def _policy_cache_key(*)
|
87
|
+
name
|
88
|
+
end
|
89
|
+
end
|
88
90
|
end
|
89
91
|
end
|
90
92
|
end
|
@@ -2,15 +2,15 @@
|
|
2
2
|
|
3
3
|
module ActionPolicy
|
4
4
|
module Ext
|
5
|
-
# Add `
|
6
|
-
module
|
5
|
+
# Add `camelize` to Symbol
|
6
|
+
module SymbolCamelize
|
7
7
|
refine Symbol do
|
8
|
-
if "".respond_to?(:
|
9
|
-
def
|
10
|
-
to_s.
|
8
|
+
if "".respond_to?(:camelize)
|
9
|
+
def camelize
|
10
|
+
to_s.camelize
|
11
11
|
end
|
12
12
|
else
|
13
|
-
def
|
13
|
+
def camelize
|
14
14
|
word = to_s.capitalize
|
15
15
|
word.gsub!(/(?:_)([a-z\d]*)/) { $1.capitalize }
|
16
16
|
word
|
@@ -12,8 +12,8 @@ module ActionPolicy
|
|
12
12
|
using ActionPolicy::Ext::StringConstantize
|
13
13
|
end
|
14
14
|
|
15
|
-
require "action_policy/ext/
|
16
|
-
using ActionPolicy::Ext::
|
15
|
+
require "action_policy/ext/symbol_camelize"
|
16
|
+
using ActionPolicy::Ext::SymbolCamelize
|
17
17
|
|
18
18
|
require "action_policy/ext/module_namespace"
|
19
19
|
using ActionPolicy::Ext::ModuleNamespace
|
@@ -45,7 +45,7 @@ module ActionPolicy
|
|
45
45
|
|
46
46
|
def call(record, **opts)
|
47
47
|
chain.each do |probe|
|
48
|
-
val = probe.call(record, opts)
|
48
|
+
val = probe.call(record, **opts)
|
49
49
|
return val unless val.nil?
|
50
50
|
end
|
51
51
|
nil
|
@@ -54,6 +54,7 @@ module ActionPolicy
|
|
54
54
|
private
|
55
55
|
|
56
56
|
def lookup_within_namespace(policy_name, namespace)
|
57
|
+
return unless namespace
|
57
58
|
NamespaceCache.fetch(namespace.name, policy_name) do
|
58
59
|
mod = namespace
|
59
60
|
|
@@ -88,12 +89,12 @@ module ActionPolicy
|
|
88
89
|
!ENV["RACK_ENV"].nil? ? ENV["RACK_ENV"] == "production" : true
|
89
90
|
|
90
91
|
# By self `policy_class` method
|
91
|
-
INSTANCE_POLICY_CLASS = ->(record,
|
92
|
+
INSTANCE_POLICY_CLASS = ->(record, **) {
|
92
93
|
record.policy_class if record.respond_to?(:policy_class)
|
93
94
|
}
|
94
95
|
|
95
96
|
# By record's class `policy_class` method
|
96
|
-
CLASS_POLICY_CLASS = ->(record,
|
97
|
+
CLASS_POLICY_CLASS = ->(record, **) {
|
97
98
|
record.class.policy_class if record.class.respond_to?(:policy_class)
|
98
99
|
}
|
99
100
|
|
@@ -106,7 +107,7 @@ module ActionPolicy
|
|
106
107
|
}
|
107
108
|
|
108
109
|
# Infer from class name
|
109
|
-
INFER_FROM_CLASS = ->(record,
|
110
|
+
INFER_FROM_CLASS = ->(record, **) {
|
110
111
|
policy_class_name_for(record).safe_constantize
|
111
112
|
}
|
112
113
|
|
@@ -114,20 +115,25 @@ module ActionPolicy
|
|
114
115
|
SYMBOL_LOOKUP = ->(record, namespace: nil, **) {
|
115
116
|
next unless record.is_a?(Symbol)
|
116
117
|
|
117
|
-
policy_name = "#{record.
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
118
|
+
policy_name = "#{record.camelize}Policy"
|
119
|
+
lookup_within_namespace(policy_name, namespace) || policy_name.safe_constantize
|
120
|
+
}
|
121
|
+
|
122
|
+
# (Optional) Infer using String#classify if available
|
123
|
+
CLASSIFY_SYMBOL_LOOKUP = ->(record, namespace: nil, **) {
|
124
|
+
next unless record.is_a?(Symbol)
|
125
|
+
|
126
|
+
policy_name = "#{record.to_s.classify}Policy"
|
127
|
+
lookup_within_namespace(policy_name, namespace) || policy_name.safe_constantize
|
123
128
|
}
|
124
129
|
|
125
130
|
self.chain = [
|
126
131
|
SYMBOL_LOOKUP,
|
132
|
+
(CLASSIFY_SYMBOL_LOOKUP if String.method_defined?(:classify)),
|
127
133
|
INSTANCE_POLICY_CLASS,
|
128
134
|
CLASS_POLICY_CLASS,
|
129
135
|
NAMESPACE_LOOKUP,
|
130
136
|
INFER_FROM_CLASS
|
131
|
-
]
|
137
|
+
].compact
|
132
138
|
end
|
133
139
|
end
|