action_policy 0.4.4 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +211 -175
  3. data/README.md +5 -4
  4. data/config/rubocop-rspec.yml +17 -0
  5. data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +62 -0
  6. data/lib/.rbnext/2.7/action_policy/i18n.rb +56 -0
  7. data/lib/.rbnext/2.7/action_policy/policy/cache.rb +101 -0
  8. data/lib/.rbnext/2.7/action_policy/policy/pre_check.rb +162 -0
  9. data/lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb +89 -0
  10. data/lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb +124 -0
  11. data/lib/.rbnext/2.7/action_policy/utils/pretty_print.rb +159 -0
  12. data/lib/.rbnext/3.0/action_policy/behaviour.rb +115 -0
  13. data/lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb +62 -0
  14. data/lib/.rbnext/3.0/action_policy/behaviours/scoping.rb +35 -0
  15. data/lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb +59 -0
  16. data/lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb +72 -0
  17. data/lib/.rbnext/3.0/action_policy/policy/aliases.rb +69 -0
  18. data/lib/.rbnext/3.0/action_policy/policy/authorization.rb +87 -0
  19. data/lib/.rbnext/3.0/action_policy/policy/cache.rb +101 -0
  20. data/lib/.rbnext/3.0/action_policy/policy/core.rb +161 -0
  21. data/lib/.rbnext/3.0/action_policy/policy/defaults.rb +31 -0
  22. data/lib/.rbnext/3.0/action_policy/policy/execution_result.rb +37 -0
  23. data/lib/.rbnext/3.0/action_policy/policy/pre_check.rb +162 -0
  24. data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +212 -0
  25. data/lib/.rbnext/3.0/action_policy/policy/scoping.rb +160 -0
  26. data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +89 -0
  27. data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +124 -0
  28. data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +159 -0
  29. data/lib/.rbnext/3.0/action_policy/utils/suggest_message.rb +19 -0
  30. data/lib/action_policy.rb +7 -1
  31. data/lib/action_policy/behaviour.rb +22 -16
  32. data/lib/action_policy/behaviours/policy_for.rb +10 -3
  33. data/lib/action_policy/behaviours/scoping.rb +2 -1
  34. data/lib/action_policy/behaviours/thread_memoized.rb +1 -3
  35. data/lib/action_policy/ext/module_namespace.rb +1 -6
  36. data/lib/action_policy/ext/policy_cache_key.rb +10 -30
  37. data/lib/action_policy/i18n.rb +1 -1
  38. data/lib/action_policy/lookup_chain.rb +26 -28
  39. data/lib/action_policy/policy/aliases.rb +7 -12
  40. data/lib/action_policy/policy/authorization.rb +8 -7
  41. data/lib/action_policy/policy/cache.rb +11 -17
  42. data/lib/action_policy/policy/core.rb +25 -12
  43. data/lib/action_policy/policy/defaults.rb +3 -9
  44. data/lib/action_policy/policy/execution_result.rb +3 -9
  45. data/lib/action_policy/policy/pre_check.rb +19 -58
  46. data/lib/action_policy/policy/reasons.rb +31 -19
  47. data/lib/action_policy/policy/scoping.rb +5 -6
  48. data/lib/action_policy/rails/controller.rb +6 -1
  49. data/lib/action_policy/rails/policy/instrumentation.rb +1 -1
  50. data/lib/action_policy/rspec/be_authorized_to.rb +5 -9
  51. data/lib/action_policy/rspec/dsl.rb +1 -1
  52. data/lib/action_policy/rspec/have_authorized_scope.rb +5 -7
  53. data/lib/action_policy/utils/pretty_print.rb +21 -24
  54. data/lib/action_policy/utils/suggest_message.rb +1 -3
  55. data/lib/action_policy/version.rb +1 -1
  56. data/lib/generators/action_policy/install/templates/{application_policy.rb → application_policy.rb.tt} +0 -0
  57. data/lib/generators/action_policy/policy/policy_generator.rb +4 -1
  58. data/lib/generators/action_policy/policy/templates/{policy.rb → policy.rb.tt} +0 -0
  59. data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
  60. data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
  61. metadata +55 -119
  62. data/.gitattributes +0 -2
  63. data/.github/ISSUE_TEMPLATE.md +0 -21
  64. data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
  65. data/.github/bug_report_template.rb +0 -175
  66. data/.gitignore +0 -15
  67. data/.rubocop.yml +0 -54
  68. data/.tidelift.yml +0 -6
  69. data/.travis.yml +0 -31
  70. data/Gemfile +0 -22
  71. data/Rakefile +0 -27
  72. data/action_policy.gemspec +0 -44
  73. data/benchmarks/namespaced_lookup_cache.rb +0 -74
  74. data/benchmarks/pre_checks.rb +0 -73
  75. data/bin/console +0 -14
  76. data/bin/setup +0 -8
  77. data/docs/.nojekyll +0 -0
  78. data/docs/CNAME +0 -1
  79. data/docs/README.md +0 -79
  80. data/docs/_sidebar.md +0 -27
  81. data/docs/aliases.md +0 -122
  82. data/docs/assets/docsify-search.js +0 -364
  83. data/docs/assets/docsify.min.js +0 -3
  84. data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
  85. data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
  86. data/docs/assets/images/banner.png +0 -0
  87. data/docs/assets/images/cache.png +0 -0
  88. data/docs/assets/images/cache.svg +0 -70
  89. data/docs/assets/images/layer.png +0 -0
  90. data/docs/assets/images/layer.svg +0 -35
  91. data/docs/assets/prism-ruby.min.js +0 -1
  92. data/docs/assets/styles.css +0 -347
  93. data/docs/assets/vue.min.css +0 -1
  94. data/docs/authorization_context.md +0 -92
  95. data/docs/behaviour.md +0 -113
  96. data/docs/caching.md +0 -291
  97. data/docs/controller_action_aliases.md +0 -109
  98. data/docs/custom_lookup_chain.md +0 -48
  99. data/docs/custom_policy.md +0 -53
  100. data/docs/debugging.md +0 -55
  101. data/docs/decorators.md +0 -27
  102. data/docs/favicon.ico +0 -0
  103. data/docs/graphql.md +0 -302
  104. data/docs/i18n.md +0 -44
  105. data/docs/index.html +0 -43
  106. data/docs/instrumentation.md +0 -84
  107. data/docs/lookup_chain.md +0 -22
  108. data/docs/namespaces.md +0 -77
  109. data/docs/non_rails.md +0 -28
  110. data/docs/pre_checks.md +0 -57
  111. data/docs/pundit_migration.md +0 -80
  112. data/docs/quick_start.md +0 -118
  113. data/docs/rails.md +0 -120
  114. data/docs/reasons.md +0 -120
  115. data/docs/scoping.md +0 -255
  116. data/docs/testing.md +0 -390
  117. data/docs/writing_policies.md +0 -107
  118. data/gemfiles/jruby.gemfile +0 -8
  119. data/gemfiles/rails42.gemfile +0 -9
  120. data/gemfiles/rails6.gemfile +0 -8
  121. data/gemfiles/railsmaster.gemfile +0 -6
  122. data/lib/action_policy/ext/string_match.rb +0 -14
  123. data/lib/action_policy/ext/yield_self_then.rb +0 -25
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/action_policy.svg)](https://badge.fury.io/rb/action_policy)
2
- [![Build Status](https://travis-ci.org/palkan/action_policy.svg?branch=master)](https://travis-ci.org/palkan/action_policy)
2
+ ![Build](https://github.com/palkan/action_policy/workflows/Build/badge.svg)
3
+ ![JRuby Build](https://github.com/palkan/action_policy/workflows/JRuby%20Build/badge.svg)
3
4
  [![Documentation](https://img.shields.io/badge/docs-link-brightgreen.svg)](https://actionpolicy.evilmartians.io)
4
5
 
5
6
  # Action Policy
@@ -21,7 +22,6 @@ Composable. Extensible. Performant.
21
22
 
22
23
  - RailsConf, 2018 "Access Denied" talk ([video](https://www.youtube.com/watch?v=NVwx0DARDis), [slides](https://speakerdeck.com/palkan/railsconf-2018-access-denied-the-missing-guide-to-authorization-in-rails))
23
24
 
24
-
25
25
  ## Integrations
26
26
 
27
27
  - GraphQL Ruby ([`action_policy-graphql`](https://github.com/palkan/action_policy-graphql))
@@ -36,7 +36,9 @@ gem "action_policy", "~> 0.4.0"
36
36
 
37
37
  And then execute:
38
38
 
39
- $ bundle
39
+ ```sh
40
+ bundle install
41
+ ```
40
42
 
41
43
  ## Usage
42
44
 
@@ -89,7 +91,6 @@ end
89
91
 
90
92
  \* See [Non-Rails Usage](docs/non_rails.md) on how to add `authorize!` to any Ruby project.
91
93
 
92
-
93
94
  When authorization is successful (i.e., the corresponding rule returns `true`), nothing happens, but in case of authorization failure `ActionPolicy::Unauthorized` error is raised.
94
95
 
95
96
  There is also an `allowed_to?` method which returns `true` or `false`, and could be used, in views, for example:
@@ -0,0 +1,17 @@
1
+ RSpec:
2
+ Language:
3
+ ExampleGroups:
4
+ Regular:
5
+ - describe_rule
6
+ Focused:
7
+ - fdescribe_rule
8
+ Skipped:
9
+ - xdescribe_rule
10
+ Includes:
11
+ Examples:
12
+ - succeed
13
+ - failed
14
+ - fsucceed
15
+ - ffailed
16
+ - xsucceed
17
+ - xfailed
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPolicy
4
+ module Behaviours
5
+ # Adds `policy_for` method
6
+ module PolicyFor
7
+ require "action_policy/ext/policy_cache_key"
8
+ using ActionPolicy::Ext::PolicyCacheKey
9
+
10
+ # Returns policy instance for the record.
11
+ def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
12
+ policy_class = with || ::ActionPolicy.lookup(
13
+ record,
14
+ **{namespace: namespace, context: context, allow_nil: allow_nil, default: default}
15
+ )
16
+ policy_class&.new(record, **context)
17
+ end
18
+
19
+ def authorization_context
20
+ raise NotImplementedError, "Please, define `authorization_context` method!"
21
+ end
22
+
23
+ def authorization_namespace
24
+ # override to provide specific authorization namespace
25
+ end
26
+
27
+ def default_authorization_policy_class
28
+ # override to provide a policy class use when no policy found
29
+ end
30
+
31
+ # Override this method to provide implicit authorization target
32
+ # that would be used in case `record` is not specified in
33
+ # `authorize!` and `allowed_to?` call.
34
+ #
35
+ # It is also used to infer a policy for scoping (in `authorized_scope` method).
36
+ def implicit_authorization_target
37
+ # no-op
38
+ end
39
+
40
+ # Return implicit authorization target or raises an exception if it's nil
41
+ def implicit_authorization_target!
42
+ implicit_authorization_target || raise(
43
+ NotFound,
44
+ [
45
+ self,
46
+ "Couldn't find implicit authorization target " \
47
+ "for #{self.class}. " \
48
+ "Please, provide policy class explicitly using `with` option or " \
49
+ "define the `implicit_authorization_target` method."
50
+ ]
51
+ )
52
+ end
53
+
54
+ def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **)
55
+ record_key = record._policy_cache_key(use_object_id: true)
56
+ context_key = context.values.map { |_1| _1._policy_cache_key(use_object_id: true) }.join(".")
57
+
58
+ "#{namespace}/#{with}/#{context_key}/#{record_key}"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPolicy
4
+ module I18n # :nodoc:
5
+ DEFAULT_UNAUTHORIZED_MESSAGE = "You are not authorized to perform this action"
6
+
7
+ class << self
8
+ def full_message(policy_class, rule, details = nil)
9
+ candidates = candidates_for(policy_class, rule)
10
+
11
+ options = {scope: :action_policy}
12
+ options.merge!(details) unless details.nil?
13
+
14
+ ::I18n.t(
15
+ candidates.shift,
16
+ default: candidates,
17
+ **options
18
+ )
19
+ end
20
+
21
+ private
22
+
23
+ def candidates_for(policy_class, rule)
24
+ policy_hierarchy = policy_class.ancestors.select { |_1| _1.respond_to?(:identifier) }
25
+ [
26
+ *policy_hierarchy.map { |klass| :"policy.#{klass.identifier}.#{rule}" },
27
+ :"policy.#{rule}",
28
+ :unauthorized,
29
+ DEFAULT_UNAUTHORIZED_MESSAGE
30
+ ]
31
+ end
32
+ end
33
+
34
+ ActionPolicy::Policy::FailureReasons.prepend(Module.new do
35
+ def full_messages
36
+ reasons.flat_map do |policy_klass, rules|
37
+ rules.flat_map do |rule|
38
+ if rule.is_a?(::Hash)
39
+ rule.map do |key, details|
40
+ ActionPolicy::I18n.full_message(policy_klass, key, details)
41
+ end
42
+ else
43
+ ActionPolicy::I18n.full_message(policy_klass, rule)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end)
49
+
50
+ ActionPolicy::Policy::ExecutionResult.prepend(Module.new do
51
+ def message
52
+ ActionPolicy::I18n.full_message(policy, rule, details)
53
+ end
54
+ end)
55
+ end
56
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_policy/version"
4
+
5
+ module ActionPolicy # :nodoc:
6
+ using RubyNext
7
+
8
+ # By default cache namespace (or prefix) contains major and minor version of the gem
9
+ CACHE_NAMESPACE = "acp:#{ActionPolicy::VERSION.split(".").take(2).join(".")}"
10
+
11
+ require "action_policy/ext/policy_cache_key"
12
+
13
+ using ActionPolicy::Ext::PolicyCacheKey
14
+
15
+ module Policy
16
+ # Provides long-lived cache through ActionPolicy.cache_store.
17
+ #
18
+ # NOTE: if cache_store is nil then we silently skip all the caching.
19
+ module Cache
20
+ class << self
21
+ def included(base)
22
+ base.extend ClassMethods
23
+ end
24
+ end
25
+
26
+ def cache_namespace() ; ActionPolicy::CACHE_NAMESPACE; end
27
+
28
+ def cache_key(*parts)
29
+ [
30
+ cache_namespace,
31
+ *parts
32
+ ].map { |_1| _1._policy_cache_key }.join("/")
33
+ end
34
+
35
+ def rule_cache_key(rule)
36
+ cache_key(
37
+ context_cache_key,
38
+ record,
39
+ self.class,
40
+ rule
41
+ )
42
+ end
43
+
44
+ def context_cache_key
45
+ authorization_context.map { |_1, _2| _2._policy_cache_key.to_s }.join("/")
46
+ end
47
+
48
+ def apply_with_cache(rule)
49
+ options = self.class.cached_rules.fetch(rule)
50
+ key = rule_cache_key(rule)
51
+
52
+ ActionPolicy.cache_store.then do |store|
53
+ @result = store.read(key)
54
+ unless result.nil?
55
+ result.cached!
56
+ next result.value
57
+ end
58
+ yield
59
+ store.write(key, result, options)
60
+ result.value
61
+ end
62
+ end
63
+
64
+ def apply(rule)
65
+ return super if ActionPolicy.cache_store.nil? ||
66
+ !self.class.cached_rules.key?(rule)
67
+
68
+ apply_with_cache(rule) { super }
69
+ end
70
+
71
+ def cache(*parts, **options)
72
+ key = cache_key(*parts)
73
+ ActionPolicy.cache_store.then do |store|
74
+ res = store.read(key)
75
+ next res unless res.nil?
76
+ res = yield
77
+ store.write(key, res, options)
78
+ res
79
+ end
80
+ end
81
+
82
+ module ClassMethods # :nodoc:
83
+ def cache(*rules, **options)
84
+ rules.each do |rule|
85
+ cached_rules[rule] = options
86
+ end
87
+ end
88
+
89
+ def cached_rules
90
+ return @cached_rules if instance_variable_defined?(:@cached_rules)
91
+
92
+ @cached_rules = if superclass.respond_to?(:cached_rules)
93
+ superclass.cached_rules.dup
94
+ else
95
+ {}
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPolicy
4
+ module Policy
5
+ # Adds callback-style checks to policies to
6
+ # extract common checks from rules.
7
+ #
8
+ # class ApplicationPolicy < ActionPolicy::Base
9
+ # authorize :user
10
+ # pre_check :allow_admins
11
+ #
12
+ # private
13
+ # # Allow every action for admins
14
+ # def allow_admins
15
+ # allow! if user.admin?
16
+ # end
17
+ # end
18
+ #
19
+ # You can specify conditional pre-checks (through `except` / `only`) options
20
+ # and skip already defined pre-checks if necessary.
21
+ #
22
+ # class UserPolicy < ApplicationPolicy
23
+ # skip_pre_check :allow_admins, only: :destroy?
24
+ #
25
+ # def destroy?
26
+ # user.admin? && !record.admin?
27
+ # end
28
+ # end
29
+ module PreCheck
30
+ # Single pre-check instance.
31
+ #
32
+ # Implements filtering logic.
33
+ class Check
34
+ attr_reader :name, :policy_class
35
+
36
+ def initialize(policy, name, except: nil, only: nil)
37
+ if !except.nil? && !only.nil?
38
+ raise ArgumentError,
39
+ "Only one of `except` and `only` may be specified for pre-check"
40
+ end
41
+
42
+ @policy_class = policy
43
+ @name = name
44
+ @blacklist = Array(except) unless except.nil?
45
+ @whitelist = Array(only) unless only.nil?
46
+
47
+ rebuild_filter
48
+ end
49
+
50
+ def applicable?(rule)
51
+ return true if filter.nil?
52
+ filter.call(rule)
53
+ end
54
+
55
+ def call(policy) ; policy.send(name); end
56
+
57
+ def skip!(except: nil, only: nil)
58
+ if !except.nil? && !only.nil?
59
+ raise ArgumentError,
60
+ "Only one of `except` and `only` may be specified when skipping pre-check"
61
+ end
62
+
63
+ if except.nil? && only.nil?
64
+ raise ArgumentError,
65
+ "At least one of `except` and `only` must be specified when skipping pre-check"
66
+ end
67
+
68
+ if except
69
+ @whitelist = Array(except)
70
+ @whitelist -= blacklist if blacklist
71
+ @blacklist = nil
72
+ else
73
+ # only
74
+ @blacklist += Array(only) if blacklist
75
+ @whitelist -= Array(only) if whitelist
76
+ @blacklist = Array(only) if filter.nil?
77
+ end
78
+
79
+ rebuild_filter
80
+ end
81
+ # rubocop: enable
82
+ # rubocop: enable
83
+
84
+ def dup
85
+ self.class.new(
86
+ policy_class,
87
+ name,
88
+ except: blacklist&.dup,
89
+ only: whitelist&.dup
90
+ )
91
+ end
92
+
93
+ private
94
+
95
+ attr_reader :whitelist, :blacklist, :filter
96
+
97
+ def rebuild_filter
98
+ @filter =
99
+ if whitelist
100
+ proc { |rule| whitelist.include?(rule) }
101
+ elsif blacklist
102
+ proc { |rule| !blacklist.include?(rule) }
103
+ end
104
+ end
105
+ end
106
+
107
+ class << self
108
+ def included(base)
109
+ base.extend ClassMethods
110
+ end
111
+ end
112
+
113
+ def run_pre_checks(rule)
114
+ self.class.pre_checks.each do |check|
115
+ next unless check.applicable?(rule)
116
+ check.call(self)
117
+ end
118
+
119
+ yield if block_given?
120
+ end
121
+
122
+ def __apply__(rule)
123
+ run_pre_checks(rule) { super }
124
+ end
125
+
126
+ module ClassMethods # :nodoc:
127
+ def pre_check(*names, **options)
128
+ names.each do |name|
129
+ # do not allow pre-check override
130
+ check = pre_checks.find { |_1| _1.name == name }
131
+ raise "Pre-check already defined: #{name}" unless check.nil?
132
+
133
+ pre_checks << Check.new(self, name, **options)
134
+ end
135
+ end
136
+
137
+ def skip_pre_check(*names, **options)
138
+ names.each do |name|
139
+ check = pre_checks.find { |_1| _1.name == name }
140
+ raise "Pre-check not found: #{name}" if check.nil?
141
+
142
+ # when no options provided we remove this check completely
143
+ next pre_checks.delete(check) if options.empty?
144
+
145
+ # otherwise duplicate and apply skip options
146
+ pre_checks[pre_checks.index(check)] = check.dup.tap { |_1| _1.skip!(**options) }
147
+ end
148
+ end
149
+
150
+ def pre_checks
151
+ return @pre_checks if instance_variable_defined?(:@pre_checks)
152
+
153
+ @pre_checks = if superclass.respond_to?(:pre_checks)
154
+ superclass.pre_checks.dup
155
+ else
156
+ []
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_policy/testing"
4
+
5
+ module ActionPolicy
6
+ module RSpec
7
+ # Authorization matcher `be_authorized_to`.
8
+ #
9
+ # Verifies that a block of code has been authorized using specific policy.
10
+ #
11
+ # Example:
12
+ #
13
+ # # in controller/request specs
14
+ # subject { patch :update, id: product.id }
15
+ #
16
+ # it "is authorized" do
17
+ # expect { subject }
18
+ # .to be_authorized_to(:manage?, product)
19
+ # .with(ProductPolicy)
20
+ # end
21
+ #
22
+ class BeAuthorizedTo < ::RSpec::Matchers::BuiltIn::BaseMatcher
23
+ attr_reader :rule, :target, :policy, :actual_calls
24
+
25
+ def initialize(rule, target)
26
+ @rule = rule
27
+ @target = target
28
+ end
29
+
30
+ def with(policy)
31
+ @policy = policy
32
+ self
33
+ end
34
+
35
+ def match(_expected, actual)
36
+ raise "This matcher only supports block expectations" unless actual.is_a?(Proc)
37
+
38
+ @policy ||= ::ActionPolicy.lookup(target)
39
+
40
+ begin
41
+ ActionPolicy::Testing::AuthorizeTracker.tracking { actual.call }
42
+ rescue ActionPolicy::Unauthorized
43
+ # we don't want to care about authorization result
44
+ end
45
+
46
+ @actual_calls = ActionPolicy::Testing::AuthorizeTracker.calls
47
+
48
+ actual_calls.any? { |_1| _1.matches?(policy, rule, target) }
49
+ end
50
+
51
+ def does_not_match?(*)
52
+ raise "This matcher doesn't support negation"
53
+ end
54
+
55
+ def supports_block_expectations?() ; true; end
56
+
57
+ def failure_message
58
+ "expected #{formatted_record} " \
59
+ "to be authorized with #{policy}##{rule}, " \
60
+ "but #{actual_calls_message}"
61
+ end
62
+
63
+ def actual_calls_message
64
+ if actual_calls.empty?
65
+ "no authorization calls have been made"
66
+ else
67
+ "the following calls were encountered:\n" \
68
+ "#{formatted_calls}"
69
+ end
70
+ end
71
+
72
+ def formatted_calls
73
+ actual_calls.map do |_1|
74
+ " - #{_1.inspect}"
75
+ end.join("\n")
76
+ end
77
+
78
+ def formatted_record(record = target) ; ::RSpec::Support::ObjectFormatter.format(record); end
79
+ end
80
+ end
81
+ end
82
+
83
+ RSpec.configure do |config|
84
+ config.include(Module.new do
85
+ def be_authorized_to(rule, target)
86
+ ActionPolicy::RSpec::BeAuthorizedTo.new(rule, target)
87
+ end
88
+ end)
89
+ end