action_policy 0.5.4 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
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