action_policy 0.4.3 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +221 -174
- data/LICENSE.txt +1 -1
- data/README.md +7 -11
- data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +62 -0
- data/lib/.rbnext/2.7/action_policy/i18n.rb +56 -0
- data/lib/.rbnext/2.7/action_policy/policy/cache.rb +101 -0
- data/lib/.rbnext/2.7/action_policy/policy/pre_check.rb +162 -0
- data/lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb +89 -0
- data/lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb +124 -0
- data/lib/.rbnext/2.7/action_policy/utils/pretty_print.rb +159 -0
- data/lib/.rbnext/3.0/action_policy/behaviour.rb +115 -0
- data/lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb +62 -0
- data/lib/.rbnext/3.0/action_policy/behaviours/scoping.rb +35 -0
- data/lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb +59 -0
- data/lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb +72 -0
- data/lib/.rbnext/3.0/action_policy/policy/aliases.rb +69 -0
- data/lib/.rbnext/3.0/action_policy/policy/authorization.rb +87 -0
- data/lib/.rbnext/3.0/action_policy/policy/cache.rb +101 -0
- data/lib/.rbnext/3.0/action_policy/policy/core.rb +161 -0
- data/lib/.rbnext/3.0/action_policy/policy/defaults.rb +31 -0
- data/lib/.rbnext/3.0/action_policy/policy/execution_result.rb +37 -0
- data/lib/.rbnext/3.0/action_policy/policy/pre_check.rb +162 -0
- data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +212 -0
- data/lib/.rbnext/3.0/action_policy/policy/scoping.rb +160 -0
- data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +89 -0
- data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +124 -0
- data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +159 -0
- data/lib/.rbnext/3.0/action_policy/utils/suggest_message.rb +19 -0
- data/lib/action_policy.rb +7 -1
- data/lib/action_policy/behaviour.rb +22 -16
- data/lib/action_policy/behaviours/policy_for.rb +10 -3
- data/lib/action_policy/behaviours/scoping.rb +2 -1
- data/lib/action_policy/behaviours/thread_memoized.rb +1 -3
- data/lib/action_policy/ext/module_namespace.rb +1 -6
- data/lib/action_policy/ext/policy_cache_key.rb +10 -30
- data/lib/action_policy/ext/{symbol_classify.rb → symbol_camelize.rb} +6 -6
- data/lib/action_policy/i18n.rb +1 -1
- data/lib/action_policy/lookup_chain.rb +41 -21
- data/lib/action_policy/policy/aliases.rb +7 -12
- data/lib/action_policy/policy/authorization.rb +8 -7
- data/lib/action_policy/policy/cache.rb +11 -17
- data/lib/action_policy/policy/core.rb +25 -12
- data/lib/action_policy/policy/defaults.rb +3 -9
- data/lib/action_policy/policy/execution_result.rb +3 -9
- data/lib/action_policy/policy/pre_check.rb +19 -58
- data/lib/action_policy/policy/reasons.rb +32 -20
- data/lib/action_policy/policy/scoping.rb +5 -6
- data/lib/action_policy/rails/controller.rb +6 -1
- data/lib/action_policy/rails/ext/active_record.rb +7 -0
- data/lib/action_policy/rails/policy/instrumentation.rb +1 -1
- data/lib/action_policy/rspec/be_authorized_to.rb +5 -9
- data/lib/action_policy/rspec/dsl.rb +3 -3
- data/lib/action_policy/rspec/have_authorized_scope.rb +5 -7
- data/lib/action_policy/utils/pretty_print.rb +21 -24
- data/lib/action_policy/utils/suggest_message.rb +1 -3
- data/lib/action_policy/version.rb +1 -1
- data/lib/generators/action_policy/install/templates/{application_policy.rb → application_policy.rb.tt} +1 -1
- data/lib/generators/action_policy/policy/policy_generator.rb +4 -1
- data/lib/generators/action_policy/policy/templates/{policy.rb → policy.rb.tt} +0 -0
- data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
- data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
- metadata +55 -119
- data/.gitattributes +0 -2
- data/.github/FUNDING.yml +0 -1
- data/.github/ISSUE_TEMPLATE.md +0 -18
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
- data/.gitignore +0 -15
- data/.rubocop.yml +0 -54
- data/.tidelift.yml +0 -6
- data/.travis.yml +0 -31
- data/Gemfile +0 -22
- data/Rakefile +0 -27
- data/action_policy.gemspec +0 -44
- data/benchmarks/namespaced_lookup_cache.rb +0 -71
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/docs/.nojekyll +0 -0
- data/docs/CNAME +0 -1
- data/docs/README.md +0 -79
- data/docs/_sidebar.md +0 -27
- data/docs/aliases.md +0 -122
- data/docs/assets/docsify-search.js +0 -364
- data/docs/assets/docsify.min.js +0 -3
- data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
- data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
- data/docs/assets/images/banner.png +0 -0
- data/docs/assets/images/cache.png +0 -0
- data/docs/assets/images/cache.svg +0 -70
- data/docs/assets/images/layer.png +0 -0
- data/docs/assets/images/layer.svg +0 -35
- data/docs/assets/prism-ruby.min.js +0 -1
- data/docs/assets/styles.css +0 -347
- data/docs/assets/vue.min.css +0 -1
- data/docs/authorization_context.md +0 -92
- data/docs/behaviour.md +0 -113
- data/docs/caching.md +0 -291
- data/docs/controller_action_aliases.md +0 -109
- data/docs/custom_lookup_chain.md +0 -48
- data/docs/custom_policy.md +0 -53
- data/docs/debugging.md +0 -55
- data/docs/decorators.md +0 -27
- data/docs/favicon.ico +0 -0
- data/docs/graphql.md +0 -302
- data/docs/i18n.md +0 -44
- data/docs/index.html +0 -43
- data/docs/instrumentation.md +0 -84
- data/docs/lookup_chain.md +0 -17
- data/docs/namespaces.md +0 -77
- data/docs/non_rails.md +0 -28
- data/docs/pre_checks.md +0 -57
- data/docs/pundit_migration.md +0 -80
- data/docs/quick_start.md +0 -118
- data/docs/rails.md +0 -120
- data/docs/reasons.md +0 -120
- data/docs/scoping.md +0 -255
- data/docs/testing.md +0 -333
- data/docs/writing_policies.md +0 -107
- data/gemfiles/jruby.gemfile +0 -8
- data/gemfiles/rails42.gemfile +0 -9
- data/gemfiles/rails6.gemfile +0 -8
- data/gemfiles/railsmaster.gemfile +0 -6
- data/lib/action_policy/ext/string_match.rb +0 -14
- data/lib/action_policy/ext/yield_self_then.rb +0 -25
data/docs/caching.md
DELETED
@@ -1,291 +0,0 @@
|
|
1
|
-
# Caching
|
2
|
-
|
3
|
-
Action Policy aims to be as performant as possible. One of the ways to accomplish that is to include a comprehensive caching system.
|
4
|
-
|
5
|
-
There are several cache layers available: rule-level memoization, local (instance-level) memoization, and _external_ cache (through cache stores).
|
6
|
-
|
7
|
-
<div class="chart-container">
|
8
|
-
<img src="assets/images/cache.svg" alt="Cache layers" width="60%">
|
9
|
-
</div>
|
10
|
-
|
11
|
-
## Policy memoization
|
12
|
-
|
13
|
-
### Per-instance
|
14
|
-
|
15
|
-
There could be a situation when you need to apply the same policy to the same record multiple times during the action (e.g., request). For example:
|
16
|
-
|
17
|
-
```ruby
|
18
|
-
# app/controllers/posts_controller.rb
|
19
|
-
class PostsController < ApplicationController
|
20
|
-
def show
|
21
|
-
@post = Post.find(params[:id])
|
22
|
-
authorize! @post
|
23
|
-
render :show
|
24
|
-
end
|
25
|
-
end
|
26
|
-
```
|
27
|
-
|
28
|
-
```erb
|
29
|
-
# app/views/posts/show.html.erb
|
30
|
-
<h1><%= @post.title %>
|
31
|
-
|
32
|
-
<% if allowed_to?(:edit?, @post) %>
|
33
|
-
<%= link_to "Edit", @post %>
|
34
|
-
<% end %>
|
35
|
-
|
36
|
-
<% if allowed_to?(:destroy?, @post) %>
|
37
|
-
<%= link_to "Delete", @post, method: :delete %>
|
38
|
-
<% end %>
|
39
|
-
```
|
40
|
-
|
41
|
-
In the above example, we need to use the same policy three times. Action Policy re-uses the policy instance to avoid unnecessary object allocation.
|
42
|
-
|
43
|
-
We rely on the following assumptions:
|
44
|
-
- parent object (e.g., a controller instance) is _ephemeral_, i.e., it is a short-lived object
|
45
|
-
- all authorizations use the same [authorization context](authorization_context.md).
|
46
|
-
|
47
|
-
We use `record.policy_cache_key` with fallback to `record.cache_key` or `record.object_id` as a part of policy identifier in the local store.
|
48
|
-
|
49
|
-
**NOTE**: policies memoization is an extension for `ActionPolicy::Behaviour` and could be included with `ActionPolicy::Behaviours::Memoized`.
|
50
|
-
|
51
|
-
**NOTE**: memoization is automatically included into Rails controllers integration, but not included into channels integration, since channels are long-lived objects.
|
52
|
-
|
53
|
-
### Per-thread
|
54
|
-
|
55
|
-
Consider a more complex situation:
|
56
|
-
|
57
|
-
```ruby
|
58
|
-
# app/controllers/comments_controller.rb
|
59
|
-
class CommentsController < ApplicationController
|
60
|
-
def index
|
61
|
-
# all comments for all posts
|
62
|
-
@comments = Comment.all
|
63
|
-
end
|
64
|
-
end
|
65
|
-
```
|
66
|
-
|
67
|
-
```erb
|
68
|
-
# app/views/comments/index.html.erb
|
69
|
-
<% @comments.each do |comment| %>
|
70
|
-
<li><%= comment.text %>
|
71
|
-
<% if allowed_to?(:edit?, comment) %>
|
72
|
-
<%= link_to comment, "Edit" %>
|
73
|
-
<% end %>
|
74
|
-
</li>
|
75
|
-
<% end %>
|
76
|
-
```
|
77
|
-
|
78
|
-
```ruby
|
79
|
-
# app/policies/comment_policy.rb
|
80
|
-
class CommentPolicy < ApplicationPolicy
|
81
|
-
def edit?
|
82
|
-
user.admin? || (user.id == record.id) ||
|
83
|
-
allowed_to?(:manage?, record.post)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
```
|
87
|
-
|
88
|
-
In some cases, we have to initialize **two** policies for each comment: one for the comment itself and one for the comment's post (in the `allowed_to?` call).
|
89
|
-
|
90
|
-
That is an example of a _N+1 authorization_ problem, which in its turn could easily cause a _N+1 query_ problem (if `PostPolicy#manage?` makes database queries). Sounds terrible, doesn't it?
|
91
|
-
|
92
|
-
It is likely that many comments belong to the same post. If so, we can move our memoization one level up and use local thread store.
|
93
|
-
|
94
|
-
Action Policy provides `ActionPolicy::Behaviours::ThreadMemoized` module with this functionality (included into Rails controllers integration by default).
|
95
|
-
|
96
|
-
If you want to add this behavior to your custom authorization-aware class, you should care about cleaning up the thread store manually (by calling `ActionPolicy::PerThreadCache.clear_all`).
|
97
|
-
|
98
|
-
**NOTE:** per-thread cache is disabled by default in test environment (when either `RACK_ENV` or `RAILS_ENV` environment variable is equal to "test").
|
99
|
-
You can turn it on (or off) by setting:
|
100
|
-
|
101
|
-
```ruby
|
102
|
-
ActionPolicy::PerThreadCache.enabled = true # or false to disable
|
103
|
-
```
|
104
|
-
|
105
|
-
## Rule cache
|
106
|
-
|
107
|
-
### Per-instance
|
108
|
-
|
109
|
-
There could be a situation when the same rule is called multiple times for the same policy instance (for example, when using [aliases](aliases.md)).
|
110
|
-
|
111
|
-
In that case, Action Policy invokes the rule method only once, remembers the result, and returns it immediately for the subsequent calls.
|
112
|
-
|
113
|
-
**NOTE**: rule results memoization is available only if you inherit from `ActionPolicy::Base` or include `ActionPolicy::Policy::CachedApply` into your `ApplicationPolicy`.
|
114
|
-
|
115
|
-
### Using the cache store
|
116
|
-
|
117
|
-
Some policy rules might be _performance-heavy_, e.g., make complex database queries.
|
118
|
-
|
119
|
-
In that case, it makes sense to cache the rule application result for a long time (not just for the duration of a request).
|
120
|
-
|
121
|
-
Action Policy provides a way to use _cache stores_ for that. You have to explicitly define which rules you want to cache in your policy class. For example:
|
122
|
-
|
123
|
-
```ruby
|
124
|
-
class StagePolicy < ApplicationPolicy
|
125
|
-
# mark show? rule to be cached
|
126
|
-
cache :show?
|
127
|
-
# you can also provide store-specific options
|
128
|
-
# cache :show?, expires_in: 1.hour
|
129
|
-
|
130
|
-
def show?
|
131
|
-
full_access? ||
|
132
|
-
user.stage_permissions.where(
|
133
|
-
stage_id: record.id
|
134
|
-
).exists?
|
135
|
-
end
|
136
|
-
|
137
|
-
private
|
138
|
-
|
139
|
-
def full_access?
|
140
|
-
!record.funnel.is_private? ||
|
141
|
-
user.permissions
|
142
|
-
.where(
|
143
|
-
funnel_id: record.funnel_id,
|
144
|
-
full_access: true
|
145
|
-
).exists?
|
146
|
-
end
|
147
|
-
end
|
148
|
-
```
|
149
|
-
|
150
|
-
You must configure a cache store to use this feature:
|
151
|
-
|
152
|
-
```ruby
|
153
|
-
ActionPolicy.cache_store = MyCacheStore.new
|
154
|
-
```
|
155
|
-
|
156
|
-
Or, in Rails:
|
157
|
-
|
158
|
-
```ruby
|
159
|
-
# config/application.rb (or config/environments/<environment>.rb)
|
160
|
-
Rails.application.configure do |config|
|
161
|
-
config.action_policy.cache_store = :redis_cache_store
|
162
|
-
end
|
163
|
-
```
|
164
|
-
|
165
|
-
Cache store must provide at least a `#read(key)` and `write(key, value, **options)` methods.
|
166
|
-
|
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
|
-
|
169
|
-
By default, Action Policy builds a cache key using the following scheme (defined in `#rule_cache_key(rule)` method):
|
170
|
-
|
171
|
-
```ruby
|
172
|
-
"#{cache_namespace}/#{context_cache_key}" \
|
173
|
-
"/#{record.policy_cache_key}/#{policy.class.name}/#{rule}"
|
174
|
-
```
|
175
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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).
|
201
|
-
|
202
|
-
#### Invalidation
|
203
|
-
|
204
|
-
There no one-size-fits-all solution for invalidation. It highly depends on your business logic.
|
205
|
-
|
206
|
-
**Case \#1**: no invalidation required.
|
207
|
-
|
208
|
-
First of all, you should try to avoid manual invalidation at all. That could be achieved by using elaborate cache keys.
|
209
|
-
|
210
|
-
Let's consider an example.
|
211
|
-
|
212
|
-
Suppose that your users have _roles_ (i.e. `User.belongs_to :role`) and you give access to resources through the `Access` model (i.e. `Resource.has_many :accesses`).
|
213
|
-
|
214
|
-
Then you can do the following:
|
215
|
-
- Keep tracking the last `Access` added/updated/deleted for resource (e.g. `Access.belongs_to :accessessable, touch: :access_updated_at`)
|
216
|
-
- Use the following cache keys:
|
217
|
-
|
218
|
-
```ruby
|
219
|
-
class User
|
220
|
-
def policy_cache_key
|
221
|
-
"user::#{id}::#{role_id}"
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
class Resource
|
226
|
-
def policy_cache_key
|
227
|
-
"#{resource.class.name}::#{id}::#{access_updated_at}"
|
228
|
-
end
|
229
|
-
end
|
230
|
-
```
|
231
|
-
|
232
|
-
**Case \#2**: discarding all cache at once.
|
233
|
-
|
234
|
-
That's pretty easy: just override `cache_namespace` method in your `ApplicationPolicy` with the new value:
|
235
|
-
|
236
|
-
```ruby
|
237
|
-
class ApplicationPolicy < ActionPolicy::Base
|
238
|
-
# It's a good idea to store the changing part in the constant
|
239
|
-
CACHE_VERSION = "v2".freeze
|
240
|
-
|
241
|
-
# or even from the env variable
|
242
|
-
# CACHE_VERSION = ENV.fetch("POLICY_CACHE_VERSION", "v2").freeze
|
243
|
-
|
244
|
-
def cache_namespace
|
245
|
-
"action_policy::#{CACHE_VERSION}"
|
246
|
-
end
|
247
|
-
end
|
248
|
-
```
|
249
|
-
|
250
|
-
**Case \#3**: discarding some keys.
|
251
|
-
|
252
|
-
That is an alternative approach to _crafting_ cache keys.
|
253
|
-
|
254
|
-
If you have a limited number of places in your application where you update access control,
|
255
|
-
you can invalidate policies cache manually. If your cache store supports `delete_matched` command (deleting keys using a wildcard), you can try the following:
|
256
|
-
|
257
|
-
```ruby
|
258
|
-
class ApplicationPolicy < ActionPolicy::Base
|
259
|
-
# Define custom cache key generator
|
260
|
-
def cache_key(rule)
|
261
|
-
"policy_cache/#{user.id}/#{self.class.name}/#{record.id}/#{rule}"
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
class Access < ApplicationRecord
|
266
|
-
belongs_to :resource
|
267
|
-
belongs_to :user
|
268
|
-
|
269
|
-
after_commit :cleanup_policy_cache, on: [:create, :destroy]
|
270
|
-
|
271
|
-
def cleanup_policy_cache
|
272
|
-
# Clear cache for the corresponding user-record pair
|
273
|
-
ActionPolicy.cache_store.delete_matched(
|
274
|
-
"policy_cache/#{user_id}/#{ResourcePolicy.name}/#{resource_id}/*"
|
275
|
-
)
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
class User < ApplicationRecord
|
280
|
-
belongs_to :role
|
281
|
-
|
282
|
-
after_commit :cleanup_policy_cache, on: [:update], if: :role_id_changed?
|
283
|
-
|
284
|
-
def cleanup_policy_cache
|
285
|
-
# Clear all policies cache for user
|
286
|
-
ActionPolicy.cache_store.delete_matched(
|
287
|
-
"policy_cache/#{user_id}/*"
|
288
|
-
)
|
289
|
-
end
|
290
|
-
end
|
291
|
-
```
|
@@ -1,109 +0,0 @@
|
|
1
|
-
# Controller Action Aliases
|
2
|
-
|
3
|
-
**This is a feature proposed here: https://github.com/palkan/action_policy/issues/25**
|
4
|
-
|
5
|
-
If you'd like to see this feature implemented, please comment on the issue to show your support.
|
6
|
-
|
7
|
-
## Outline
|
8
|
-
|
9
|
-
Say you have abstracted your `authorize!` call to a controller superclass because your policy can
|
10
|
-
be executed without regard to the record in any of the subclass controllers:
|
11
|
-
|
12
|
-
```ruby
|
13
|
-
class AbstractController < ApplicationController
|
14
|
-
authorize :context
|
15
|
-
before_action :authorize_context
|
16
|
-
|
17
|
-
def context
|
18
|
-
# Some code to get your policy context
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def authorize_context
|
24
|
-
authorize! Context
|
25
|
-
end
|
26
|
-
end
|
27
|
-
```
|
28
|
-
|
29
|
-
Your policy might look like this:
|
30
|
-
|
31
|
-
```ruby
|
32
|
-
class ContextPolicy < ApplicationPolicy
|
33
|
-
authorize :context
|
34
|
-
|
35
|
-
alias_rule :index?, :show?, to: :view?
|
36
|
-
alias_rule :new?, :create?, :update?, :destroy?, to: :edit?
|
37
|
-
|
38
|
-
def view?
|
39
|
-
context.has_permission_to(:view, user)
|
40
|
-
end
|
41
|
-
|
42
|
-
def edit?
|
43
|
-
context.has_permission_to(:edit, user)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
```
|
47
|
-
|
48
|
-
We can safely add aliases for the common REST actions in the policy.
|
49
|
-
|
50
|
-
You may then want to include a concern in your subclass controller(s) that add extra actions to the controller.
|
51
|
-
|
52
|
-
|
53
|
-
```ruby
|
54
|
-
class ConcreteController < AbstractController
|
55
|
-
include AdditionalFunctionalityConcern
|
56
|
-
|
57
|
-
def index
|
58
|
-
# Index Action
|
59
|
-
end
|
60
|
-
|
61
|
-
def new
|
62
|
-
# New Action
|
63
|
-
end
|
64
|
-
|
65
|
-
# etc...
|
66
|
-
end
|
67
|
-
```
|
68
|
-
|
69
|
-
At this point you may be wondering how to tell your abstracted policy that these new methods map to either
|
70
|
-
the `view?` or `edit?` rule. You can currently provide the rule to execute to the `authorize!` method with
|
71
|
-
the `to:` parameter but since our call to `authorize!` is in a superclass it has no idea about our concern.
|
72
|
-
I propose the following controller method:
|
73
|
-
|
74
|
-
```ruby
|
75
|
-
alias_action(*actions, to_rule: rule)
|
76
|
-
```
|
77
|
-
|
78
|
-
Here's an example:
|
79
|
-
|
80
|
-
```ruby
|
81
|
-
module AdditionalFunctionalityConcern
|
82
|
-
extend ActiveSupport::Concern
|
83
|
-
|
84
|
-
included do
|
85
|
-
alias_action [:first_action, :second_action], to_rule: :view?
|
86
|
-
alias_action [:third_action], to_rule: :edit?
|
87
|
-
end
|
88
|
-
|
89
|
-
def first_action
|
90
|
-
# First Action
|
91
|
-
end
|
92
|
-
|
93
|
-
def second_action
|
94
|
-
# Second Action
|
95
|
-
end
|
96
|
-
|
97
|
-
def third_action
|
98
|
-
# Third Action
|
99
|
-
end
|
100
|
-
end
|
101
|
-
```
|
102
|
-
|
103
|
-
When `authorize!` is called in a controller, it will first check the action aliases for a corresponding
|
104
|
-
rule. If one is found, it will execute that rule instead of a rule matching the name of the current action.
|
105
|
-
The rule may point at a concrete rule in the policy, or a rule alias in the policy, it doens't matter, the
|
106
|
-
alias in the policy will be resolved like normal.
|
107
|
-
|
108
|
-
If you'd like to see this feature implemented, please show your support on the
|
109
|
-
[Github Issue](https://github.com/palkan/action_policy/issues/25).
|
data/docs/custom_lookup_chain.md
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
# Custom Lookup Chain
|
2
|
-
|
3
|
-
Action Policy's lookup chain is just an array of _probes_ (lambdas with a specific interface).
|
4
|
-
|
5
|
-
The lookup process itself is pretty simple:
|
6
|
-
- Call the first probe;
|
7
|
-
- Return the result if it is not `nil`;
|
8
|
-
- Go to the next probe.
|
9
|
-
|
10
|
-
You can override the default chain with your own. For example:
|
11
|
-
|
12
|
-
```ruby
|
13
|
-
ActionPolicy::LookupChain.chain = [
|
14
|
-
# Probe accepts record as the first argument
|
15
|
-
# and arbitrary options (passed to `authorize!` / `allowed_to?` call)
|
16
|
-
lambda do |record, **options|
|
17
|
-
# your custom lookup logic
|
18
|
-
end
|
19
|
-
]
|
20
|
-
```
|
21
|
-
|
22
|
-
## NullPolicy example
|
23
|
-
|
24
|
-
Let's consider a simple example of extending the existing lookup chain with one more probe.
|
25
|
-
|
26
|
-
Suppose that we want to have a fallback policy (policy used when none found for the resource) instead of raising an `ActionPolicy::NotFound` error.
|
27
|
-
|
28
|
-
Let's call this policy a `NullPolicy`:
|
29
|
-
|
30
|
-
```ruby
|
31
|
-
class NullPolicy < ActionPolicy::Base
|
32
|
-
default_rule :any?
|
33
|
-
|
34
|
-
def any?
|
35
|
-
false
|
36
|
-
end
|
37
|
-
end
|
38
|
-
```
|
39
|
-
|
40
|
-
Here we use the [default rule](aliases.md#default-rule) to handle any rule applied.
|
41
|
-
|
42
|
-
Now we need to add a simple probe to the end of our lookup chain:
|
43
|
-
|
44
|
-
```ruby
|
45
|
-
ActionPolicy::LookupChain.chain << ->(_, _) { NullPolicy }
|
46
|
-
```
|
47
|
-
|
48
|
-
That's it!
|
data/docs/custom_policy.md
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
# Custom Base Policy
|
2
|
-
|
3
|
-
`ActionPolicy::Base` is a combination of all available policy extensions with the default configuration.
|
4
|
-
|
5
|
-
It looks like this:
|
6
|
-
|
7
|
-
|
8
|
-
```ruby
|
9
|
-
class ActionPolicy::Base
|
10
|
-
include ActionPolicy::Policy::Core
|
11
|
-
include ActionPolicy::Policy::Authorization
|
12
|
-
include ActionPolicy::Policy::PreCheck
|
13
|
-
include ActionPolicy::Policy::Reasons
|
14
|
-
include ActionPolicy::Policy::Aliases
|
15
|
-
include ActionPolicy::Policy::Scoping
|
16
|
-
include ActionPolicy::Policy::Cache
|
17
|
-
include ActionPolicy::Policy::CachedApply
|
18
|
-
include ActionPolicy::Policy::Defaults
|
19
|
-
|
20
|
-
# ActionPolicy::Policy::Defaults module adds the following
|
21
|
-
|
22
|
-
authorize :user
|
23
|
-
|
24
|
-
default_rule :manage?
|
25
|
-
alias_rule :new?, to: :create?
|
26
|
-
|
27
|
-
def index?
|
28
|
-
false
|
29
|
-
end
|
30
|
-
|
31
|
-
def create?
|
32
|
-
false
|
33
|
-
end
|
34
|
-
|
35
|
-
def manage?
|
36
|
-
false
|
37
|
-
end
|
38
|
-
end
|
39
|
-
```
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
You can write your `ApplicationPolicy` from scratch instead of inheriting from `ActionPolicy::Base`
|
44
|
-
if the defaults above do not fit your needs. The only required component is `ActionPolicy::Policy::Core`:
|
45
|
-
|
46
|
-
```ruby
|
47
|
-
# minimal ApplicationPolicy
|
48
|
-
class ApplicationPolicy
|
49
|
-
include ActionPolicy::Policy::Core
|
50
|
-
end
|
51
|
-
```
|
52
|
-
|
53
|
-
The `Core` module provides `apply` and `allowed_to?` methods.
|