action_policy 0.5.4 → 0.5.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 911b1f7b929d458ae0bb95eb22fa11ff2a17925d9c797b626664bdad82b7abbc
4
- data.tar.gz: 69bc6ead609db7cbcdc3d31455e7e17b88bf9037a51a8b1722e414a7e50c41e4
3
+ metadata.gz: 939f6e73eca2c33b4f9d3575fde063ac346e46bcda7ab05c062ea9a35edf7252
4
+ data.tar.gz: 8c200d093dd9efa19d6cabd811c4ce29f7c4420efd3e8052c863adbf43c2a632
5
5
  SHA512:
6
- metadata.gz: e3cf8e4bd9347f052a34cee11fad004030ee80d9281c6bbbb9fe99b8f6665b0c611661d6ce3be8578345ef3171ad35d2c38fff8936bdd47efef48bb137a5a415
7
- data.tar.gz: 63c4444667971ee445b60e2d9ffee89402021151ac730f00063f2066984c1ca75c15ae40b45fbce60ce9c4201826116bb417f6e62dbfd91f96a3f3974e26a458
6
+ metadata.gz: 380f476a6716d6fc096d1b6abb6ef68eba62c4ad5fb761427754870582550a25394f24c0b29bab711785ab4806812b9c20ca0ec26f0f4efe7ddca11b762a9585
7
+ data.tar.gz: 0a63c058b49e2063af867ee74a78066e1e39f5a22d226748ee5780543e40287d672bfc177bedaf91f7036168ac72746a9128dc222a4d8a7bfe0690cdda7b5b49
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.5.5 (2020-12-28)
6
+
7
+ - Upgrade to Ruby 3.0. ([@palkan][])
8
+
5
9
  ## 0.5.4 (2020-12-09)
6
10
 
7
11
  - Add support for RSpec aliases detection when linting policy specs with `rubocop-rspec` 2.0 ([@pirj][])
