action_policy 0.4.1 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +230 -172
- 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 +210 -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 +15 -33
- 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 +14 -17
- data/lib/action_policy/policy/cache.rb +34 -18
- 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 +30 -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/testing.rb +1 -12
- 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 -77
- 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 -273
- 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 -8
- 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/rails.md
DELETED
@@ -1,120 +0,0 @@
|
|
1
|
-
# Using with Rails
|
2
|
-
|
3
|
-
Action Policy seamlessly integrates with Ruby on Rails applications.
|
4
|
-
|
5
|
-
In most cases, you do not have to do anything except writing policy files and adding `authorize!` calls.
|
6
|
-
|
7
|
-
**NOTE:** both controllers and channels extensions are built on top of the Action Policy [behaviour](./behaviour.md) mixin.
|
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
|
-
|
16
|
-
## Controllers integration
|
17
|
-
|
18
|
-
Action Policy assumes that you have a `current_user` method which specifies the current authenticated subject (`user`).
|
19
|
-
|
20
|
-
You can turn off this behaviour by setting `config.action_policy.controller_authorize_current_user = false` in `application.rb`, or override it:
|
21
|
-
|
22
|
-
```ruby
|
23
|
-
class ApplicationController < ActionController::Base
|
24
|
-
authorize :user, through: :my_current_user
|
25
|
-
end
|
26
|
-
```
|
27
|
-
|
28
|
-
> Read more about [authorization context](authorization_context.md).
|
29
|
-
|
30
|
-
In case you don't want to include Action Policy to controllers at all,
|
31
|
-
you can turn disable the integration by setting `config.action_policy.auto_inject_into_controller = false` in `application.rb`.
|
32
|
-
|
33
|
-
### `verify_authorized` hooks
|
34
|
-
|
35
|
-
Usually, you need all of your actions to be authorized. Action Policy provides a controller hook which ensures that an `authorize!` call has been made during the action:
|
36
|
-
|
37
|
-
```ruby
|
38
|
-
class ApplicationController < ActionController::Base
|
39
|
-
# adds an after_action callback to verify
|
40
|
-
# that `authorize!` has been called.
|
41
|
-
verify_authorized
|
42
|
-
|
43
|
-
# you can also pass additional options,
|
44
|
-
# like with a usual callback
|
45
|
-
verify_authorized except: :index
|
46
|
-
end
|
47
|
-
```
|
48
|
-
|
49
|
-
You can skip this check when necessary:
|
50
|
-
|
51
|
-
```ruby
|
52
|
-
class PostsController < ApplicationController
|
53
|
-
skip_verify_authorized only: :show
|
54
|
-
end
|
55
|
-
```
|
56
|
-
|
57
|
-
When an unauthorized action is encountered, the `ActionPolicy::UnauthorizedAction` error is raised.
|
58
|
-
|
59
|
-
### Resource-less `authorize!`
|
60
|
-
|
61
|
-
You can also call `authorize!` without a resource specified.
|
62
|
-
In that case, Action Policy tries to infer the resource class from the controller name:
|
63
|
-
|
64
|
-
```ruby
|
65
|
-
class PostsController < ApplicationPolicy
|
66
|
-
def index
|
67
|
-
# Uses Post class as a resource implicitly.
|
68
|
-
# NOTE: it just calls `controller_name.classify.safe_constantize`,
|
69
|
-
# you can override this by defining `implicit_authorization_target` method.
|
70
|
-
authorize!
|
71
|
-
end
|
72
|
-
end
|
73
|
-
```
|
74
|
-
|
75
|
-
### Usage with `API` and `Metal` controllers
|
76
|
-
|
77
|
-
Action Policy is only included into `ActionController::Base`. If you want to use it with other base Rails controllers, you have to include it manually:
|
78
|
-
|
79
|
-
```ruby
|
80
|
-
class ApiController < ApplicationController::API
|
81
|
-
include ActionPolicy::Controller
|
82
|
-
|
83
|
-
# NOTE: you have to provide authorization context manually as well
|
84
|
-
authorize :user, through: :current_user
|
85
|
-
end
|
86
|
-
```
|
87
|
-
|
88
|
-
## Channels integration
|
89
|
-
|
90
|
-
Action Policy also integrates with Action Cable to help you authorize your channels actions:
|
91
|
-
|
92
|
-
```ruby
|
93
|
-
class ChatChannel < ApplicationCable::Channel
|
94
|
-
def follow(data)
|
95
|
-
chat = Chat.find(data["chat_id"])
|
96
|
-
|
97
|
-
# Verify against ChatPolicy#show? rule
|
98
|
-
authorize! chat, to: :show?
|
99
|
-
stream_from chat
|
100
|
-
end
|
101
|
-
end
|
102
|
-
```
|
103
|
-
|
104
|
-
Action Policy assumes that you have `current_user` as a connection identifier.
|
105
|
-
|
106
|
-
You can turn off this behaviour by setting `config.action_policy.channel_authorize_current_user = false` in `application.rb`, or override it:
|
107
|
-
|
108
|
-
```ruby
|
109
|
-
module ApplicationCable
|
110
|
-
class Channel < ActionCable::Channel::Base
|
111
|
-
# assuming that identifier is called `user`
|
112
|
-
authorize :user
|
113
|
-
end
|
114
|
-
end
|
115
|
-
```
|
116
|
-
|
117
|
-
> Read more about [authorization context](authorization_context.md).
|
118
|
-
|
119
|
-
In case you do not want to include Action Policy to channels at all,
|
120
|
-
you can disable the integration by setting `config.action_policy.auto_inject_into_channel = false` in `application.rb`.
|
data/docs/reasons.md
DELETED
@@ -1,120 +0,0 @@
|
|
1
|
-
# Failure Reasons
|
2
|
-
|
3
|
-
When you have complex policy rules, it could be helpful to have an ability to define an exact reason for why a specific authorization was rejected.
|
4
|
-
|
5
|
-
It is especially helpful when you compose policies (i.e., use one policy within another) or want
|
6
|
-
to expose permissions to client applications (see [GraphQL](./graphql)).
|
7
|
-
|
8
|
-
Action Policy allows you to track failed `allowed_to?` checks in your rules.
|
9
|
-
|
10
|
-
Consider an example:
|
11
|
-
|
12
|
-
```ruby
|
13
|
-
class ApplicantPolicy < ApplicationPolicy
|
14
|
-
def show?
|
15
|
-
user.has_permission?(:view_applicants) &&
|
16
|
-
allowed_to?(:show?, object.stage)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
```
|
20
|
-
|
21
|
-
When `ApplicantPolicy#show?` check fails, the exception has the `result` object, which in its turn contains additional information about the failure (`reasons`):
|
22
|
-
|
23
|
-
```ruby
|
24
|
-
class ApplicationController < ActionController::Base
|
25
|
-
rescue_from ActionPolicy::Unauthorized do |ex|
|
26
|
-
p ex.result.reasons.details #=> { stage: [:show?] }
|
27
|
-
|
28
|
-
# or with i18n support
|
29
|
-
p ex.result.reasons.full_messages #=> ["You do not have access to the stage"]
|
30
|
-
end
|
31
|
-
end
|
32
|
-
```
|
33
|
-
|
34
|
-
The reason key is the corresponding policy [identifier](writing_policies.md#identifiers).
|
35
|
-
|
36
|
-
You can also wrap _local_ rules into `allowed_to?` to populate reasons:
|
37
|
-
|
38
|
-
```ruby
|
39
|
-
class ApplicantPolicy < ApplicationPolicy
|
40
|
-
def show?
|
41
|
-
allowed_to?(:view_applicants?) &&
|
42
|
-
allowed_to?(:show?, object.stage)
|
43
|
-
end
|
44
|
-
|
45
|
-
def view_applicants?
|
46
|
-
user.has_permission?(:view_applicants)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# then the reasons object could be
|
51
|
-
p ex.result.reasons.details #=> { applicant: [:view_applicants?] }
|
52
|
-
|
53
|
-
# or
|
54
|
-
p ex.result.reasons.details #=> { stage: [:show?] }
|
55
|
-
```
|
56
|
-
|
57
|
-
## Detailed Reasons
|
58
|
-
|
59
|
-
You can provide additional details to your failure reasons by using a `details: { ... }` option:
|
60
|
-
|
61
|
-
```ruby
|
62
|
-
class ApplicantPolicy < ApplicationPolicy
|
63
|
-
def show?
|
64
|
-
allowed_to?(:show?, object.stage)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
class StagePolicy < ApplicationPolicy
|
69
|
-
def show?
|
70
|
-
# Add stage title to the failure reason (if any)
|
71
|
-
# (could be used by client to show more descriptive message)
|
72
|
-
details[:title] = record.title
|
73
|
-
|
74
|
-
# then perform the checks
|
75
|
-
user.stages.where(id: record.id).exists?
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
# when accessing the reasons
|
80
|
-
p ex.result.reasons.details #=> { stage: [{show?: {title: "Onboarding"}] }
|
81
|
-
```
|
82
|
-
|
83
|
-
**NOTE**: when using detailed reasons, the `details` array contains as the last element
|
84
|
-
a hash with ALL details reasons for the policy (in a form of `<rule> => <details>`).
|
85
|
-
|
86
|
-
The additional details are especially helpful when combined with localization, 'cause you can you them as interpolation data source for your translations. For example, for the above policy:
|
87
|
-
|
88
|
-
```yml
|
89
|
-
en:
|
90
|
-
action_policy:
|
91
|
-
policy:
|
92
|
-
stage:
|
93
|
-
show?: "The %{title} stage is not accessible"
|
94
|
-
```
|
95
|
-
|
96
|
-
And then when you call `full_messages`:
|
97
|
-
|
98
|
-
```ruby
|
99
|
-
p ex.result.reasons.full_messages #=> The Onboarding stage is not accessible
|
100
|
-
```
|
101
|
-
|
102
|
-
**P.S. What is the point of failure reasons?**
|
103
|
-
|
104
|
-
Failure reasons helps you to write _actionable_ error messages, i.e. to provide a user with helpful feedback.
|
105
|
-
|
106
|
-
For example, in the above scenario, when the reason is `ApplicantPolicy#view_applicants?`, you could show the following message:
|
107
|
-
|
108
|
-
```
|
109
|
-
You don't have enough permissions to view applicants.
|
110
|
-
Please, ask your manager to update your role.
|
111
|
-
```
|
112
|
-
|
113
|
-
And when the reason is `StagePolicy#show?`:
|
114
|
-
|
115
|
-
```
|
116
|
-
You don't have access to the stage XYZ.
|
117
|
-
Please, ask your manager to grant access to this stage.
|
118
|
-
```
|
119
|
-
|
120
|
-
Much more useful than just showing "You are not authorized to perform this action," isn't it?
|
data/docs/scoping.md
DELETED
@@ -1,255 +0,0 @@
|
|
1
|
-
# Scoping
|
2
|
-
|
3
|
-
By _scoping_ we mean an ability to use policies to _scope data_ (or _filter/modify/transform/choose-your-verb_).
|
4
|
-
|
5
|
-
The most common situation is when you want to _scope_ ActiveRecord relations depending
|
6
|
-
on the current user permissions. Without policies it could look like this:
|
7
|
-
|
8
|
-
```ruby
|
9
|
-
class PostsController < ApplicationController
|
10
|
-
def index
|
11
|
-
@posts =
|
12
|
-
if current_user.admin?
|
13
|
-
Post.all
|
14
|
-
else
|
15
|
-
Post.where(user: current_user)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
```
|
20
|
-
|
21
|
-
That's a very simplified example. In practice scoping rules might be more complex, and it's likely that we would use them in multiple places.
|
22
|
-
|
23
|
-
Action Policy allows you to define scoping rules within a policy class and use them with the help of `authorized_scope` method (`authorized` alias is also available):
|
24
|
-
|
25
|
-
```ruby
|
26
|
-
class PostsController < ApplicationController
|
27
|
-
def index
|
28
|
-
@posts = authorized_scope(Post.all)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class PostPolicy < ApplicationPolicy
|
33
|
-
relation_scope do |relation|
|
34
|
-
next relation if user.admin?
|
35
|
-
relation.where(user: user)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
```
|
39
|
-
|
40
|
-
## Define scopes
|
41
|
-
|
42
|
-
To define scope you should use either `scope_for` or `smth_scope` methods in your policy:
|
43
|
-
|
44
|
-
```ruby
|
45
|
-
class PostPolicy < ApplicationPolicy
|
46
|
-
# define a scope of a `relation` type
|
47
|
-
scope_for :relation do |relation|
|
48
|
-
relation.where(user: user)
|
49
|
-
end
|
50
|
-
|
51
|
-
# define a scope of `my_data` type,
|
52
|
-
# which acts on hashes
|
53
|
-
scope_for :my_data do |data|
|
54
|
-
next data if user.admin?
|
55
|
-
data.delete_if { |k, _| SENSITIVE_KEYS.include?(k) }
|
56
|
-
end
|
57
|
-
end
|
58
|
-
```
|
59
|
-
|
60
|
-
Scopes have _types_: different types of scopes are meant to be applied to different data types.
|
61
|
-
|
62
|
-
You can specify multiple scopes (_named scopes_) for the same type providing a scope name:
|
63
|
-
|
64
|
-
```ruby
|
65
|
-
class EventPolicy < ApplictionPolicy
|
66
|
-
scope_for :relation, :own do |relation|
|
67
|
-
relation.where(owner: user)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
```
|
71
|
-
|
72
|
-
When the second argument is not specified, the `:default` is implied as the scope name.
|
73
|
-
|
74
|
-
Also, there are cases where it might be easier to add options to existing scope than create a new one.
|
75
|
-
|
76
|
-
For example, if you use soft-deletion and your logic inside a scope depends on if deleted records are included, you can add `with_deleted` option:
|
77
|
-
|
78
|
-
```ruby
|
79
|
-
class PostPolicy < ApplicationPolicy
|
80
|
-
scope_for :relation do |relation, with_deleted: false|
|
81
|
-
rel = some_logic(relation)
|
82
|
-
with_deleted ? rel.with_deleted : rel
|
83
|
-
end
|
84
|
-
end
|
85
|
-
```
|
86
|
-
|
87
|
-
You can add as many options as you want:
|
88
|
-
|
89
|
-
```ruby
|
90
|
-
class PostPolicy < ApplicationPolicy
|
91
|
-
scope_for :relation do |relation, with_deleted: false, magic_number: 42, some_required_option:|
|
92
|
-
# Your code
|
93
|
-
end
|
94
|
-
end
|
95
|
-
```
|
96
|
-
## Apply scopes
|
97
|
-
|
98
|
-
Action Policy behaviour (`ActionPolicy::Behaviour`) provides an `authorized` method which allows you to use scoping:
|
99
|
-
|
100
|
-
```ruby
|
101
|
-
class PostsController < ApplicationController
|
102
|
-
def index
|
103
|
-
# The first argument is the target,
|
104
|
-
# which is passed to the scope block
|
105
|
-
#
|
106
|
-
# The second argument is the scope type
|
107
|
-
@posts = authorized_scope(Post, type: :relation)
|
108
|
-
#
|
109
|
-
# For named scopes provide `as` option
|
110
|
-
@events = authorized_scope(Event, type: :relation, as: :own)
|
111
|
-
#
|
112
|
-
# If you want to specify scope options provide `scope_options` option
|
113
|
-
@events = authorized_scope(Event, type: :relation, scope_options: {with_deleted: true})
|
114
|
-
end
|
115
|
-
end
|
116
|
-
```
|
117
|
-
|
118
|
-
You can also specify additional options for policy class inference (see [behaviour docs](behaviour)). For example, to explicitly specify the policy class use:
|
119
|
-
|
120
|
-
```ruby
|
121
|
-
@posts = authorized_scope(Post, with: CustomPostPolicy)
|
122
|
-
```
|
123
|
-
|
124
|
-
## Using scopes within policy
|
125
|
-
|
126
|
-
You can also use scopes within policy classes using the same `authorized_scope` method.
|
127
|
-
For example:
|
128
|
-
|
129
|
-
```ruby
|
130
|
-
relation_scope(:edit) do |scope|
|
131
|
-
teachers = authorized_scope(Teacher.all, as: :edit)
|
132
|
-
scope
|
133
|
-
.joins(:teachers)
|
134
|
-
.where(teacher_id: teachers)
|
135
|
-
end
|
136
|
-
```
|
137
|
-
|
138
|
-
## Using scopes explicitly
|
139
|
-
|
140
|
-
To use scopes without including Action Policy [behaviour](behaviour)
|
141
|
-
do the following:
|
142
|
-
|
143
|
-
```ruby
|
144
|
-
# initialize policy
|
145
|
-
policy = ApplicantPolicy.new(user: user)
|
146
|
-
# apply scope
|
147
|
-
policy.apply_scope(User.all, type: :relation)
|
148
|
-
```
|
149
|
-
|
150
|
-
## Scope type inference
|
151
|
-
|
152
|
-
Action Policy could look up a scope type if it's not specified and if _scope matchers_ were configured.
|
153
|
-
|
154
|
-
Scope matcher is an object that implements `#===` (_case equality_) or a Proc. You can define it within a policy class:
|
155
|
-
|
156
|
-
```ruby
|
157
|
-
class ApplicationPolicy < ActionPolicy::Base
|
158
|
-
scope_matcher :relation, ActiveRecord::Relation
|
159
|
-
|
160
|
-
# use Proc to handle AR models classes
|
161
|
-
scope_matcher :relation, ->(target) { target < ActiveRecord::Base }
|
162
|
-
|
163
|
-
scope_matcher :custom, MyCustomClass
|
164
|
-
end
|
165
|
-
```
|
166
|
-
|
167
|
-
Adding a scope matcher also adds a DSL to define scope rules (just a syntax sugar):
|
168
|
-
|
169
|
-
```ruby
|
170
|
-
class ApplicationPolicy < ActionPolicy::Base
|
171
|
-
scope_matcher :relation, ActiveRecord::Relation
|
172
|
-
|
173
|
-
# now you can define scope rules like this
|
174
|
-
relation_scope { |relation| relation }
|
175
|
-
end
|
176
|
-
```
|
177
|
-
|
178
|
-
When `authorized_scope` is called without the explicit scope type, Action Policy uses matchers (in the order they're defined) to infer the type.
|
179
|
-
|
180
|
-
## Rails integration
|
181
|
-
|
182
|
-
Action Policy provides a couple of _scope matchers_ out-of-the-box for Active Record relations and Action Controller paramters.
|
183
|
-
|
184
|
-
### Active Record scopes
|
185
|
-
|
186
|
-
Scope type `:relation` is automatically applied to the object of `ActiveRecord::Relation` type.
|
187
|
-
|
188
|
-
To define Active Record scopes you can use `relation_scope` macro (which is just an alias for `scope :relation`) in your policy:
|
189
|
-
|
190
|
-
```ruby
|
191
|
-
class PostPolicy < ApplicationPolicy
|
192
|
-
# Equals `scope_for :active_record_relation do ...`
|
193
|
-
relation_scope do |scope|
|
194
|
-
if super_user? || admin?
|
195
|
-
scope
|
196
|
-
else
|
197
|
-
scope.joins(:accesses).where(accesses: {user_id: user.id})
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
# define named scope
|
202
|
-
relation_scope(:own) do |scope|
|
203
|
-
next scope.none if user.guest?
|
204
|
-
scope.where(user: user)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
```
|
208
|
-
|
209
|
-
**NOTE:** the `:active_record_relation` scoping is used if and only if an `ActiveRecord::Relation` is passed to `authorized`:
|
210
|
-
|
211
|
-
```ruby
|
212
|
-
def index
|
213
|
-
# BAD: Post is not a relation; raises an exception
|
214
|
-
@posts = authorized_scope(Post)
|
215
|
-
|
216
|
-
# GOOD:
|
217
|
-
@posts = authorized_scope(Post.all)
|
218
|
-
end
|
219
|
-
```
|
220
|
-
|
221
|
-
### Action Controller parameters
|
222
|
-
|
223
|
-
Use scopes of type `:params` if your strong parameters filterings depend on the current user:
|
224
|
-
|
225
|
-
```ruby
|
226
|
-
class UserPolicy < ApplicationPolicy
|
227
|
-
# Equals to `scope_for :action_controller_params do ...`
|
228
|
-
params_filter do |params|
|
229
|
-
if user.admin?
|
230
|
-
params.permit(:name, :email, :role)
|
231
|
-
else
|
232
|
-
params.permit(:name)
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
params_filter(:update) do |params|
|
237
|
-
params.permit(:name)
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
class UsersController < ApplicationController
|
242
|
-
def create
|
243
|
-
# Call `authorized_scope` on `params` object
|
244
|
-
@user = User.create!(authorized_scope(params.require(:user)))
|
245
|
-
# Or you can use `authorized` alias which fits this case better
|
246
|
-
@user = User.create!(authorized(params.require(:user)))
|
247
|
-
head :ok
|
248
|
-
end
|
249
|
-
|
250
|
-
def update
|
251
|
-
@user.update!(authorized_scope(params.require(:user), as: :update))
|
252
|
-
head :ok
|
253
|
-
end
|
254
|
-
end
|
255
|
-
```
|