action_policy 0.4.4 → 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 +203 -174
- data/README.md +5 -4
- 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 +10 -30
- data/lib/action_policy/i18n.rb +1 -1
- data/lib/action_policy/lookup_chain.rb +29 -15
- data/lib/action_policy/policy/aliases.rb +7 -12
- data/lib/action_policy/policy/authorization.rb +8 -7
- data/lib/action_policy/policy/cache.rb +11 -17
- 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 +29 -19
- data/lib/action_policy/policy/scoping.rb +5 -6
- data/lib/action_policy/rails/controller.rb +6 -1
- 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 +1 -1
- data/lib/action_policy/rspec/have_authorized_scope.rb +5 -7
- 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} +0 -0
- 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 +29 -119
- data/.gitattributes +0 -2
- data/.github/ISSUE_TEMPLATE.md +0 -21
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
- data/.github/bug_report_template.rb +0 -175
- 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 -74
- data/benchmarks/pre_checks.rb +0 -73
- 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 -79
- 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 -291
- 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 -22
- 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 -390
- data/docs/writing_policies.md +0 -107
- data/gemfiles/jruby.gemfile +0 -8
- data/gemfiles/rails42.gemfile +0 -9
- 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
@@ -11,6 +11,8 @@ unless "".respond_to?(:underscore)
|
|
11
11
|
end
|
12
12
|
|
13
13
|
module ActionPolicy
|
14
|
+
using RubyNext
|
15
|
+
|
14
16
|
# Raised when `resolve_rule` failed to find an approriate
|
15
17
|
# policy rule method for the activity
|
16
18
|
class UnknownRule < Error
|
@@ -75,15 +77,28 @@ module ActionPolicy
|
|
75
77
|
# `apply` also calls pre-checks.
|
76
78
|
def apply(rule)
|
77
79
|
@result = self.class.result_class.new(self.class, rule)
|
78
|
-
|
80
|
+
|
81
|
+
catch :policy_fulfilled do
|
82
|
+
result.load __apply__(rule)
|
83
|
+
end
|
84
|
+
|
85
|
+
result.value
|
86
|
+
end
|
87
|
+
|
88
|
+
def deny!
|
89
|
+
result&.load false
|
90
|
+
throw :policy_fulfilled
|
91
|
+
end
|
92
|
+
|
93
|
+
def allow!
|
94
|
+
result&.load true
|
95
|
+
throw :policy_fulfilled
|
79
96
|
end
|
80
97
|
|
81
98
|
# This method performs the rule call.
|
82
99
|
# Override or extend it to provide custom functionality
|
83
100
|
# (such as caching, pre checks, etc.)
|
84
|
-
def __apply__(rule)
|
85
|
-
public_send(rule)
|
86
|
-
end
|
101
|
+
def __apply__(rule) = public_send(rule)
|
87
102
|
|
88
103
|
# Wrap code that could modify result
|
89
104
|
# to prevent the current result modification
|
@@ -102,16 +117,16 @@ module ActionPolicy
|
|
102
117
|
# If record is `nil` then we uses the current policy.
|
103
118
|
def allowed_to?(rule, record = :__undef__, **options)
|
104
119
|
if (record == :__undef__ || record == self.record) && options.empty?
|
105
|
-
__apply__(rule)
|
120
|
+
__apply__(resolve_rule(rule))
|
106
121
|
else
|
107
|
-
policy_for(record: record, **options).
|
122
|
+
policy_for(record: record, **options).then do |policy|
|
123
|
+
policy.apply(policy.resolve_rule(rule))
|
124
|
+
end
|
108
125
|
end
|
109
126
|
end
|
110
127
|
|
111
128
|
# An alias for readability purposes
|
112
|
-
def check?(*args)
|
113
|
-
allowed_to?(*args)
|
114
|
-
end
|
129
|
+
def check?(*args) = allowed_to?(*args)
|
115
130
|
|
116
131
|
# Returns a rule name (policy method name) for activity.
|
117
132
|
#
|
@@ -127,9 +142,7 @@ module ActionPolicy
|
|
127
142
|
# Return annotated source code for the rule
|
128
143
|
# NOTE: require "method_source" and "unparser" gems to be installed.
|
129
144
|
# Otherwise returns empty string.
|
130
|
-
def inspect_rule(rule)
|
131
|
-
PrettyPrint.print_method(self, rule)
|
132
|
-
end
|
145
|
+
def inspect_rule(rule) = PrettyPrint.print_method(self, rule)
|
133
146
|
|
134
147
|
# Helper for printing the annotated rule source.
|
135
148
|
# Useful for debugging: type `pp :show?` within the context of the policy
|
@@ -21,17 +21,11 @@ module ActionPolicy
|
|
21
21
|
base.authorize :user
|
22
22
|
end
|
23
23
|
|
24
|
-
def index?
|
25
|
-
false
|
26
|
-
end
|
24
|
+
def index?() = false
|
27
25
|
|
28
|
-
def create?
|
29
|
-
false
|
30
|
-
end
|
26
|
+
def create?() = false
|
31
27
|
|
32
|
-
def manage?
|
33
|
-
false
|
34
|
-
end
|
28
|
+
def manage?() = false
|
35
29
|
end
|
36
30
|
end
|
37
31
|
end
|
@@ -19,21 +19,15 @@ module ActionPolicy
|
|
19
19
|
@value = value
|
20
20
|
end
|
21
21
|
|
22
|
-
def success?
|
23
|
-
@value == true
|
24
|
-
end
|
22
|
+
def success?() = @value == true
|
25
23
|
|
26
|
-
def fail?
|
27
|
-
@value == false
|
28
|
-
end
|
24
|
+
def fail?() = @value == false
|
29
25
|
|
30
26
|
def cached!
|
31
27
|
@cached = true
|
32
28
|
end
|
33
29
|
|
34
|
-
def cached?
|
35
|
-
@cached == true
|
36
|
-
end
|
30
|
+
def cached?() = @cached == true
|
37
31
|
|
38
32
|
def inspect
|
39
33
|
"<#{policy}##{rule}: #{@value}>"
|
@@ -31,40 +31,12 @@ module ActionPolicy
|
|
31
31
|
#
|
32
32
|
# Implements filtering logic.
|
33
33
|
class Check
|
34
|
-
# Wrapper over check result
|
35
|
-
class Result
|
36
|
-
DENY = :__deny__
|
37
|
-
ALLOW = :__allow__
|
38
|
-
|
39
|
-
KINDS = {
|
40
|
-
DENY => false,
|
41
|
-
ALLOW => true
|
42
|
-
}.freeze
|
43
|
-
|
44
|
-
attr_reader :fulfilled
|
45
|
-
|
46
|
-
def initialize(val)
|
47
|
-
@fulfilled = KINDS.key?(val)
|
48
|
-
@value = val
|
49
|
-
end
|
50
|
-
|
51
|
-
alias fulfilled? fulfilled
|
52
|
-
|
53
|
-
def denied?
|
54
|
-
@value == DENY
|
55
|
-
end
|
56
|
-
|
57
|
-
def value
|
58
|
-
KINDS.fetch(@value)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
34
|
attr_reader :name, :policy_class
|
63
35
|
|
64
36
|
def initialize(policy, name, except: nil, only: nil)
|
65
37
|
if !except.nil? && !only.nil?
|
66
38
|
raise ArgumentError,
|
67
|
-
|
39
|
+
"Only one of `except` and `only` may be specified for pre-check"
|
68
40
|
end
|
69
41
|
|
70
42
|
@policy_class = policy
|
@@ -80,23 +52,17 @@ module ActionPolicy
|
|
80
52
|
filter.call(rule)
|
81
53
|
end
|
82
54
|
|
83
|
-
def call(policy)
|
84
|
-
Result.new(policy.send(name)).tap do |res|
|
85
|
-
# add denial reason if Reasons included
|
86
|
-
policy.result.reasons.add(policy, name) if
|
87
|
-
res.denied? && policy.result.respond_to?(:reasons)
|
88
|
-
end
|
89
|
-
end
|
55
|
+
def call(policy) = policy.send(name)
|
90
56
|
|
91
57
|
def skip!(except: nil, only: nil)
|
92
58
|
if !except.nil? && !only.nil?
|
93
59
|
raise ArgumentError,
|
94
|
-
|
60
|
+
"Only one of `except` and `only` may be specified when skipping pre-check"
|
95
61
|
end
|
96
62
|
|
97
63
|
if except.nil? && only.nil?
|
98
64
|
raise ArgumentError,
|
99
|
-
|
65
|
+
"At least one of `except` and `only` must be specified when skipping pre-check"
|
100
66
|
end
|
101
67
|
|
102
68
|
if except
|
@@ -116,7 +82,12 @@ module ActionPolicy
|
|
116
82
|
# rubocop: enable
|
117
83
|
|
118
84
|
def dup
|
119
|
-
self.class.new(
|
85
|
+
self.class.new(
|
86
|
+
policy_class,
|
87
|
+
name,
|
88
|
+
except: blacklist&.dup,
|
89
|
+
only: whitelist&.dup
|
90
|
+
)
|
120
91
|
end
|
121
92
|
|
122
93
|
private
|
@@ -142,21 +113,12 @@ module ActionPolicy
|
|
142
113
|
def run_pre_checks(rule)
|
143
114
|
self.class.pre_checks.each do |check|
|
144
115
|
next unless check.applicable?(rule)
|
145
|
-
|
146
|
-
return res.value if res.fulfilled?
|
116
|
+
check.call(self)
|
147
117
|
end
|
148
118
|
|
149
119
|
yield if block_given?
|
150
120
|
end
|
151
121
|
|
152
|
-
def deny!
|
153
|
-
Check::Result::DENY
|
154
|
-
end
|
155
|
-
|
156
|
-
def allow!
|
157
|
-
Check::Result::ALLOW
|
158
|
-
end
|
159
|
-
|
160
122
|
def __apply__(rule)
|
161
123
|
run_pre_checks(rule) { super }
|
162
124
|
end
|
@@ -165,7 +127,7 @@ module ActionPolicy
|
|
165
127
|
def pre_check(*names, **options)
|
166
128
|
names.each do |name|
|
167
129
|
# do not allow pre-check override
|
168
|
-
check = pre_checks.find {
|
130
|
+
check = pre_checks.find { _1.name == name }
|
169
131
|
raise "Pre-check already defined: #{name}" unless check.nil?
|
170
132
|
|
171
133
|
pre_checks << Check.new(self, name, **options)
|
@@ -174,26 +136,25 @@ module ActionPolicy
|
|
174
136
|
|
175
137
|
def skip_pre_check(*names, **options)
|
176
138
|
names.each do |name|
|
177
|
-
check = pre_checks.find {
|
139
|
+
check = pre_checks.find { _1.name == name }
|
178
140
|
raise "Pre-check not found: #{name}" if check.nil?
|
179
141
|
|
180
142
|
# when no options provided we remove this check completely
|
181
143
|
next pre_checks.delete(check) if options.empty?
|
182
144
|
|
183
145
|
# otherwise duplicate and apply skip options
|
184
|
-
pre_checks[pre_checks.index(check)] = check.dup.tap {
|
146
|
+
pre_checks[pre_checks.index(check)] = check.dup.tap { _1.skip!(**options) }
|
185
147
|
end
|
186
148
|
end
|
187
149
|
|
188
150
|
def pre_checks
|
189
151
|
return @pre_checks if instance_variable_defined?(:@pre_checks)
|
190
152
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
153
|
+
if superclass.respond_to?(:pre_checks)
|
154
|
+
superclass.pre_checks.dup
|
155
|
+
else
|
156
|
+
[]
|
157
|
+
end => @pre_checks
|
197
158
|
end
|
198
159
|
end
|
199
160
|
end
|
@@ -1,16 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
unless "".respond_to?(:then)
|
4
|
-
require "action_policy/ext/yield_self_then"
|
5
|
-
using ActionPolicy::Ext::YieldSelfThen
|
6
|
-
end
|
7
|
-
|
8
|
-
unless {}.respond_to?(:transform_keys)
|
9
|
-
require "action_policy/ext/hash_transform_keys"
|
10
|
-
using ActionPolicy::Ext::HashTransformKeys
|
11
|
-
end
|
12
|
-
|
13
3
|
module ActionPolicy
|
4
|
+
using RubyNext
|
5
|
+
|
14
6
|
module Policy
|
15
7
|
# Failures reasons store
|
16
8
|
class FailureReasons
|
@@ -33,17 +25,11 @@ module ActionPolicy
|
|
33
25
|
|
34
26
|
# Return Hash of the form:
|
35
27
|
# { policy_identifier => [rules, ...] }
|
36
|
-
def details
|
37
|
-
reasons.transform_keys(&:identifier)
|
38
|
-
end
|
28
|
+
def details() = reasons.transform_keys(&:identifier)
|
39
29
|
|
40
|
-
def empty?
|
41
|
-
reasons.empty?
|
42
|
-
end
|
30
|
+
def empty?() = reasons.empty?
|
43
31
|
|
44
|
-
def present?
|
45
|
-
!empty?
|
46
|
-
end
|
32
|
+
def present?() = !empty?
|
47
33
|
|
48
34
|
private
|
49
35
|
|
@@ -82,6 +68,25 @@ module ActionPolicy
|
|
82
68
|
@details = nil
|
83
69
|
end
|
84
70
|
|
71
|
+
# Returns all the details merged together
|
72
|
+
def all_details
|
73
|
+
return @all_details if defined?(@all_details)
|
74
|
+
|
75
|
+
{}.tap do |all|
|
76
|
+
next unless defined?(@reasons)
|
77
|
+
|
78
|
+
reasons.reasons.each_value do |rules|
|
79
|
+
detailed_reasons = rules.last
|
80
|
+
|
81
|
+
next unless detailed_reasons.is_a?(Hash)
|
82
|
+
|
83
|
+
detailed_reasons.each_value do |details|
|
84
|
+
all.merge!(details)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end => @all_details
|
88
|
+
end
|
89
|
+
|
85
90
|
# Add reasons to inspect
|
86
91
|
def inspect
|
87
92
|
super.then do |str|
|
@@ -195,6 +200,11 @@ module ActionPolicy
|
|
195
200
|
|
196
201
|
res.success?
|
197
202
|
end
|
203
|
+
|
204
|
+
def deny!(reason = nil)
|
205
|
+
result&.reasons&.add(self, reason) if reason
|
206
|
+
super()
|
207
|
+
end
|
198
208
|
end
|
199
209
|
end
|
200
210
|
end
|
@@ -148,12 +148,11 @@ module ActionPolicy
|
|
148
148
|
def scope_matchers
|
149
149
|
return @scope_matchers if instance_variable_defined?(:@scope_matchers)
|
150
150
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
end
|
151
|
+
if superclass.respond_to?(:scope_matchers)
|
152
|
+
superclass.scope_matchers.dup
|
153
|
+
else
|
154
|
+
[]
|
155
|
+
end => @scope_matchers
|
157
156
|
end
|
158
157
|
end
|
159
158
|
end
|
@@ -26,6 +26,7 @@ module ActionPolicy
|
|
26
26
|
helper_method :allowed_to? if respond_to?(:helper_method)
|
27
27
|
|
28
28
|
attr_writer :authorize_count
|
29
|
+
attr_reader :verify_authorized_skipped
|
29
30
|
|
30
31
|
protected :authorize_count=, :authorize_count
|
31
32
|
end
|
@@ -57,13 +58,17 @@ module ActionPolicy
|
|
57
58
|
|
58
59
|
def verify_authorized
|
59
60
|
raise UnauthorizedAction.new(controller_path, action_name) if
|
60
|
-
authorize_count.zero?
|
61
|
+
authorize_count.zero? && !verify_authorized_skipped
|
61
62
|
end
|
62
63
|
|
63
64
|
def authorize_count
|
64
65
|
@authorize_count ||= 0
|
65
66
|
end
|
66
67
|
|
68
|
+
def skip_verify_authorized!
|
69
|
+
@verify_authorized_skipped = true
|
70
|
+
end
|
71
|
+
|
67
72
|
class_methods do
|
68
73
|
# Adds after_action callback to check that
|
69
74
|
# authorize! method has been called.
|
@@ -11,7 +11,7 @@ module ActionPolicy # :nodoc:
|
|
11
11
|
INIT_EVENT_NAME = "action_policy.init"
|
12
12
|
APPLY_EVENT_NAME = "action_policy.apply_rule"
|
13
13
|
|
14
|
-
def initialize(
|
14
|
+
def initialize(record = nil, **params)
|
15
15
|
event = {policy: self.class.name}
|
16
16
|
ActiveSupport::Notifications.instrument(INIT_EVENT_NAME, event) { super }
|
17
17
|
end
|
@@ -45,16 +45,14 @@ module ActionPolicy
|
|
45
45
|
|
46
46
|
@actual_calls = ActionPolicy::Testing::AuthorizeTracker.calls
|
47
47
|
|
48
|
-
actual_calls.any? {
|
48
|
+
actual_calls.any? { _1.matches?(policy, rule, target) }
|
49
49
|
end
|
50
50
|
|
51
51
|
def does_not_match?(*)
|
52
52
|
raise "This matcher doesn't support negation"
|
53
53
|
end
|
54
54
|
|
55
|
-
def supports_block_expectations?
|
56
|
-
true
|
57
|
-
end
|
55
|
+
def supports_block_expectations?() = true
|
58
56
|
|
59
57
|
def failure_message
|
60
58
|
"expected #{formatted_record} " \
|
@@ -72,14 +70,12 @@ module ActionPolicy
|
|
72
70
|
end
|
73
71
|
|
74
72
|
def formatted_calls
|
75
|
-
actual_calls.map do
|
76
|
-
" - #{
|
73
|
+
actual_calls.map do
|
74
|
+
" - #{_1.inspect}"
|
77
75
|
end.join("\n")
|
78
76
|
end
|
79
77
|
|
80
|
-
def formatted_record(record = target)
|
81
|
-
::RSpec::Support::ObjectFormatter.format(record)
|
82
|
-
end
|
78
|
+
def formatted_record(record = target) = ::RSpec::Support::ObjectFormatter.format(record)
|
83
79
|
end
|
84
80
|
end
|
85
81
|
end
|
@@ -50,7 +50,7 @@ if defined?(::RSpec)
|
|
50
50
|
::RSpec.shared_context "action_policy:policy_context" do
|
51
51
|
let(:record) { nil }
|
52
52
|
let(:context) { {} }
|
53
|
-
let(:policy) { described_class.new(record, context) }
|
53
|
+
let(:policy) { described_class.new(record, **context) }
|
54
54
|
end
|
55
55
|
|
56
56
|
::RSpec.shared_context "action_policy:policy_rule_context" do |policy_rule, *args, method: "describe", block: nil|
|
@@ -21,7 +21,7 @@ module ActionPolicy
|
|
21
21
|
#
|
22
22
|
class HaveAuthorizedScope < ::RSpec::Matchers::BuiltIn::BaseMatcher
|
23
23
|
attr_reader :type, :name, :policy, :scope_options, :actual_scopes,
|
24
|
-
|
24
|
+
:target_expectations
|
25
25
|
|
26
26
|
def initialize(type)
|
27
27
|
@type = type
|
@@ -56,7 +56,7 @@ module ActionPolicy
|
|
56
56
|
|
57
57
|
@actual_scopes = ActionPolicy::Testing::AuthorizeTracker.scopings
|
58
58
|
|
59
|
-
matching_scopes = actual_scopes.select {
|
59
|
+
matching_scopes = actual_scopes.select { _1.matches?(policy, type, name, scope_options) }
|
60
60
|
|
61
61
|
return false if matching_scopes.empty?
|
62
62
|
|
@@ -75,9 +75,7 @@ module ActionPolicy
|
|
75
75
|
raise "This matcher doesn't support negation"
|
76
76
|
end
|
77
77
|
|
78
|
-
def supports_block_expectations?
|
79
|
-
true
|
80
|
-
end
|
78
|
+
def supports_block_expectations?() = true
|
81
79
|
|
82
80
|
def failure_message
|
83
81
|
"expected a scoping named :#{name} for type :#{type} " \
|
@@ -109,8 +107,8 @@ module ActionPolicy
|
|
109
107
|
end
|
110
108
|
|
111
109
|
def formatted_scopings
|
112
|
-
actual_scopes.map do
|
113
|
-
" - #{
|
110
|
+
actual_scopes.map do
|
111
|
+
" - #{_1.inspect}"
|
114
112
|
end.join("\n")
|
115
113
|
end
|
116
114
|
end
|