@@ -431,7 +435,7 @@ This value is now stored in a cache (if any) instead of just the call result (`t
431
435
  [@ilyasgaraev]: https://github.com/ilyasgaraev
432
436
  [@brendon]: https://github.com/brendon
433
437
  [@DmitryTsepelev]: https://github.com/DmitryTsepelev
434
- [@korolvs]: https://github.com/korolvs
438
+ [@korolvs]: https://github.com/slavadev
435
439
  [@nicolas-brousse]: https://github.com/nicolas-brousse
436
440
  [@somenugget]: https://github.com/somenugget
437
441
  [@Be-ngt-oH]: https://github.com/Be-ngt-oH
@@ -11,7 +11,7 @@ module ActionPolicy
11
11
  def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
12
12
  policy_class = with || ::ActionPolicy.lookup(
13
13
  record,
14
- **{namespace: namespace, context: context, allow_nil: allow_nil, default: default}
14
+ namespace: namespace, context: context, allow_nil: allow_nil, default: default
15
15
  )
16
16
  policy_class&.new(record, **context)
17
17
  end
@@ -19,11 +19,11 @@ module ActionPolicy
19
19
  type ||= authorization_scope_type_for(policy, target)
20
20
  name = as
21
21
 
22
- Authorizer.scopify(target, policy, **{type: type, name: name, scope_options: scope_options})
22
+ Authorizer.scopify(target, policy, type: type, name: name, scope_options: scope_options)
23
23
  end
24
24
 
25
25
  # For backward compatibility
26
- alias authorized authorized_scope
26
+ alias_method :authorized, :authorized_scope
27
27
 
28
28
  # Infer scope type for target if none provided.
29
29
  # Raises an exception if type couldn't be inferred.
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ old_verbose = $VERBOSE
4
+
5
+ begin
6
+ require "method_source"
7
+ # Ignore parse warnings when patch
8
+ # Ruby version mismatches
9
+ $VERBOSE = nil
10
+ require "parser/current"
11
+ require "unparser"
12
+ rescue LoadError
13
+ # do nothing
14
+ ensure
15
+ $VERBOSE = old_verbose
16
+ end
17
+
18
+ module ActionPolicy
19
+ using RubyNext
20
+
21
+ # Takes the object and a method name,
22
+ # and returns the "annotated" source code for the method:
23
+ # code is split into parts by logical operators and each
24
+ # part is evaluated separately.
25
+ #
26
+ # Example:
27
+ #
28
+ # class MyClass
29
+ # def access?
30
+ # admin? && access_feed?
31
+ # end
32
+ # end
33
+ #
34
+ # puts PrettyPrint.format_method(MyClass.new, :access?)
35
+ #
36
+ # #=> MyClass#access?
37
+ # #=> ↳ admin? #=> false
38
+ # #=> AND
39
+ # #=> access_feed? #=> true
40
+ module PrettyPrint
41
+ TRUE = "\e[32mtrue\e[0m"
42
+ FALSE = "\e[31mfalse\e[0m"
43
+
44
+ class Visitor
45
+ attr_reader :lines, :object
46
+ attr_accessor :indent
47
+
48
+ def initialize(object)
49
+ @object = object
50
+ end
51
+
52
+ def collect(ast)
53
+ @lines = []
54
+ @indent = 0
55
+
56
+ visit_node(ast)
57
+
58
+ lines.join("\n")
59
+ end
60
+
61
+ def visit_node(ast)
62
+ if respond_to?("visit_#{ast.type}")
63
+ send("visit_#{ast.type}", ast)
64
+ else
65
+ visit_missing ast
66
+ end
67
+ end
68
+
69
+ def expression_with_result(sexp)
70
+ expression = Unparser.unparse(sexp)
71
+ "#{expression} #=> #{PrettyPrint.colorize(eval_exp(expression))}"
72
+ end
73
+
74
+ def eval_exp(exp)
75
+ return "<skipped>" if ignore_exp?(exp)
76
+ object.instance_eval(exp)
77
+ rescue => e
78
+ "Failed: #{e.message}"
79
+ end
80
+
81
+ def visit_and(ast)
82
+ visit_node(ast.children[0])
83
+ lines << indented("AND")
84
+ visit_node(ast.children[1])
85
+ end
86
+
87
+ def visit_or(ast)
88
+ visit_node(ast.children[0])
89
+ lines << indented("OR")
90
+ visit_node(ast.children[1])
91
+ end
92
+
93
+ def visit_begin(ast)
94
+ # Parens
95
+ if ast.children.size == 1
96
+ lines << indented("(")
97
+ self.indent += 2
98
+ visit_node(ast.children[0])
99
+ self.indent -= 2
100
+ lines << indented(")")
101
+ else
102
+ # Multiple expressions
103
+ ast.children.each do |node|
104
+ visit_node(node)
105
+ # restore indent after each expression
106
+ self.indent -= 2
107
+ end
108
+ end
109
+ end
110
+
111
+ def visit_missing(ast)
112
+ lines << indented(expression_with_result(ast))
113
+ end
114
+
115
+ def indented(str)
116
+ "#{indent.zero? ? "↳ " : ""}#{" " * indent}#{str}".tap do
117
+ # increase indent after the first expression
118
+ self.indent += 2 if indent.zero?
119
+ end
120
+ end
121
+
122
+ # Some lines should not be evaled
123
+ def ignore_exp?(exp)
124
+ PrettyPrint.ignore_expressions.any? { exp.match?(_1) }
125
+ end
126
+ end
127
+
128
+ class << self
129
+ attr_accessor :ignore_expressions
130
+
131
+ if defined?(::Unparser) && defined?(::MethodSource)
132
+ def available?() = true
133
+
134
+ def print_method(object, method_name)
135
+ ast = object.method(method_name).source.then(&Unparser.method(:parse))
136
+ # outer node is a method definition itself
137
+ body = ast.children[2]
138
+
139
+ Visitor.new(object).collect(body)
140
+ end
141
+ else
142
+ def available?() = false
143
+
144
+ def print_method(_, _) = ""
145
+ end
146
+
147
+ def colorize(val)
148
+ return val unless $stdout.isatty
149
+ return TRUE if val.eql?(true)
150
+ return FALSE if val.eql?(false)
151
+ val
152
+ end
153
+ end
154
+
155
+ self.ignore_expressions = [
156
+ /^\s*binding\.(pry|irb)\s*$/s
157
+ ]
158
+ end
159
+ end
@@ -11,7 +11,7 @@ module ActionPolicy
11
11
  def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
12
12
  policy_class = with || ::ActionPolicy.lookup(
13
13
  record,
14
- **{namespace: namespace, context: context, allow_nil: allow_nil, default: default}
14
+ namespace: namespace, context: context, allow_nil: allow_nil, default: default
15
15
  )
16
16
  policy_class&.new(record, **context)
17
17
  end
@@ -40,7 +40,7 @@ module ActionPolicy
40
40
  base.prepend InstanceMethods
41
41
  end
42
42
 
43
- alias included prepended
43
+ alias_method :included, :prepended
44
44
  end
45
45
 
46
46
  module InstanceMethods # :nodoc:
@@ -104,11 +104,11 @@ module ActionPolicy
104
104
  def authorization_targets
105
105
  return @authorization_targets if instance_variable_defined?(:@authorization_targets)
106
106
 
107
- if superclass.respond_to?(:authorization_targets)
107
+ @authorization_targets = if superclass.respond_to?(:authorization_targets)
108
108
  superclass.authorization_targets.dup
109
109
  else
110
110
  {}
111
- end => @authorization_targets
111
+ end
112
112
  end
113
113
  end
114
114
  end
@@ -24,7 +24,7 @@ module ActionPolicy
24
24
  base.prepend InstanceMethods
25
25
  end
26
26
 
27
- alias included prepended
27
+ alias_method :included, :prepended
28
28
  end
29
29
 
30
30
  module InstanceMethods # :nodoc:
@@ -66,7 +66,7 @@ module ActionPolicy
66
66
  base.prepend InstanceMethods
67
67
  end
68
68
 
69
- alias included prepended
69
+ alias_method :included, :prepended
70
70
  end
71
71
 
72
72
  module InstanceMethods # :nodoc:
@@ -11,7 +11,7 @@ module ActionPolicy
11
11
  def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
12
12
  policy_class = with || ::ActionPolicy.lookup(
13
13
  record,
14
- **{namespace, context, allow_nil, default}
14
+ namespace:, context:, allow_nil:, default:
15
15
  )
16
16
  policy_class&.new(record, **context)
17
17
  end
@@ -19,11 +19,11 @@ module ActionPolicy
19
19
  type ||= authorization_scope_type_for(policy, target)
20
20
  name = as
21
21
 
22
- Authorizer.scopify(target, policy, **{type, name, scope_options})
22
+ Authorizer.scopify(target, policy, type:, name:, scope_options:)
23
23
  end
24
24
 
25
25
  # For backward compatibility
26
- alias authorized authorized_scope
26
+ alias_method :authorized, :authorized_scope
27
27
 
28
28
  # Infer scope type for target if none provided.
29
29
  # Raises an exception if type couldn't be inferred.
@@ -40,7 +40,7 @@ module ActionPolicy
40
40
  base.prepend InstanceMethods
41
41
  end
42
42
 
43
- alias included prepended
43
+ alias_method :included, :prepended
44
44
  end
45
45
 
46
46
  module InstanceMethods # :nodoc:
@@ -52,7 +52,7 @@ module ActionPolicy
52
52
  class << self
53
53
  attr_accessor :chain, :namespace_cache_enabled
54
54
 
55
- alias namespace_cache_enabled? namespace_cache_enabled
55
+ alias_method :namespace_cache_enabled?, :namespace_cache_enabled
56
56
 
57
57
  def call(record, **opts)
58
58
  chain.each do |probe|
@@ -53,11 +53,11 @@ module ActionPolicy
53
53
  def rules_aliases
54
54
  return @rules_aliases if instance_variable_defined?(:@rules_aliases)
55
55
 
56
- if superclass.respond_to?(:rules_aliases)
56
+ @rules_aliases = if superclass.respond_to?(:rules_aliases)
57
57
  superclass.rules_aliases.dup
58
58
  else
59
59
  {}
60
- end => @rules_aliases
60
+ end
61
61
  end
62
62
 
63
63
  def method_added(name)
@@ -75,11 +75,11 @@ module ActionPolicy
75
75
  def authorization_targets
76
76
  return @authorization_targets if instance_variable_defined?(:@authorization_targets)
77
77
 
78
- if superclass.respond_to?(:authorization_targets)
78
+ @authorization_targets = if superclass.respond_to?(:authorization_targets)
79
79
  superclass.authorization_targets.dup
80
80
  else
81
81
  {}
82
- end => @authorization_targets
82
+ end
83
83
  end
84
84
  end
85
85
  end
@@ -89,11 +89,11 @@ module ActionPolicy # :nodoc:
89
89
  def cached_rules
90
90
  return @cached_rules if instance_variable_defined?(:@cached_rules)
91
91
 
92
- if superclass.respond_to?(:cached_rules)
92
+ @cached_rules = if superclass.respond_to?(:cached_rules)
93
93
  superclass.cached_rules.dup
94
94
  else
95
95
  {}
96
- end => @cached_rules
96
+ end
97
97
  end
98
98
  end
99
99
  end
@@ -150,11 +150,11 @@ module ActionPolicy
150
150
  def pre_checks
151
151
  return @pre_checks if instance_variable_defined?(:@pre_checks)
152
152
 
153
- if superclass.respond_to?(:pre_checks)
153
+ @pre_checks = if superclass.respond_to?(:pre_checks)
154
154
  superclass.pre_checks.dup
155
155
  else
156
156
  []
157
- end => @pre_checks
157
+ end
158
158
  end
159
159
  end
160
160
  end
@@ -72,7 +72,7 @@ module ActionPolicy
72
72
  def all_details
73
73
  return @all_details if defined?(@all_details)
74
74
 
75
- {}.tap do |all|
75
+ @all_details = {}.tap do |all|
76
76
  next unless defined?(@reasons)
77
77
 
78
78
  reasons.reasons.each_value do |rules|
@@ -84,7 +84,7 @@ module ActionPolicy
84
84
  all.merge!(details)
85
85
  end
86
86
  end
87
- end => @all_details
87
+ end
88
88
  end
89
89
 
90
90
  # Add reasons to inspect
@@ -148,11 +148,11 @@ module ActionPolicy
148
148
  def scope_matchers
149
149
  return @scope_matchers if instance_variable_defined?(:@scope_matchers)
150
150
 
151
- if superclass.respond_to?(:scope_matchers)
151
+ @scope_matchers = if superclass.respond_to?(:scope_matchers)
152
152
  superclass.scope_matchers.dup
153
153
  else
154
154
  []
155
- end => @scope_matchers
155
+ end
156
156
  end
157
157
  end
158
158
  end
@@ -21,6 +21,7 @@ module ActionPolicy
21
21
  yield scopes.first.target
22
22
  end
23
23
  end
24
+
24
25
  # Asserts that the given policy was used to authorize the given target.
25
26
  #
26
27
  # def test_authorize
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionPolicy
4
- VERSION = "0.5.4"
4
+ VERSION = "0.5.5"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_policy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-09 00:00:00.000000000 Z
11
+ date: 2020-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-next-core
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.10.3
19
+ version: 0.11.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.10.3
26
+ version: 0.11.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: ammeter
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -133,6 +133,10 @@ files:
133
133
  - LICENSE.txt
134
134
  - README.md
135
135
  - config/rubocop-rspec.yml
136
+ - lib/.rbnext/1995.next/action_policy/behaviours/policy_for.rb
137
+ - lib/.rbnext/1995.next/action_policy/behaviours/scoping.rb
138
+ - lib/.rbnext/1995.next/action_policy/policy/authorization.rb
139
+ - lib/.rbnext/1995.next/action_policy/utils/pretty_print.rb
136
140
  - lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb
137
141
  - lib/.rbnext/2.7/action_policy/i18n.rb
138
142
  - lib/.rbnext/2.7/action_policy/policy/cache.rb
@@ -140,20 +144,15 @@ files:
140
144
  - lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb
141
145
  - lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb
142
146
  - lib/.rbnext/2.7/action_policy/utils/pretty_print.rb
143
- - lib/.rbnext/3.0/action_policy/behaviour.rb
144
- - lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb
145
- - lib/.rbnext/3.0/action_policy/behaviours/scoping.rb
146
147
  - lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb
147
148
  - lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb
148
149
  - lib/.rbnext/3.0/action_policy/policy/aliases.rb
149
- - lib/.rbnext/3.0/action_policy/policy/authorization.rb
150
150
  - lib/.rbnext/3.0/action_policy/policy/cache.rb
151
151
  - lib/.rbnext/3.0/action_policy/policy/core.rb
152
152
  - lib/.rbnext/3.0/action_policy/policy/defaults.rb
153
153
  - lib/.rbnext/3.0/action_policy/policy/execution_result.rb
154
154
  - lib/.rbnext/3.0/action_policy/policy/pre_check.rb
155
155
  - lib/.rbnext/3.0/action_policy/policy/reasons.rb
156
- - lib/.rbnext/3.0/action_policy/policy/scoping.rb
157
156
  - lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb
158
157
  - lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb
159
158
  - lib/.rbnext/3.0/action_policy/utils/pretty_print.rb
@@ -1,115 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "action_policy/behaviours/policy_for"
4
- require "action_policy/behaviours/scoping"
5
- require "action_policy/behaviours/memoized"
6
- require "action_policy/behaviours/thread_memoized"
7
- require "action_policy/behaviours/namespaced"
8
-
9
- require "action_policy/authorizer"
10
-
11
- module ActionPolicy
12
- # Provides `authorize!` and `allowed_to?` methods and
13
- # `authorize` class method to define authorization context.
14
- #
15
- # Could be included anywhere to perform authorization.
16
- module Behaviour
17
- include ActionPolicy::Behaviours::PolicyFor
18
- include ActionPolicy::Behaviours::Scoping
19
-
20
- def self.included(base)
21
- # Handle ActiveSupport::Concern differently
22
- if base.respond_to?(:class_methods)
23
- base.class_methods do
24
- include ClassMethods
25
- end
26
- else
27
- base.extend ClassMethods
28
- end
29
- end
30
-
31
- # Authorize action against a policy.
32
- #
33
- # Policy is inferred from record
34
- # (unless explicitly specified through `with` option).
35
- #
36
- # Raises `ActionPolicy::Unauthorized` if check failed.
37
- def authorize!(record = :__undef__, to:, **options)
38
- policy = lookup_authorization_policy(record, **options)
39
-
40
- Authorizer.call(policy, authorization_rule_for(policy, to))
41
- end
42
-
43
- # Checks that an activity is allowed for the current context (e.g. user).
44
- #
45
- # Returns true of false.
46
- def allowed_to?(rule, record = :__undef__, **options)
47
- policy = lookup_authorization_policy(record, **options)
48
-
49
- policy.apply(authorization_rule_for(policy, rule))
50
- end
51
-
52
- # Returns the authorization result object after applying a specified rule to a record.
53
- def allowance_to(rule, record = :__undef__, **options)
54
- policy = lookup_authorization_policy(record, **options)
55
-
56
- policy.apply(authorization_rule_for(policy, rule))
57
- policy.result
58
- end
59
-
60
- def authorization_context
61
- return @__authorization_context if
62
- instance_variable_defined?(:@__authorization_context)
63
-
64
- @__authorization_context = self.class.authorization_targets
65
- .each_with_object({}) do |(key, meth), obj|
66
- obj[key] = send(meth)
67
- end
68
- end
69
-
70
- # Check that rule is defined for policy,
71
- # otherwise fallback to :manage? rule.
72
- def authorization_rule_for(policy, rule)
73
- policy.resolve_rule(rule)
74
- end
75
-
76
- def lookup_authorization_policy(record, **options) # :nodoc:
77
- record = implicit_authorization_target! if record == :__undef__
78
- raise ArgumentError, "Record must be specified" if record.nil?
79
-
80
- options[:context] && (options[:context] = authorization_context.merge(options[:context]))
81
-
82
- policy_for(record: record, **options)
83
- end
84
-
85
- module ClassMethods # :nodoc:
86
- # Configure authorization context.
87
- #
88
- # For example:
89
- #
90
- # class ApplicationController < ActionController::Base
91
- # # Pass the value of `current_user` to authorization as `user`
92
- # authorize :user, through: :current_user
93
- # end
94
- #
95
- # # Assuming that in your ApplicationPolicy
96
- # class ApplicationPolicy < ActionPolicy::Base
97
- # authorize :user
98
- # end
99
- def authorize(key, through: nil)
100
- meth = through || key
101
- authorization_targets[key] = meth
102
- end
103
-
104
- def authorization_targets
105
- return @authorization_targets if instance_variable_defined?(:@authorization_targets)
106
-
107
- @authorization_targets = if superclass.respond_to?(:authorization_targets)
108
- superclass.authorization_targets.dup
109
- else
110
- {}
111
- end
112
- end
113
- end
114
- end
115
- end
@@ -1,160 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "action_policy/behaviours/scoping"
4
-
5
- require "action_policy/utils/suggest_message"
6
-
7
- module ActionPolicy
8
- class UnknownScopeType < Error # :nodoc:
9
- include ActionPolicy::SuggestMessage
10
-
11
- MESSAGE_TEMPLATE = "Unknown policy scope type :%s for %s%s"
12
-
13
- attr_reader :message
14
-
15
- def initialize(policy_class, type)
16
- @message = format(
17
- MESSAGE_TEMPLATE,
18
- type, policy_class,
19
- suggest(type, policy_class.scoping_handlers.keys)
20
- )
21
- end
22
- end
23
-
24
- class UnknownNamedScope < Error # :nodoc:
25
- include ActionPolicy::SuggestMessage
26
-
27
- MESSAGE_TEMPLATE = "Unknown named scope :%s for type :%s for %s%s"
28
-
29
- attr_reader :message
30
-
31
- def initialize(policy_class, type, name)
32
- @message = format(
33
- MESSAGE_TEMPLATE, name, type, policy_class,
34
- suggest(name, policy_class.scoping_handlers[type].keys)
35
- )
36
- end
37
- end
38
-
39
- class UnrecognizedScopeTarget < Error # :nodoc:
40
- MESSAGE_TEMPLATE = "Couldn't infer scope type for %s instance"
41
-
42
- attr_reader :message
43
-
44
- def initialize(target)
45
- target_class = target.is_a?(Module) ? target : target.class
46
-
47
- @message = format(
48
- MESSAGE_TEMPLATE, target_class
49
- )
50
- end
51
- end
52
-
53
- module Policy
54
- # Scoping is used to modify the _object under authorization_.
55
- #
56
- # The most common situation is when you want to _scope_ the collection depending
57
- # on the current user permissions.
58
- #
59
- # For example:
60
- #
61
- # class ApplicationPolicy < ActionPolicy::Base
62
- # # Scoping only makes sense when you have the authorization context
63
- # authorize :user
64
- #
65
- # # :relation here is a scoping type
66
- # scope_for :relation do |relation|
67
- # # authorization context is available within a scope
68
- # if user.admin?
69
- # relation
70
- # else
71
- # relation.publicly_visible
72
- # end
73
- # end
74
- # end
75
- #
76
- # base_scope = User.all
77
- # authorized_scope = ApplicantPolicy.new(user: user)
78
- # .apply_scope(base_scope, type: :relation)
79
- module Scoping
80
- class << self
81
- def included(base)
82
- base.extend ClassMethods
83
- end
84
- end
85
-
86
- include ActionPolicy::Behaviours::Scoping
87
-
88
- # Pass target to the scope handler of the specified type and name.
89
- # If `name` is not specified then `:default` name is used.
90
- # If `type` is not specified then we try to infer the type from the
91
- # target class.
92
- def apply_scope(target, type:, name: :default, scope_options: nil)
93
- raise ActionPolicy::UnknownScopeType.new(self.class, type) unless
94
- self.class.scoping_handlers.key?(type)
95
-
96
- raise ActionPolicy::UnknownNamedScope.new(self.class, type, name) unless
97
- self.class.scoping_handlers[type].key?(name)
98
-
99
- mid = :"__scoping__#{type}__#{name}"
100
- scope_options ? send(mid, target, **scope_options) : send(mid, target)
101
- end
102
-
103
- def resolve_scope_type(target)
104
- lookup_type_from_target(target) ||
105
- raise(ActionPolicy::UnrecognizedScopeTarget, target)
106
- end
107
-
108
- def lookup_type_from_target(target)
109
- self.class.scope_matchers.detect do |(_type, matcher)|
110
- matcher === target
111
- end&.first
112
- end
113
-
114
- module ClassMethods # :nodoc:
115
- # Register a new scoping method for the `type`
116
- def scope_for(type, name = :default, &block)
117
- mid = :"__scoping__#{type}__#{name}"
118
-
119
- define_method(mid, &block)
120
-
121
- scoping_handlers[type][name] = mid
122
- end
123
-
124
- def scoping_handlers
125
- return @scoping_handlers if instance_variable_defined?(:@scoping_handlers)
126
-
127
- @scoping_handlers =
128
- Hash.new { |h, k| h[k] = {} }.tap do |handlers|
129
- if superclass.respond_to?(:scoping_handlers)
130
- superclass.scoping_handlers.each do |k, v|
131
- handlers[k] = v.dup
132
- end
133
- end
134
- end
135
- end
136
-
137
- # Define scope type matcher.
138
- #
139
- # Scope matcher is an object that implements `#===` (_case equality_) or a Proc.
140
- #
141
- # When no type is provided when applying a scope we try to infer a type
142
- # from the target object by calling matchers one by one until we find a matching
143
- # type (i.e. there is a matcher which returns `true` when applying it to the target).
144
- def scope_matcher(type, class_or_proc)
145
- scope_matchers << [type, class_or_proc]
146
- end
147
-
148
- def scope_matchers
149
- return @scope_matchers if instance_variable_defined?(:@scope_matchers)
150
-
151
- @scope_matchers = if superclass.respond_to?(:scope_matchers)
152
- superclass.scope_matchers.dup
153
- else
154
- []
155
- end
156
- end
157
- end
158
- end
159
- end
160
- end