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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +233 -171
- data/LICENSE.txt +1 -1
- data/README.md +7 -11
- 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 -1
- 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 +30 -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
|
-
```
|