action_policy 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +203 -174
  3. data/README.md +5 -4
  4. data/lib/action_policy.rb +7 -1
  5. data/lib/action_policy/behaviour.rb +22 -16
  6. data/lib/action_policy/behaviours/policy_for.rb +10 -3
  7. data/lib/action_policy/behaviours/scoping.rb +2 -1
  8. data/lib/action_policy/behaviours/thread_memoized.rb +1 -3
  9. data/lib/action_policy/ext/module_namespace.rb +1 -6
  10. data/lib/action_policy/ext/policy_cache_key.rb +10 -30
  11. data/lib/action_policy/i18n.rb +1 -1
  12. data/lib/action_policy/lookup_chain.rb +29 -15
  13. data/lib/action_policy/policy/aliases.rb +7 -12
  14. data/lib/action_policy/policy/authorization.rb +8 -7
  15. data/lib/action_policy/policy/cache.rb +11 -17
  16. data/lib/action_policy/policy/core.rb +25 -12
  17. data/lib/action_policy/policy/defaults.rb +3 -9
  18. data/lib/action_policy/policy/execution_result.rb +3 -9
  19. data/lib/action_policy/policy/pre_check.rb +19 -58
  20. data/lib/action_policy/policy/reasons.rb +29 -19
  21. data/lib/action_policy/policy/scoping.rb +5 -6
  22. data/lib/action_policy/rails/controller.rb +6 -1
  23. data/lib/action_policy/rails/policy/instrumentation.rb +1 -1
  24. data/lib/action_policy/rspec/be_authorized_to.rb +5 -9
  25. data/lib/action_policy/rspec/dsl.rb +1 -1
  26. data/lib/action_policy/rspec/have_authorized_scope.rb +5 -7
  27. data/lib/action_policy/utils/pretty_print.rb +21 -24
  28. data/lib/action_policy/utils/suggest_message.rb +1 -3
  29. data/lib/action_policy/version.rb +1 -1
  30. data/lib/generators/action_policy/install/templates/{application_policy.rb → application_policy.rb.tt} +0 -0
  31. data/lib/generators/action_policy/policy/policy_generator.rb +4 -1
  32. data/lib/generators/action_policy/policy/templates/{policy.rb → policy.rb.tt} +0 -0
  33. data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
  34. data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
  35. metadata +29 -119
  36. data/.gitattributes +0 -2
  37. data/.github/ISSUE_TEMPLATE.md +0 -21
  38. data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
  39. data/.github/bug_report_template.rb +0 -175
  40. data/.gitignore +0 -15
  41. data/.rubocop.yml +0 -54
  42. data/.tidelift.yml +0 -6
  43. data/.travis.yml +0 -31
  44. data/Gemfile +0 -22
  45. data/Rakefile +0 -27
  46. data/action_policy.gemspec +0 -44
  47. data/benchmarks/namespaced_lookup_cache.rb +0 -74
  48. data/benchmarks/pre_checks.rb +0 -73
  49. data/bin/console +0 -14
  50. data/bin/setup +0 -8
  51. data/docs/.nojekyll +0 -0
  52. data/docs/CNAME +0 -1
  53. data/docs/README.md +0 -79
  54. data/docs/_sidebar.md +0 -27
  55. data/docs/aliases.md +0 -122
  56. data/docs/assets/docsify-search.js +0 -364
  57. data/docs/assets/docsify.min.js +0 -3
  58. data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
  59. data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
  60. data/docs/assets/images/banner.png +0 -0
  61. data/docs/assets/images/cache.png +0 -0
  62. data/docs/assets/images/cache.svg +0 -70
  63. data/docs/assets/images/layer.png +0 -0
  64. data/docs/assets/images/layer.svg +0 -35
  65. data/docs/assets/prism-ruby.min.js +0 -1
  66. data/docs/assets/styles.css +0 -347
  67. data/docs/assets/vue.min.css +0 -1
  68. data/docs/authorization_context.md +0 -92
  69. data/docs/behaviour.md +0 -113
  70. data/docs/caching.md +0 -291
  71. data/docs/controller_action_aliases.md +0 -109
  72. data/docs/custom_lookup_chain.md +0 -48
  73. data/docs/custom_policy.md +0 -53
  74. data/docs/debugging.md +0 -55
  75. data/docs/decorators.md +0 -27
  76. data/docs/favicon.ico +0 -0
  77. data/docs/graphql.md +0 -302
  78. data/docs/i18n.md +0 -44
  79. data/docs/index.html +0 -43
  80. data/docs/instrumentation.md +0 -84
  81. data/docs/lookup_chain.md +0 -22
  82. data/docs/namespaces.md +0 -77
  83. data/docs/non_rails.md +0 -28
  84. data/docs/pre_checks.md +0 -57
  85. data/docs/pundit_migration.md +0 -80
  86. data/docs/quick_start.md +0 -118
  87. data/docs/rails.md +0 -120
  88. data/docs/reasons.md +0 -120
  89. data/docs/scoping.md +0 -255
  90. data/docs/testing.md +0 -390
  91. data/docs/writing_policies.md +0 -107
  92. data/gemfiles/jruby.gemfile +0 -8
  93. data/gemfiles/rails42.gemfile +0 -9
  94. data/gemfiles/rails6.gemfile +0 -8
  95. data/gemfiles/railsmaster.gemfile +0 -6
  96. data/lib/action_policy/ext/string_match.rb +0 -14
  97. data/lib/action_policy/ext/yield_self_then.rb +0 -25
