action_policy 0.2.4 → 0.3.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +26 -64
- data/.travis.yml +13 -10
- data/CHANGELOG.md +216 -1
- data/Gemfile +7 -0
- data/LICENSE.txt +1 -1
- data/Rakefile +10 -0
- data/action_policy.gemspec +5 -3
- data/benchmarks/namespaced_lookup_cache.rb +18 -22
- data/docs/README.md +3 -3
- data/docs/_sidebar.md +4 -0
- data/docs/aliases.md +9 -5
- data/docs/authorization_context.md +59 -1
- data/docs/behaviour.md +113 -0
- data/docs/caching.md +6 -4
- data/docs/custom_policy.md +1 -2
- data/docs/debugging.md +55 -0
- data/docs/decorators.md +27 -0
- data/docs/i18n.md +41 -2
- data/docs/instrumentation.md +70 -2
- data/docs/lookup_chain.md +5 -4
- data/docs/namespaces.md +1 -1
- data/docs/non_rails.md +2 -3
- data/docs/pundit_migration.md +77 -2
- data/docs/quick_start.md +5 -5
- data/docs/rails.md +5 -2
- data/docs/reasons.md +50 -3
- data/docs/scoping.md +262 -0
- data/docs/testing.md +232 -21
- data/docs/writing_policies.md +1 -1
- data/gemfiles/jruby.gemfile +3 -0
- data/gemfiles/rails42.gemfile +3 -0
- data/gemfiles/rails6.gemfile +8 -0
- data/gemfiles/railsmaster.gemfile +1 -1
- data/lib/action_policy.rb +3 -3
- data/lib/action_policy/authorizer.rb +12 -4
- data/lib/action_policy/base.rb +2 -0
- data/lib/action_policy/behaviour.rb +14 -3
- data/lib/action_policy/behaviours/memoized.rb +1 -1
- data/lib/action_policy/behaviours/policy_for.rb +12 -3
- data/lib/action_policy/behaviours/scoping.rb +32 -0
- data/lib/action_policy/behaviours/thread_memoized.rb +1 -1
- data/lib/action_policy/ext/hash_transform_keys.rb +19 -0
- data/lib/action_policy/ext/module_namespace.rb +1 -1
- data/lib/action_policy/ext/policy_cache_key.rb +2 -1
- data/lib/action_policy/ext/proc_case_eq.rb +14 -0
- data/lib/action_policy/ext/string_constantize.rb +1 -0
- data/lib/action_policy/ext/symbol_classify.rb +22 -0
- data/lib/action_policy/i18n.rb +56 -0
- data/lib/action_policy/lookup_chain.rb +21 -3
- data/lib/action_policy/policy/cache.rb +10 -6
- data/lib/action_policy/policy/core.rb +31 -19
- data/lib/action_policy/policy/execution_result.rb +12 -0
- data/lib/action_policy/policy/pre_check.rb +2 -6
- data/lib/action_policy/policy/reasons.rb +99 -12
- data/lib/action_policy/policy/scoping.rb +165 -0
- data/lib/action_policy/rails/authorizer.rb +20 -0
- data/lib/action_policy/rails/controller.rb +4 -14
- data/lib/action_policy/rails/ext/active_record.rb +10 -0
- data/lib/action_policy/rails/policy/instrumentation.rb +24 -0
- data/lib/action_policy/rails/scope_matchers/action_controller_params.rb +19 -0
- data/lib/action_policy/rails/scope_matchers/active_record.rb +29 -0
- data/lib/action_policy/railtie.rb +29 -7
- data/lib/action_policy/rspec.rb +1 -0
- data/lib/action_policy/rspec/be_authorized_to.rb +1 -1
- data/lib/action_policy/rspec/dsl.rb +103 -0
- data/lib/action_policy/rspec/have_authorized_scope.rb +126 -0
- data/lib/action_policy/rspec/pundit_syntax.rb +1 -1
- data/lib/action_policy/test_helper.rb +69 -4
- data/lib/action_policy/testing.rb +54 -0
- data/lib/action_policy/utils/pretty_print.rb +137 -0
- data/lib/action_policy/utils/suggest_message.rb +21 -0
- data/lib/action_policy/version.rb +1 -1
- metadata +58 -11
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
=end
|
3
|
+
#
|
4
|
+
# This benchmark measures the efficiency of NamespaceCache.
|
5
|
+
#
|
6
|
+
# Run it multiple times with cache on/off to see the results:
|
7
|
+
#
|
8
|
+
# $ bundle exec ruby namespaced_lookup_cache.rb
|
9
|
+
# $ bundle exec ruby namespaced_lookup_cache.rb
|
10
|
+
# $ NO_CACHE=1 bundle exec ruby namespaced_lookup_cache.rb
|
11
|
+
# $ NO_CACHE=1 bundle exec ruby namespaced_lookup_cache.rb
|
12
|
+
#
|
15
13
|
|
16
14
|
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
17
15
|
|
@@ -37,7 +35,7 @@ end
|
|
37
35
|
a = A.new
|
38
36
|
b = B.new
|
39
37
|
|
40
|
-
if ENV[
|
38
|
+
if ENV["NO_CACHE"]
|
41
39
|
ActionPolicy::LookupChain.namespace_cache_enabled = false
|
42
40
|
end
|
43
41
|
|
@@ -60,16 +58,14 @@ Benchmark.ips do |x|
|
|
60
58
|
ActionPolicy.lookup(b, namespace: X::Y::Z)
|
61
59
|
end
|
62
60
|
|
63
|
-
x.hold!
|
61
|
+
x.hold! "temp_results"
|
64
62
|
|
65
63
|
x.compare!
|
66
64
|
end
|
67
65
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
no cache B: 42505.4 i/s - 4.20x slower
|
75
|
-
=end
|
66
|
+
#
|
67
|
+
# Comparison:
|
68
|
+
# cache B: 178577.4 i/s
|
69
|
+
# cache A: 173061.4 i/s - same-ish: difference falls within error
|
70
|
+
# no cache A: 97991.7 i/s - same-ish: difference falls within error
|
71
|
+
# no cache B: 42505.4 i/s - 4.20x slower
|
data/docs/README.md
CHANGED
@@ -36,11 +36,11 @@ Why did we decide to build our own authorization gem instead of using the existi
|
|
36
36
|
|
37
37
|
[Pundit][] has been our framework of choice for a long time. Being too _dead-simple_, it required a lot of hacking to fulfill business logic requirements.
|
38
38
|
|
39
|
-
These _hacks_ later
|
39
|
+
These _hacks_ later became Action Policy (initially, we even called it "Pundit, re-visited").
|
40
40
|
|
41
41
|
We also took a few ideas from [CanCanCan][]—such as [default rules and rule aliases](./aliases.md).
|
42
42
|
|
43
|
-
It is also worth noting that Action Policy (despite
|
43
|
+
It is also worth noting that Action Policy (despite having a _Railsy_ name) is designed to be **Rails-free**. On the other hand, it contains some Rails-specific extensions and seamlessly integrates into the framework.
|
44
44
|
|
45
45
|
So, what are the main reasons to consider Action Policy as your authorization tool?
|
46
46
|
|
@@ -50,7 +50,7 @@ So, what are the main reasons to consider Action Policy as your authorization to
|
|
50
50
|
|
51
51
|
- **Code Organization**: use [namespaces](./namespaces.md) to organize your policies (for example, when you have multiple authorization strategies); add [pre-checks](./pre_checks.md) to make rules more readable and better express your business-logic.
|
52
52
|
|
53
|
-
-
|
53
|
+
- **...and more**: [testability](./testing.md), [i18n](./i18n.md) integrations, [actionable errors](./reasons.md).
|
54
54
|
|
55
55
|
Learn more about the motivation behind the Action Policy and its features by watching this [RailsConf talk](https://www.youtube.com/watch?v=NVwx0DARDis).
|
56
56
|
|
data/docs/_sidebar.md
CHANGED
@@ -5,17 +5,21 @@
|
|
5
5
|
* [Non-Rails Usage](non_rails.md)
|
6
6
|
* [Testing](testing.md)
|
7
7
|
* Features
|
8
|
+
* [Authorization Behaviour](behaviour.md)
|
8
9
|
* [Policy Lookup](lookup_chain.md)
|
9
10
|
* [Authorization Context](authorization_context.md)
|
10
11
|
* [Aliases](aliases.md)
|
11
12
|
* [Pre-Checks](pre_checks.md)
|
13
|
+
* [Scoping](scoping.md)
|
12
14
|
* [Caching](caching.md)
|
13
15
|
* [Namespaces](namespaces.md)
|
14
16
|
* [Failure Reasons](reasons.md)
|
15
17
|
* [Instrumentation](instrumentation.md)
|
16
18
|
* [I18n Support](i18n.md)
|
19
|
+
* [Debugging](debugging.md)
|
17
20
|
* Tips & Tricks
|
18
21
|
* [From Pundit to Action Policy](./pundit_migration.md)
|
22
|
+
* [Dealing with Decorators](./decorators.md)
|
19
23
|
* [Controller Action Aliases](controller_action_aliases.md)
|
20
24
|
* Customize
|
21
25
|
* [Base Policy](custom_policy.md)
|
data/docs/aliases.md
CHANGED
@@ -76,11 +76,14 @@ class SuperPolicy < ApplicationPolicy
|
|
76
76
|
|
77
77
|
alias_rule :update?, :destroy?, :create?, to: :edit?
|
78
78
|
|
79
|
-
def manage
|
79
|
+
def manage?
|
80
|
+
end
|
80
81
|
|
81
|
-
def edit
|
82
|
+
def edit?
|
83
|
+
end
|
82
84
|
|
83
|
-
def index
|
85
|
+
def index?
|
86
|
+
end
|
84
87
|
end
|
85
88
|
|
86
89
|
class SubPolicy < AbstractPolicy
|
@@ -88,7 +91,8 @@ class SubPolicy < AbstractPolicy
|
|
88
91
|
|
89
92
|
alias_rule :index?, :update?, to: :manage?
|
90
93
|
|
91
|
-
def create
|
94
|
+
def create?
|
95
|
+
end
|
92
96
|
end
|
93
97
|
```
|
94
98
|
|
@@ -102,7 +106,7 @@ Authorizing against the SuperPolicy:
|
|
102
106
|
* `index?` will resolve to `index?`
|
103
107
|
* `something?` will resolve to `manage?`
|
104
108
|
|
105
|
-
Authorizing against the
|
109
|
+
Authorizing against the SubPolicy:
|
106
110
|
|
107
111
|
* `index?` will resolve to `manage?`
|
108
112
|
* `update?` will resolve to `manage?`
|
@@ -8,7 +8,7 @@ You must configure authorization context in **two places**: in the policy itself
|
|
8
8
|
|
9
9
|
By default, `ActionPolicy::Base` includes `user` as authorization context. If you don't need it, you have to [build your own base policy](custom_policy.md).
|
10
10
|
|
11
|
-
To specify additional contexts, you should use `authorize` method:
|
11
|
+
To specify additional contexts, you should use the `authorize` method:
|
12
12
|
|
13
13
|
```ruby
|
14
14
|
class ApplicationPolicy < ActionPolicy::Base
|
@@ -31,3 +31,61 @@ class ApplicationController < ActionController::Base
|
|
31
31
|
authorize :account, through: :current_account
|
32
32
|
end
|
33
33
|
```
|
34
|
+
|
35
|
+
## Nested Policies vs Contexts
|
36
|
+
|
37
|
+
See also: [action_policy#36](https://github.com/palkan/action_policy/issues/36) and [action_policy#37](https://github.com/palkan/action_policy/pull/37)
|
38
|
+
|
39
|
+
When you call another policy from the policy object (e.g. via `allowed_to?` method),
|
40
|
+
the context of the current policy is passed to the _nested_ policy.
|
41
|
+
|
42
|
+
That means that if the nested policy has a different authorization context, we won't be able
|
43
|
+
to build it (event if you configure all the required keys in the controller).
|
44
|
+
|
45
|
+
For example:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class UserPolicy < ActionPolicy::Base
|
49
|
+
authorize :user
|
50
|
+
|
51
|
+
def show?
|
52
|
+
allowed_to?(:show?, record.profile)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class ProfilePolicy < ActionPolicy::Base
|
57
|
+
authorize :user, :account
|
58
|
+
end
|
59
|
+
|
60
|
+
class ApplicationController < ActionController::Base
|
61
|
+
authorize :user, through: :current_user
|
62
|
+
authorize :account, through: :current_account
|
63
|
+
end
|
64
|
+
|
65
|
+
class UsersController < ApplicationController
|
66
|
+
def show
|
67
|
+
user = User.find(params[:id])
|
68
|
+
|
69
|
+
authorize! user #=> raises "Missing policy authorization context: account"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
That means that **all the policies that could be used together MUST share the same set of authorization contexts** (or at least the _parent_ policies contexts must be subsets of the nested policies contexts).
|
75
|
+
|
76
|
+
|
77
|
+
## Explicit context
|
78
|
+
|
79
|
+
You can override the _implicit_ authorization context (generated with `authorize` method) in-place
|
80
|
+
by passing the `context` option:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
def show
|
84
|
+
user = User.find(params[:id])
|
85
|
+
|
86
|
+
authorize! user, context: {account: user.account}
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
**NOTE:** the explictly provided context is merged with the implicit one (i.e. you can specify
|
91
|
+
only the keys you want to override).
|
data/docs/behaviour.md
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# Action Policy Behaviour
|
2
|
+
|
3
|
+
Action Policy provides a mixin called `ActionPolicy::Behaviour` which adds authorization methods to your classes.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
Let's make our custom _service_ object aware of authorization:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class PostUpdateAction
|
11
|
+
# First, we should include the behaviour
|
12
|
+
include ActionPolicy::Behaviour
|
13
|
+
|
14
|
+
# Secondly, provide authorization subject (performer)
|
15
|
+
authorize :user
|
16
|
+
|
17
|
+
attr_reader :user
|
18
|
+
|
19
|
+
def initialize(user)
|
20
|
+
@user = user
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(post, params)
|
24
|
+
# Now we can use authorization methods
|
25
|
+
authorize! post, to: :update?
|
26
|
+
|
27
|
+
post.update!(params)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
`ActionPolicy::Behaviour` provides `authorize` class-level method to configure [authorization context](authorization_context.md) and the instance-level methods: `authorize!`, `allowed_to?` and `authorized`:
|
33
|
+
|
34
|
+
### `authorize!`
|
35
|
+
|
36
|
+
This is a _guard-method_ which raises an `ActionPolicy::Unauthorized` exception
|
37
|
+
if authorization failed (i.e. policy rule returns false):
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# `to` is a name of the policy rule to apply
|
41
|
+
authorize! post, to: :update?
|
42
|
+
```
|
43
|
+
|
44
|
+
### `allowed_to?`
|
45
|
+
|
46
|
+
This is a _predicate_ version of `authorize!`: it returns true if authorization succeed and false otherwise:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# the first argument is the rule to apply
|
50
|
+
# the second one is the target
|
51
|
+
if allowed_to?(:edit?, post)
|
52
|
+
# ...
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
### `authorized`
|
57
|
+
|
58
|
+
See [scoping](./scoping.md) docs.
|
59
|
+
|
60
|
+
## Policy lookup
|
61
|
+
|
62
|
+
All three instance methods (`authorize!`, `allowed_to?`, `authorized`) uses the same
|
63
|
+
`policy_for` to lookup a policy class for authorization target. So, you can provide additional options to control the policy lookup process:
|
64
|
+
|
65
|
+
- Explicitly specify policy class using `with` option:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
allowed_to?(:edit?, post, with: SpecialPostPolicy)
|
69
|
+
```
|
70
|
+
|
71
|
+
- Provide a [namespace](./namespaces.md):
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# Would try to lookup Admin::PostPolicy first
|
75
|
+
authorize! post, to: :destroy?, namespace: Admin
|
76
|
+
```
|
77
|
+
|
78
|
+
## Implicit authorization target
|
79
|
+
|
80
|
+
You can omit the authorization target for all the methods by defining an _implicit authorization target_:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class PostActions
|
84
|
+
include ActionPolicy::Behaviour
|
85
|
+
|
86
|
+
authorize :user
|
87
|
+
|
88
|
+
attr_reader :user, :post
|
89
|
+
|
90
|
+
def initialize(user, post)
|
91
|
+
@user = user
|
92
|
+
@post = post
|
93
|
+
end
|
94
|
+
|
95
|
+
def update(params)
|
96
|
+
# post is used here implicitly as a target
|
97
|
+
authorize! to: :update
|
98
|
+
|
99
|
+
post.update!(params)
|
100
|
+
end
|
101
|
+
|
102
|
+
def destroy
|
103
|
+
# post is used here implicitly as a target
|
104
|
+
authorize! to: :destroy
|
105
|
+
|
106
|
+
post.destroy!
|
107
|
+
end
|
108
|
+
|
109
|
+
def implicit_authorization_target
|
110
|
+
post
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
data/docs/caching.md
CHANGED
@@ -139,10 +139,10 @@ class StagePolicy < ApplicationPolicy
|
|
139
139
|
def full_access?
|
140
140
|
!record.funnel.is_private? ||
|
141
141
|
user.permissions
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
142
|
+
.where(
|
143
|
+
funnel_id: record.funnel_id,
|
144
|
+
full_access: true
|
145
|
+
).exists?
|
146
146
|
end
|
147
147
|
end
|
148
148
|
```
|
@@ -177,6 +177,8 @@ Where `cache_namespace` is equal to `"acp:#{MAJOR_GEM_VERSION}.#{MINOR_GEM_VERSI
|
|
177
177
|
|
178
178
|
If any object does not respond to `#policy_cache_key`, we fallback to `#cache_key`. If `#cache_key` is not defined, an `ArgumentError` is raised.
|
179
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
|
+
|
180
182
|
You can define your own `cache_key` / `cache_namespace` / `context_cache_key` methods for policy class to override this logic.
|
181
183
|
|
182
184
|
#### Invalidation
|
data/docs/custom_policy.md
CHANGED
@@ -4,7 +4,6 @@
|
|
4
4
|
|
5
5
|
It looks like this:
|
6
6
|
|
7
|
-
<span style="display:none;"># rubocop:disable Style/ClassAndModuleChildren</span>
|
8
7
|
|
9
8
|
```ruby
|
10
9
|
class ActionPolicy::Base
|
@@ -38,7 +37,7 @@ class ActionPolicy::Base
|
|
38
37
|
end
|
39
38
|
```
|
40
39
|
|
41
|
-
|
40
|
+
|
42
41
|
|
43
42
|
You can write your `ApplicationPolicy` from scratch instead of inheriting from `ActionPolicy::Base`
|
44
43
|
if the defaults above do not fit your needs. The only required component is `ActionPolicy::Policy::Core`:
|
data/docs/debugging.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Debug Helpers
|
2
|
+
|
3
|
+
**NOTE:** this functionality requires two additional gems to be available in the app:
|
4
|
+
- [unparser](https://github.com/mbj/unparser)
|
5
|
+
- [method_source](https://github.com/banister/method_source).
|
6
|
+
|
7
|
+
We usually describe policy rules using _boolean expressions_ (e.g. `A or (B and C)` where each of `A`, `B` and `C` is a simple boolean expression or predicate method).
|
8
|
+
|
9
|
+
When dealing with complex policies, it could be hard to figure out which predicate/check made policy to fail.
|
10
|
+
|
11
|
+
The `Policy#pp(rule)` method aims to help debug such situations.
|
12
|
+
|
13
|
+
Consider a (synthetic) example:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
def feed?
|
17
|
+
(admin? || allowed_to?(:access_feed?)) &&
|
18
|
+
(user.name == "Jack" || user.name == "Kate")
|
19
|
+
end
|
20
|
+
|
21
|
+
```
|
22
|
+
|
23
|
+
Suppose that you want to debug this rule ("Why does it return false?").
|
24
|
+
You can drop a [`binding.pry`](https://github.com/deivid-rodriguez/pry-byebug) (or `binding.irb`) right at the beginning of the method:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
def feed?
|
28
|
+
binding.pry # rubocop:disable Lint/Debugger
|
29
|
+
#...
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
Now, run your code and trigger the breakpoint (i.e., run the method):
|
34
|
+
|
35
|
+
```
|
36
|
+
# now you can preview the execution of the rule using the `pp` method (defined on the policy instance)
|
37
|
+
pry> pp :feed?
|
38
|
+
MyPolicy#feed?
|
39
|
+
↳ (
|
40
|
+
admin? #=> false
|
41
|
+
OR
|
42
|
+
allowed_to?(:access_feed?) #=> true
|
43
|
+
)
|
44
|
+
AND
|
45
|
+
(
|
46
|
+
user.name == "Jack" #=> false
|
47
|
+
OR
|
48
|
+
user.name == "Kate" #=> true
|
49
|
+
)
|
50
|
+
|
51
|
+
# you can also check other rules or methods as well
|
52
|
+
pry> pp :admin?
|
53
|
+
MyPolicy#admin?
|
54
|
+
↳ user.admin? #=> false
|
55
|
+
```
|
data/docs/decorators.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Dealing with Decorators
|
2
|
+
|
3
|
+
Ref: [action_policy#7](https://github.com/palkan/action_policy/issues/7).
|
4
|
+
|
5
|
+
Since Action Policy [lookup mechanism](./lookup_chain.md) relies on the target
|
6
|
+
record's class properties (names, methods) it could break when using with _decorators_.
|
7
|
+
|
8
|
+
To make `authorize!` and other [behaviour](./behaviour.md) methods work seamlessly with decorated
|
9
|
+
objects, you might want to _enhance_ the `policy_for` method.
|
10
|
+
|
11
|
+
For example, when using the [Draper](https://github.com/drapergem/draper) gem:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
module ActionPolicy
|
15
|
+
module Draper
|
16
|
+
def policy_for(record:, **opts)
|
17
|
+
# From https://github.com/GoodMeasuresLLC/draper-cancancan/blob/master/lib/draper/cancancan.rb
|
18
|
+
record = record.model while record.is_a?(Draper::Decorator)
|
19
|
+
super(record: record, **opts)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ApplicationController < ActionController::Base
|
25
|
+
prepend ActionPolicy::Draper
|
26
|
+
end
|
27
|
+
```
|