@@ -1,44 +0,0 @@
1
- # I18n Support
2
-
3
- `ActionPolicy` integrates with [`i18n`][] to support localizable `full_messages` for [reasons](./reasons.md) and the execution result's `message`:
4
-
5
- ```ruby
6
- class ApplicationController < ActionController::Base
7
- rescue_from ActionPolicy::Unauthorized do |ex|
8
- p ex.result.message #=> "You do not have access to the stage"
9
- p ex.result.reasons.full_messages #=> ["You do not have access to the stage"]
10
- end
11
- end
12
- ```
13
-
14
- The message contains a string for the _rule_ that was called, while `full_messages` contains the list of reasons, why `ActionPolicy::Unauthorized` has been raised. You can find more information about tracking failure reasons [here](./reasons.md).
15
-
16
- ## Configuration
17
-
18
- `ActionPolicy` doesn't provide any localization out-of-the-box and uses "You are not authorized to perform this action" as the default message.
19
-
20
- You can add your app-level default fallback by providing the `action_policy.unauthorized` key value.
21
-
22
- When using **Rails**, all you need is to add translations to any file under the `config/locales` folder (or create a new file, e.g. `config/locales/policies.yml`).
23
-
24
- Non-Rails projets should configure [`i18n`][] gem manually:
25
-
26
- ```ruby
27
- I18n.load_path << Dir[File.expand_path("config/locales") + "/*.yml"]
28
- ```
29
-
30
- ## Translations lookup
31
-
32
- `ActionPolicy` uses the `action_policy` scope. Specific policies translations must be stored inside the `policy` sub-scope.
33
-
34
- The following algorithm is used to find out the translation for a policy with a class `klass` and rule `rule`:
35
- 1. Translation for `"#{klass.identifier}.#{rule}"` key, when `self.identifier =` is not specified then underscored class name without the _Policy_ suffix would be used (e.g. `GuestUserPolicy` turns into `guest_user:` scope)
36
- 2. Repeat step 1 for each ancestor which looks like a policy (`.respond_to?(:identifier)?`) up to `ActionPolicy::Base`
37
- 3. Use `#{rule}` key
38
- 4. Use `en.action_policy.unauthorized` key
39
- 5. Use a default message provided by the gem
40
-
41
- For example, given a `GuestUserPolicy` class which is inherited from `DefaultUserPolicy` and a rule `feed?`, the following list of possible translation keys would be used: `[:"action_policy.policy.guest_user.feed?", :"action_policy.policy.default_user.feed?", :"action_policy.policy.feed?", :"action_policy.unauthorized"]`
42
-
43
-
44
- [`i18n`]: https://github.com/svenfuchs/i18n
@@ -1,43 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <title>Action Policy: authorization framework for Ruby/Rails applications</title>
6
- <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
7
- <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
8
- <meta itemprop="name" content="Action Policy" />
9
- <meta property="og:title" content="Action Policy" />
10
- <meta name="description" content="Athorization framework for Ruby/Rails application" />
11
- <meta itemprop="description" content="Authorization framework for Ruby/Rails application" />
12
- <meta property="og:description" content="Authorization framework for Ruby/Rails application" />
13
- <meta itemprop="image" content="http://actionpolicy.evilmartians.io/assets/images/banner.png" />
14
- <meta property="og:image" content="http://actionpolicy.evilmartians.io/assets/images/banner.png" />
15
- <meta name="twitter:card" content="summary_large_image" />
16
- <meta name="twitter:site" content="@palkan_tula" />
17
- <meta name="twitter:creator" content="@palkan_tula" />
18
- <meta property="og:site_name" content="Action Policy" />
19
- <meta name="keywords" content="ruby, rails, authorization, open-source" />
20
- <meta name="theme-color" content="#CB2028" />
21
- <link rel="stylesheet" href="assets/vue.min.css">
22
- <link rel="stylesheet" href="assets/styles.css">
23
- </head>
24
- <body>
25
- <div id="app"></div>
26
- <script>
27
- window.$docsify = {
28
- name: 'action_policy',
29
- repo: 'https://github.com/palkan/action_policy',
30
- loadSidebar: true,
31
- subMaxLevel: 3,
32
- ga: 'UA-104346673-3',
33
- auto2top: true,
34
- search: {
35
- namespace: 'action-policy'
36
- }
37
- }
38
- </script>
39
- <script src="assets/docsify.min.js"></script>
40
- <script src="assets/docsify-search.js"></script>
41
- <script src="assets/prism-ruby.min.js"></script>
42
- </body>
43
- </html>
@@ -1,84 +0,0 @@
1
- # Instrumentation
2
-
3
- Action Policy integrates with [Rails instrumentation system](https://guides.rubyonrails.org/active_support_instrumentation.html), `ActiveSupport::Notifications`.
4
-
5
- ## Events
6
-
7
- ### `action_policy.apply_rule`
8
-
9
- This event is triggered every time a policy rule is applied:
10
- - when `authorize!` is called
11
- - when `allowed_to?` is called within the policy or the [behaviour](behaviour)
12
- - when `apply_rule` is called explicitly (i.e. `SomePolicy.new(record, context).apply_rule(record)`).
13
-
14
- The event contains the following information:
15
- - `:policy` – policy class name
16
- - `:rule` – applied rule (String)
17
- - `:value` – the result of the rule application (true of false)
18
- - `:cached` – whether we hit the [cache](caching)\*.
19
-
20
- \* This parameter tracks only the cache store usage, not memoization.
21
-
22
- You can use this event to track your policy cache usage and also detect _slow_ checks.
23
-
24
- Here is an example code for sending policy stats to [Librato](https://librato.com/)
25
- using [`librato-rack`](https://github.com/librato/librato-rack):
26
-
27
- ```ruby
28
- ActiveSupport::Notifications.subscribe("action_policy.apply_rule") do |event, started, finished, _, data|
29
- # Track hit and miss events separately (to display two measurements)
30
- measurement = "#{event}.#{(data[:cached] ? "hit" : "miss")}"
31
- # show ms times
32
- timing = ((finished - started) * 1000).to_i
33
- Librato.tracker.check_worker
34
- Librato.timing measurement, timing, percentile: [95, 99]
35
- end
36
- ```
37
-
38
- ### `action_policy.authorize`
39
-
40
- This event is identical to `action_policy.apply_rule` with the one difference:
41
- **it's only triggered when `authorize!` method is called**.
42
-
43
- The motivation behind having a separate event for this method is to monitor the number of failed
44
- authorizations: the high number of failed authorizations usually means that we do not take
45
- into account authorization rules in the application UI (e.g., we show a "Delete" button to the user not
46
- permitted to do that).
47
-
48
- The `action_policy.apply_rule` might have a large number of failures, 'cause it also tracks the usage of non-raising applications (i.e. `allowed_to?`).
49
-
50
- ### `action_policy.init`
51
-
52
- This event is triggered every time a new policy object is initialized.
53
-
54
- The event contains the following information:
55
-
56
- - `:policy` – policy class name.
57
-
58
- This event is useful if you want to track the number of initialized policies per _action_ (for example, when you want to ensure that
59
- the [memoization](caching.md) works as expected).
60
-
61
- ## Turn off instrumentation
62
-
63
- Instrumentation is enabled by default. To turn it off add to your configuration:
64
-
65
- ```ruby
66
- config.action_policy.instrumentation_enabled = false
67
- ```
68
-
69
- **NOTE:** changing this setting after the application has been initialized doesn't take any effect.
70
-
71
- ## Non-Rails usage
72
-
73
- If you don't use Rails itself but have `ActiveSupport::Notifications` available in your application,
74
- you can use the instrumentation feature with some additional configuration:
75
-
76
- ```ruby
77
- # Enable `apply_rule` event by extending the base policy class
78
- require "action_policy/rails/policy/instrumentation"
79
- ActionPolicy::Base.include ActionPolicy::Policy::Rails::Instrumentation
80
-
81
- # Enabled `authorize` event by extending the authorizer class
82
- require "action_policy/rails/authorizer"
83
- ActionPolicy::Authorizer.singleton_class.prepend ActionPolicy::Rails::Authorizer
84
- ```
@@ -1,22 +0,0 @@
1
- # Policy Lookup
2
-
3
- Action Policy tries to automatically infer policy class from the target using the following _probes_:
4
-
5
- 1. If the target is a `Symbol`:
6
-
7
- a) Try `"#{target.to_s.camelize}Policy"` as a `policy_name` (see below);
8
-
9
- b) If `String#classify` is available, e.g. when using Rails' ActiveSupport, try `"#{target.to_s.classify}Policy"`;
10
-
11
- 2. If the target responds to `policy_class`, then use it;
12
- 3. If the target's class responds to `policy_class`, then use it;
13
- 4. If the target or the target's class responds to `policy_name`, then use it (the `policy_name` should end with `Policy` as it's not appended automatically);
14
- 5. Otherwise, use `#{target.class.name}Policy`.
15
-
16
- > \* [Namespaces](namespaces.md) could be also be considered when `namespace` option is set.
17
-
18
- You can call `ActionPolicy.lookup(record, options)` to infer policy class for the record.
19
-
20
- When no policy class is found, an `ActionPolicy::NotFound` error is raised.
21
-
22
- You can [customize lookup](custom_lookup_chain.md) logic if necessary.
@@ -1,77 +0,0 @@
1
- # Namespaces
2
-
3
- Action Policy can lookup policies with respect to the current execution _namespace_ (i.e., authorization class module).
4
-
5
- Consider an example:
6
-
7
- ```ruby
8
- module Admin
9
- class UsersController < ApplicationController
10
- def index
11
- # uses Admin::UserPolicy if any, otherwise fallbacks to UserPolicy
12
- authorize!
13
- end
14
- end
15
- end
16
- ```
17
-
18
- Module nesting is also supported:
19
-
20
- ```ruby
21
- module Admin
22
- module Client
23
- class UsersController < ApplicationController
24
- def index
25
- # lookup for Admin::Client::UserPolicy -> Admin::UserPolicy -> UserPolicy
26
- authorize!
27
- end
28
- end
29
- end
30
- end
31
- ```
32
-
33
- **NOTE**: to support namespaced lookup for non-inferrable resources,
34
- you should specify `policy_name` at a class level (instead of `policy_class`, which doesn't take namespaces into account):
35
-
36
- ```ruby
37
- class Guest < User
38
- def self.policy_name
39
- "UserPolicy"
40
- end
41
- end
42
- ```
43
-
44
- **NOTE**: by default, we use class's name as a policy name; so, for namespaced resources, the namespace part is also included:
45
-
46
- ```ruby
47
- class Admin
48
- class User
49
- end
50
- end
51
-
52
- # search for Admin::UserPolicy, but not for UserPolicy
53
- authorize! Admin::User.new
54
- ```
55
-
56
- You can access the current authorization namespace through `authorization_namespace` method.
57
-
58
- You can also define your own namespacing logic by overriding `authorization_namespace`:
59
-
60
- ```ruby
61
- def authorization_namespace
62
- return ::Admin if current_user.admin?
63
- return ::Staff if current_user.staff?
64
- # fallback to current namespace
65
- super
66
- end
67
- ```
68
-
69
- **NOTE**: namespace support is an extension for `ActionPolicy::Behaviour` and could be included with `ActionPolicy::Behaviours::Namespaced` (included into Rails controllers and channel integrations by default).
70
-
71
- ## Namespace resolution cache
72
-
73
- We cache namespaced policy resolution for better performance (it could affect performance when we look up a policy from a deeply nested module context, see the [benchmark](https://github.com/palkan/action_policy/blob/master/benchmarks/namespaced_lookup_cache.rb)).
74
-
75
- It could be disabled by setting `ActionPolicy::LookupChain.namespace_cache_enabled = false`. It's enabled by default unless `RACK_ENV` env var is specified and is not equal to `"production"` (e.g. when `RACK_ENV=test` the cache is disabled).
76
-
77
- When using Rails it's enabled only in production mode but could be configured through setting the `config.action_policy.namespace_cache_enabled` parameter.
@@ -1,28 +0,0 @@
1
- # Using with Ruby applications
2
-
3
- Action Policy is designed to be independent of any framework and does not have specific dependencies on Ruby on Rails.
4
-
5
- You can [write your policies](writing_policies.md) for non-Rails applications the same way as you would do for Rails applications.
6
-
7
- In order to have `authorize!` / `allowed_to?` / `authorized` methods, you will have to include [`ActionPolicy::Behaviour`](./behaviour.md) into your class (where you want to perform authorization):
8
-
9
- ```ruby
10
- class PostUpdateAction
11
- include ActionPolicy::Behaviour
12
-
13
- # provide authorization subject (performer)
14
- authorize :user
15
-
16
- attr_reader :user
17
-
18
- def initialize(user)
19
- @user = user
20
- end
21
-
22
- def call(post, params)
23
- authorize! post, to: :update?
24
-
25
- post.update!(params)
26
- end
27
- end
28
- ```
@@ -1,57 +0,0 @@
1
- # Pre-Checks
2
-
3
- Consider a typical situation when you start most—or even all—of your rules with the same predicates.
4
-
5
- For example, when you have a super-user role in the application:
6
-
7
- ```ruby
8
- class PostPolicy < ApplicationPolicy
9
- def show?
10
- user.super_admin? || record.published
11
- end
12
-
13
- def update?
14
- user.super_admin? || (user.id == record.user_id)
15
- end
16
-
17
- # more rules
18
- end
19
- ```
20
-
21
- Action Policy allows you to extract the common parts from rules into _pre-checks_:
22
-
23
- ```ruby
24
- class PostPolicy < ApplicationPolicy
25
- pre_check :allow_admins
26
-
27
- def show?
28
- record.published
29
- end
30
-
31
- def update?
32
- user.id == record.user_id
33
- end
34
-
35
- private
36
-
37
- def allow_admins
38
- allow! if user.super_admin?
39
- end
40
- end
41
- ```
42
-
43
- Pre-checks act like _callbacks_: you can add multiple pre-checks, specify `except` and `only` options, and skip already defined pre-checks if necessary:
44
-
45
- ```ruby
46
- class UserPolicy < ApplicationPolicy
47
- skip_pre_check :allow_admins, only: :destroy?
48
-
49
- def destroy?
50
- user.admin? && !record.admin?
51
- end
52
- end
53
- ```
54
-
55
- To halt the authorization process within a pre-check, you must return either `allow!` or `deny!` call value. When any other value is returned, the pre-check is ignored, and the rule is called (or next pre-check).
56
-
57
- **NOTE**: pre-checks are available only if you inherit from `ActionPolicy::Base` or include `ActionPolicy::Policy::PreCheck` into your `ApplicationPolicy`.
@@ -1,80 +0,0 @@
1
- # Migrate from Pundit to Action Policy
2
-
3
- Migration from Pundit to Action Policy could be done in a progressive way: first, we make Pundit polices and authorization helpers use Action Policy under the hood, then you can rewrite policies in the Action Policy way.
4
-
5
- ### Phase 1. Quacking like a Pundit.
6
-
7
- #### Step 1. Prepare controllers.
8
-
9
- - Remove `include Pundit` from ApplicationController
10
-
11
- - Add `authorize` method:
12
-
13
- ```ruby
14
- def authorize(record, rule = nil)
15
- options = {}
16
- options[:to] = rule unless rule.nil?
17
-
18
- authorize! record, **options
19
- end
20
- ```
21
-
22
- - Configure [authorization context](authorization_context) if necessary, e.g. add `authorize :current_user, as: :user` to `ApplicationController` (**NOTE:** added automatically in Rails apps)
23
-
24
- - Add `policy` and `policy_scope` helpers:
25
-
26
- ```ruby
27
- helper_method :policy
28
- helper_method :policy_scope
29
-
30
- def policy(record)
31
- policy_for(record)
32
- end
33
-
34
- def policy_scope(scope)
35
- authorized scope
36
- end
37
-
38
- ```
39
-
40
- **NOTE**: `policy` defined above is not equal to `allowed_to?` since it doesn't take into account pre-checks.
41
-
42
- #### Step 2. Prepare policies.
43
-
44
- We assume that you have a base class for all your policies, e.g. `ApplicationPolicy`.
45
-
46
- Then do the following:
47
- - Add `include ActionPolicy::Policy::Core` to `ApplicationPolicy`
48
-
49
- - Update `ApplicationPolicy#initialize`:
50
-
51
- ```ruby
52
- def initialize(target, user:)
53
- # ...
54
- end
55
- ```
56
-
57
- - [Rewrite scopes](scoping).
58
-
59
- Unfortunately, there is no easy way to migrate Pundit class-based scope to Action Policies scopes.
60
-
61
- #### Step 3. Replace RSpec helper:
62
-
63
- We provide a Pundit-compatibile syntax for RSpec tests:
64
-
65
- ```
66
- # Remove DSL
67
- # require "pundit/rspec"
68
- #
69
- # Add Action Policy Pundit DSL
70
- require "action_policy/rspec/pundit_syntax"
71
- ```
72
-
73
- ### Phase 2. No more Pundit.
74
-
75
- When everything is green, it's time to fully migrate to ActionPolicy:
76
- - make ApplicationPolicy inherit from `ActionPolicy::Base`
77
- - migrate view helpers (from `policy(..)` to `allowed_to?`, from `policy_scope` to `authorized`)
78
- - re-write specs using simple non-DSL syntax (or [Action Policy RSpec syntax](testing#rspec-dsl))
79
- - add [authorization tests](testing#testing-authorization) (add `require 'action_policy/rspec'`)
80
- - use [Reasons](reasons), [I18n integration](i18n), [cache](caching) and other Action Policy features!
@@ -1,118 +0,0 @@
1
- # Quick Start
2
-
3
- ## Installation
4
-
5
- Install Action Policy with RubyGems:
6
-
7
- ```ruby
8
- gem install action_policy
9
- ```
10
-
11
- Or add `action_policy` to your application's `Gemfile`:
12
-
13
- ```ruby
14
- gem "action_policy"
15
- ```
16
-
17
- And then execute:
18
-
19
- $ bundle
20
-
21
- ## Basic usage
22
-
23
- The core component of Action Policy is a _policy class_. Policy class describes how you control access to resources.
24
-
25
- We suggest having a separate policy class for each resource and encourage you to follow these conventions:
26
- - put policies into the `app/policies` folder (when using with Rails);
27
- - name policies using the corresponding singular resource name (model name) with a `Policy` suffix, e.g. `Post -> PostPolicy`;
28
- - name rules using a predicate form of the corresponding activity (typically, a controller's action), e.g. `PostsController#update -> PostPolicy#update?`.
29
-
30
- We also recommend to use an application-specific `ApplicationPolicy` with a global configuration to inherit from:
31
-
32
- ```ruby
33
- class ApplicationPolicy < ActionPolicy::Base
34
- end
35
- ```
36
-
37
- You could use the following command to generate it when using Rails:
38
-
39
- ```sh
40
- rails generate action_policy:install
41
- ```
42
-
43
- **NOTE:** it is not necessary to inherit from `ActionPolicy::Base`; instead, you can [construct basic policy](custom_policy.md) choosing only the components you need.
44
-
45
- Rules must be public methods on the class. Using private methods as rules will raise an error.
46
-
47
- Consider a simple example:
48
-
49
- ```ruby
50
- class PostPolicy < ApplicationPolicy
51
- # everyone can see any post
52
- def show?
53
- true
54
- end
55
-
56
- def update?
57
- # `user` is a performing subject,
58
- # `record` is a target object (post we want to update)
59
- user.admin? || (user.id == record.user_id)
60
- end
61
- end
62
- ```
63
-
64
- Now you can easily add authorization to your Rails\* controller:
65
-
66
- ```ruby
67
- class PostsController < ApplicationController
68
- def update
69
- @post = Post.find(params[:id])
70
- authorize! @post
71
-
72
- if @post.update(post_params)
73
- redirect_to @post
74
- else
75
- render :edit
76
- end
77
- end
78
- end
79
- ```
80
-
81
- \* See [Non-Rails Usage](non_rails.md) on how to add `authorize!` to any Ruby project.
82
-
83
- In the above case, Action Policy automatically infers a policy class and a rule to verify access: `@post -> Post -> PostPolicy`, rule is inferred from the action name (`update -> update?`), and `current_user` is used as `user` within the policy by default (read more about [authorization context](authorization_context.md)).
84
-
85
- When authorization is successful (i.e., the corresponding rule returns `true`), nothing happens, but in case of an authorization failure `ActionPolicy::Unauthorized` error is raised:
86
-
87
- ```ruby
88
- rescue_from ActionPolicy::Unauthorized do |ex|
89
- # Exception object contains the following information
90
- ex.policy #=> policy class, e.g. UserPolicy
91
- ex.rule #=> applied rule, e.g. :show?
92
- end
93
- ```
94
-
95
- There is also an `allowed_to?` method which returns `true` or `false` and could be used, for example, in views:
96
-
97
- ```erb
98
- <% @posts.each do |post| %>
99
- <li><%= post.title %>
100
- <% if allowed_to?(:edit?, post) %>
101
- <%= link_to "Edit", post %>
102
- <% end %>
103
- </li>
104
- <% end %>
105
- ```
106
-
107
- Although Action Policy tries to [infer the corresponding policy class](policy_lookup.md) and rule itself, there could be a situation when you want to specify those values explicitly:
108
-
109
- ```ruby
110
- # specify the rule to verify access
111
- authorize! @post, to: :update?
112
-
113
- # specify policy class
114
- authorize! @post, with: CustomPostPolicy
115
-
116
- # or
117
- allowed_to? :edit?, @post, with: CustomPostPolicy
118
- ```