pundit 2.2.0 → 2.5.2

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -29
  3. data/CONTRIBUTING.md +3 -5
  4. data/README.md +125 -54
  5. data/SECURITY.md +19 -0
  6. data/config/rubocop-rspec.yml +5 -0
  7. data/lib/generators/pundit/install/install_generator.rb +3 -1
  8. data/lib/generators/pundit/install/templates/{application_policy.rb → application_policy.rb.tt} +1 -1
  9. data/lib/generators/pundit/policy/policy_generator.rb +3 -1
  10. data/lib/generators/pundit/policy/templates/policy.rb.tt +16 -0
  11. data/lib/generators/rspec/policy_generator.rb +4 -1
  12. data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +1 -1
  13. data/lib/generators/test_unit/policy_generator.rb +4 -1
  14. data/lib/pundit/authorization.rb +176 -75
  15. data/lib/pundit/cache_store/legacy_store.rb +27 -0
  16. data/lib/pundit/cache_store/null_store.rb +30 -0
  17. data/lib/pundit/cache_store.rb +24 -0
  18. data/lib/pundit/context.rb +190 -0
  19. data/lib/pundit/error.rb +71 -0
  20. data/lib/pundit/helper.rb +16 -0
  21. data/lib/pundit/policy_finder.rb +34 -2
  22. data/lib/pundit/railtie.rb +20 -0
  23. data/lib/pundit/rspec.rb +92 -7
  24. data/lib/pundit/version.rb +2 -1
  25. data/lib/pundit.rb +45 -140
  26. metadata +25 -170
  27. data/.gitignore +0 -19
  28. data/.rubocop.yml +0 -72
  29. data/.travis.yml +0 -26
  30. data/.yardopts +0 -1
  31. data/CODE_OF_CONDUCT.md +0 -28
  32. data/Gemfile +0 -7
  33. data/Rakefile +0 -20
  34. data/lib/generators/pundit/policy/templates/policy.rb +0 -10
  35. data/pundit.gemspec +0 -33
  36. data/spec/authorization_spec.rb +0 -258
  37. data/spec/generators_spec.rb +0 -43
  38. data/spec/policies/post_policy_spec.rb +0 -22
  39. data/spec/policy_finder_spec.rb +0 -187
  40. data/spec/pundit_spec.rb +0 -427
  41. data/spec/spec_helper.rb +0 -275
  42. /data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pundit
4
+ # @api private
5
+ # @since v1.0.0
6
+ # To avoid name clashes with common Error naming when mixing in Pundit,
7
+ # keep it here with compact class style definition.
8
+ class Error < StandardError; end
9
+
10
+ # Error that will be raised when authorization has failed
11
+ # @since v0.1.0
12
+ class NotAuthorizedError < Error
13
+ # @see #initialize
14
+ # @since v0.2.3
15
+ attr_reader :query
16
+ # @see #initialize
17
+ # @since v0.2.3
18
+ attr_reader :record
19
+ # @see #initialize
20
+ # @since v0.2.3
21
+ attr_reader :policy
22
+
23
+ # @since v1.0.0
24
+ #
25
+ # @overload initialize(message)
26
+ # Create an error with a simple error message.
27
+ # @param [String] message A simple error message string.
28
+ #
29
+ # @overload initialize(options)
30
+ # Create an error with the specified attributes.
31
+ # @param [Hash] options The error options.
32
+ # @option options [String] :message Optional custom error message. Will default to a generalized message.
33
+ # @option options [Symbol] :query The name of the policy method that was checked.
34
+ # @option options [Object] :record The object that was being checked with the policy.
35
+ # @option options [Class] :policy The class of policy that was used for the check.
36
+ def initialize(options = {})
37
+ if options.is_a? String
38
+ message = options
39
+ else
40
+ @query = options[:query]
41
+ @record = options[:record]
42
+ @policy = options[:policy]
43
+
44
+ message = options.fetch(:message) do
45
+ record_name = record.is_a?(Class) ? record.to_s : "this #{record.class}"
46
+ "not allowed to #{policy.class}##{query} #{record_name}"
47
+ end
48
+ end
49
+
50
+ super(message)
51
+ end
52
+ end
53
+
54
+ # Error that will be raised if a policy or policy scope constructor is not called correctly.
55
+ # @since v2.0.0
56
+ class InvalidConstructorError < Error; end
57
+
58
+ # Error that will be raised if a controller action has not called the
59
+ # `authorize` or `skip_authorization` methods.
60
+ # @since v0.2.3
61
+ class AuthorizationNotPerformedError < Error; end
62
+
63
+ # Error that will be raised if a controller action has not called the
64
+ # `policy_scope` or `skip_policy_scope` methods.
65
+ # @since v0.3.0
66
+ class PolicyScopingNotPerformedError < AuthorizationNotPerformedError; end
67
+
68
+ # Error that will be raised if a policy or policy scope is not defined.
69
+ # @since v0.1.0
70
+ class NotDefinedError < Error; end
71
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pundit
4
+ # Rails view helpers, to allow a slightly different view-specific
5
+ # implementation of the methods in {Pundit::Authorization}.
6
+ #
7
+ # @api private
8
+ # @since v1.0.0
9
+ module Helper
10
+ # @see Pundit::Authorization#pundit_policy_scope
11
+ # @since v1.0.0
12
+ def policy_scope(scope)
13
+ pundit_policy_scope(scope)
14
+ end
15
+ end
16
+ end
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # String#safe_constantize, String#demodulize, String#underscore, String#camelize
4
+ require "active_support/core_ext/string/inflections"
5
+
3
6
  module Pundit
4
7
  # Finds policy and scope classes for given object.
8
+ # @since v0.1.0
5
9
  # @api public
6
10
  # @example
7
11
  # user = User.find(params[:id])
@@ -10,10 +14,18 @@ module Pundit
10
14
  # finder.scope #=> UserPolicy::Scope
11
15
  #
12
16
  class PolicyFinder
17
+ # A constant applied to the end of the class name to find the policy class.
18
+ #
19
+ # @api private
20
+ # @since v2.5.0
21
+ SUFFIX = "Policy"
22
+
23
+ # @see #initialize
24
+ # @since v0.1.0
13
25
  attr_reader :object
14
26
 
15
27
  # @param object [any] the object to find policy and scope classes for
16
- #
28
+ # @since v0.1.0
17
29
  def initialize(object)
18
30
  @object = object
19
31
  end
@@ -24,6 +36,7 @@ module Pundit
24
36
  # scope = finder.scope #=> UserPolicy::Scope
25
37
  # scope.resolve #=> <#ActiveRecord::Relation ...>
26
38
  #
39
+ # @since v0.1.0
27
40
  def scope
28
41
  "#{policy}::Scope".safe_constantize
29
42
  end
@@ -35,6 +48,7 @@ module Pundit
35
48
  # policy.show? #=> true
36
49
  # policy.update? #=> false
37
50
  #
51
+ # @since v0.1.0
38
52
  def policy
39
53
  klass = find(object)
40
54
  klass.is_a?(String) ? klass.safe_constantize : klass
@@ -43,6 +57,7 @@ module Pundit
43
57
  # @return [Scope{#resolve}] scope class which can resolve to a scope
44
58
  # @raise [NotDefinedError] if scope could not be determined
45
59
  #
60
+ # @since v0.1.0
46
61
  def scope!
47
62
  scope or raise NotDefinedError, "unable to find scope `#{find(object)}::Scope` for `#{object.inspect}`"
48
63
  end
@@ -50,13 +65,15 @@ module Pundit
50
65
  # @return [Class] policy class with query methods
51
66
  # @raise [NotDefinedError] if policy could not be determined
52
67
  #
68
+ # @since v0.1.0
53
69
  def policy!
54
70
  policy or raise NotDefinedError, "unable to find policy `#{find(object)}` for `#{object.inspect}`"
55
71
  end
56
72
 
57
73
  # @return [String] the name of the key this object would have in a params hash
58
74
  #
59
- def param_key
75
+ # @since v1.1.0
76
+ def param_key # rubocop:disable Metrics/AbcSize
60
77
  model = object.is_a?(Array) ? object.last : object
61
78
 
62
79
  if model.respond_to?(:model_name)
@@ -70,6 +87,12 @@ module Pundit
70
87
 
71
88
  private
72
89
 
90
+ # Given an object, find the policy class name.
91
+ #
92
+ # Uses recursion to handle namespaces.
93
+ #
94
+ # @return [String, Class] the policy class, or its name.
95
+ # @since v0.2.0
73
96
  def find(subject)
74
97
  if subject.is_a?(Array)
75
98
  modules = subject.dup
@@ -86,6 +109,15 @@ module Pundit
86
109
  end
87
110
  end
88
111
 
112
+ # Given an object, find its' class name.
113
+ #
114
+ # - Supports ActiveModel.
115
+ # - Supports regular classes.
116
+ # - Supports symbols.
117
+ # - Supports object instances.
118
+ #
119
+ # @return [String, Class] the class, or its name.
120
+ # @since v1.1.0
89
121
  def find_class_name(subject)
90
122
  if subject.respond_to?(:model_name)
91
123
  subject.model_name
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pundit
4
+ # @since v2.5.0
5
+ class Railtie < Rails::Railtie
6
+ if Rails.version.to_f >= 8.0
7
+ initializer "pundit.stats_directories" do
8
+ require "rails/code_statistics"
9
+
10
+ if Rails.root.join("app/policies").directory?
11
+ Rails::CodeStatistics.register_directory("Policies", "app/policies")
12
+ end
13
+
14
+ if Rails.root.join("test/policies").directory?
15
+ Rails::CodeStatistics.register_directory("Policy tests", "test/policies", test_directory: true)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
data/lib/pundit/rspec.rb CHANGED
@@ -1,10 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pundit"
4
+ # Array#to_sentence
5
+ require "active_support/core_ext/array/conversions"
6
+
3
7
  module Pundit
8
+ # Namespace for Pundit's RSpec integration.
9
+ # @since v0.1.0
4
10
  module RSpec
11
+ # Namespace for Pundit's RSpec matchers.
5
12
  module Matchers
6
13
  extend ::RSpec::Matchers::DSL
7
14
 
15
+ # @!method description=(description)
16
+ class << self
17
+ # Used to build a suitable description for the Pundit `permit` matcher.
18
+ # @api public
19
+ # @param value [String, Proc]
20
+ # @example
21
+ # Pundit::RSpec::Matchers.description = ->(user, record) do
22
+ # "permit user with role #{user.role} to access record with ID #{record.id}"
23
+ # end
24
+ attr_writer :description
25
+
26
+ # Used to retrieve a suitable description for the Pundit `permit` matcher.
27
+ # @api private
28
+ # @private
29
+ def description(user, record)
30
+ return @description.call(user, record) if defined?(@description) && @description.respond_to?(:call)
31
+
32
+ @description
33
+ end
34
+ end
35
+
8
36
  # rubocop:disable Metrics/BlockLength
9
37
  matcher :permit do |user, record|
10
38
  match_proc = lambda do |policy|
@@ -22,15 +50,25 @@ module Pundit
22
50
  end
23
51
 
24
52
  failure_message_proc = lambda do |policy|
25
- was_were = @violating_permissions.count > 1 ? "were" : "was"
26
53
  "Expected #{policy} to grant #{permissions.to_sentence} on " \
27
- "#{record} but #{@violating_permissions.to_sentence} #{was_were} not granted"
54
+ "#{record} but #{@violating_permissions.to_sentence} #{was_or_were} not granted"
28
55
  end
29
56
 
30
57
  failure_message_when_negated_proc = lambda do |policy|
31
- was_were = @violating_permissions.count > 1 ? "were" : "was"
32
58
  "Expected #{policy} not to grant #{permissions.to_sentence} on " \
33
- "#{record} but #{@violating_permissions.to_sentence} #{was_were} granted"
59
+ "#{record} but #{@violating_permissions.to_sentence} #{was_or_were} granted"
60
+ end
61
+
62
+ def was_or_were
63
+ if @violating_permissions.count > 1
64
+ "were"
65
+ else
66
+ "was"
67
+ end
68
+ end
69
+
70
+ description do
71
+ Pundit::RSpec::Matchers.description(user, record) || super()
34
72
  end
35
73
 
36
74
  if respond_to?(:match_when_negated)
@@ -39,26 +77,73 @@ module Pundit
39
77
  failure_message(&failure_message_proc)
40
78
  failure_message_when_negated(&failure_message_when_negated_proc)
41
79
  else
80
+ # :nocov:
81
+ # Compatibility with RSpec < 3.0, released 2014-06-01.
42
82
  match_for_should(&match_proc)
43
83
  match_for_should_not(&match_when_negated_proc)
44
84
  failure_message_for_should(&failure_message_proc)
45
85
  failure_message_for_should_not(&failure_message_when_negated_proc)
86
+ # :nocov:
87
+ end
88
+
89
+ if ::RSpec.respond_to?(:current_example)
90
+ def current_example
91
+ ::RSpec.current_example
92
+ end
93
+ else
94
+ # :nocov:
95
+ # Compatibility with RSpec < 3.0, released 2014-06-01.
96
+ def current_example
97
+ example
98
+ end
99
+ # :nocov:
46
100
  end
47
101
 
48
102
  def permissions
49
- current_example = ::RSpec.respond_to?(:current_example) ? ::RSpec.current_example : example
50
- current_example.metadata[:permissions]
103
+ current_example.metadata.fetch(:permissions) do
104
+ raise KeyError, <<~ERROR.strip
105
+ No permissions in example metadata, did you forget to wrap with `permissions :show?, ...`?
106
+ ERROR
107
+ end
51
108
  end
52
109
  end
53
110
  # rubocop:enable Metrics/BlockLength
54
111
  end
55
112
 
113
+ # Mixed in to all policy example groups to provide a DSL.
56
114
  module DSL
115
+ # @example
116
+ # describe PostPolicy do
117
+ # permissions :show?, :update? do
118
+ # it { is_expected.to permit(user, own_post) }
119
+ # end
120
+ # end
121
+ #
122
+ # @example focused example group
123
+ # describe PostPolicy do
124
+ # permissions :show?, :update?, :focus do
125
+ # it { is_expected.to permit(user, own_post) }
126
+ # end
127
+ # end
128
+ #
129
+ # @param list [Symbol, Array<Symbol>] a permission to describe
130
+ # @return [void]
57
131
  def permissions(*list, &block)
58
- describe(list.to_sentence, permissions: list, caller: caller) { instance_eval(&block) }
132
+ metadata = { permissions: list, caller: caller }
133
+
134
+ if list.last == :focus
135
+ list.pop
136
+ metadata[:focus] = true
137
+ end
138
+
139
+ description = list.to_sentence
140
+ describe(description, metadata) { instance_eval(&block) }
59
141
  end
60
142
  end
61
143
 
144
+ # Mixed in to all policy example groups.
145
+ #
146
+ # @private not useful
62
147
  module PolicyExampleGroup
63
148
  include Pundit::RSpec::Matchers
64
149
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pundit
4
- VERSION = "2.2.0"
4
+ # The current version of Pundit.
5
+ VERSION = "2.5.2"
5
6
  end
data/lib/pundit.rb CHANGED
@@ -1,172 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support"
4
+
3
5
  require "pundit/version"
6
+ require "pundit/error"
4
7
  require "pundit/policy_finder"
5
- require "active_support/concern"
6
- require "active_support/core_ext/string/inflections"
7
- require "active_support/core_ext/object/blank"
8
- require "active_support/core_ext/module/introspection"
9
- require "active_support/dependencies/autoload"
8
+ require "pundit/context"
10
9
  require "pundit/authorization"
11
-
12
- # @api private
13
- # To avoid name clashes with common Error naming when mixing in Pundit,
14
- # keep it here with compact class style definition.
15
- class Pundit::Error < StandardError; end # rubocop:disable Style/ClassAndModuleChildren
16
-
10
+ require "pundit/helper"
11
+ require "pundit/cache_store"
12
+ require "pundit/cache_store/null_store"
13
+ require "pundit/cache_store/legacy_store"
14
+ require "pundit/railtie" if defined?(Rails)
15
+
16
+ # Hello? Yes, this is Pundit.
17
+ #
17
18
  # @api public
18
19
  module Pundit
19
- SUFFIX = "Policy"
20
+ # @api private
21
+ # @since v1.0.0
22
+ # @deprecated See {Pundit::PolicyFinder}
23
+ SUFFIX = Pundit::PolicyFinder::SUFFIX
20
24
 
21
25
  # @api private
26
+ # @private
27
+ # @since v0.1.0
22
28
  module Generators; end
23
29
 
24
- # Error that will be raised when authorization has failed
25
- class NotAuthorizedError < Error
26
- attr_reader :query, :record, :policy
27
-
28
- def initialize(options = {})
29
- if options.is_a? String
30
- message = options
31
- else
32
- @query = options[:query]
33
- @record = options[:record]
34
- @policy = options[:policy]
35
-
36
- message = options.fetch(:message) { "not allowed to #{query} this #{record.class}" }
37
- end
38
-
39
- super(message)
40
- end
41
- end
42
-
43
- # Error that will be raised if a policy or policy scope constructor is not called correctly.
44
- class InvalidConstructorError < Error; end
45
-
46
- # Error that will be raised if a controller action has not called the
47
- # `authorize` or `skip_authorization` methods.
48
- class AuthorizationNotPerformedError < Error; end
49
-
50
- # Error that will be raised if a controller action has not called the
51
- # `policy_scope` or `skip_policy_scope` methods.
52
- class PolicyScopingNotPerformedError < AuthorizationNotPerformedError; end
53
-
54
- # Error that will be raised if a policy or policy scope is not defined.
55
- class NotDefinedError < Error; end
56
-
57
30
  def self.included(base)
58
- ActiveSupport::Deprecation.warn <<~WARNING.strip_heredoc
31
+ location = caller_locations(1, 1).first
32
+ warn <<~WARNING
59
33
  'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead.
34
+ (called from #{location.label} at #{location.path}:#{location.lineno})
60
35
  WARNING
61
36
  base.include Authorization
62
37
  end
63
38
 
64
39
  class << self
65
- # Retrieves the policy for the given record, initializing it with the
66
- # record and user and finally throwing an error if the user is not
67
- # authorized to perform the given action.
68
- #
69
- # @param user [Object] the user that initiated the action
70
- # @param possibly_namespaced_record [Object, Array] the object we're checking permissions of
71
- # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
72
- # @param policy_class [Class] the policy class we want to force use of
73
- # @param cache [#[], #[]=] a Hash-like object to cache the found policy instance in
74
- # @raise [NotAuthorizedError] if the given query method returned false
75
- # @return [Object] Always returns the passed object record
76
- def authorize(user, possibly_namespaced_record, query, policy_class: nil, cache: {})
77
- record = pundit_model(possibly_namespaced_record)
78
- policy = if policy_class
79
- policy_class.new(user, record)
40
+ # @see Pundit::Context#authorize
41
+ # @since v1.0.0
42
+ def authorize(user, record, query, policy_class: nil, cache: nil)
43
+ context = if cache
44
+ policy_cache = CacheStore::LegacyStore.new(cache)
45
+ Context.new(user: user, policy_cache: policy_cache)
80
46
  else
81
- cache[possibly_namespaced_record] ||= policy!(user, possibly_namespaced_record)
47
+ Context.new(user: user)
82
48
  end
83
49
 
84
- raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
85
-
86
- record
50
+ context.authorize(record, query: query, policy_class: policy_class)
87
51
  end
88
52
 
89
- # Retrieves the policy scope for the given record.
90
- #
91
- # @see https://github.com/varvet/pundit#scopes
92
- # @param user [Object] the user that initiated the action
93
- # @param scope [Object] the object we're retrieving the policy scope for
94
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
95
- # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
96
- def policy_scope(user, scope)
97
- policy_scope_class = PolicyFinder.new(scope).scope
98
- return unless policy_scope_class
99
-
100
- begin
101
- policy_scope = policy_scope_class.new(user, pundit_model(scope))
102
- rescue ArgumentError
103
- raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
104
- end
105
-
106
- policy_scope.resolve
53
+ # @see Pundit::Context#policy_scope
54
+ # @since v0.1.0
55
+ def policy_scope(user, *args, **kwargs, &block)
56
+ Context.new(user: user).policy_scope(*args, **kwargs, &block)
107
57
  end
108
58
 
109
- # Retrieves the policy scope for the given record.
110
- #
111
- # @see https://github.com/varvet/pundit#scopes
112
- # @param user [Object] the user that initiated the action
113
- # @param scope [Object] the object we're retrieving the policy scope for
114
- # @raise [NotDefinedError] if the policy scope cannot be found
115
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
116
- # @return [Scope{#resolve}] instance of scope class which can resolve to a scope
117
- def policy_scope!(user, scope)
118
- policy_scope_class = PolicyFinder.new(scope).scope!
119
- return unless policy_scope_class
120
-
121
- begin
122
- policy_scope = policy_scope_class.new(user, pundit_model(scope))
123
- rescue ArgumentError
124
- raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
125
- end
126
-
127
- policy_scope.resolve
59
+ # @see Pundit::Context#policy_scope!
60
+ # @since v0.1.0
61
+ def policy_scope!(user, *args, **kwargs, &block)
62
+ Context.new(user: user).policy_scope!(*args, **kwargs, &block)
128
63
  end
129
64
 
130
- # Retrieves the policy for the given record.
131
- #
132
- # @see https://github.com/varvet/pundit#policies
133
- # @param user [Object] the user that initiated the action
134
- # @param record [Object] the object we're retrieving the policy for
135
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
136
- # @return [Object, nil] instance of policy class with query methods
137
- def policy(user, record)
138
- policy = PolicyFinder.new(record).policy
139
- policy&.new(user, pundit_model(record))
140
- rescue ArgumentError
141
- raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
65
+ # @see Pundit::Context#policy
66
+ # @since v0.1.0
67
+ def policy(user, *args, **kwargs, &block)
68
+ Context.new(user: user).policy(*args, **kwargs, &block)
142
69
  end
143
70
 
144
- # Retrieves the policy for the given record.
145
- #
146
- # @see https://github.com/varvet/pundit#policies
147
- # @param user [Object] the user that initiated the action
148
- # @param record [Object] the object we're retrieving the policy for
149
- # @raise [NotDefinedError] if the policy cannot be found
150
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
151
- # @return [Object] instance of policy class with query methods
152
- def policy!(user, record)
153
- policy = PolicyFinder.new(record).policy!
154
- policy.new(user, pundit_model(record))
155
- rescue ArgumentError
156
- raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
157
- end
158
-
159
- private
160
-
161
- def pundit_model(record)
162
- record.is_a?(Array) ? record.last : record
163
- end
164
- end
165
-
166
- # @api private
167
- module Helper
168
- def policy_scope(scope)
169
- pundit_policy_scope(scope)
71
+ # @see Pundit::Context#policy!
72
+ # @since v0.1.0
73
+ def policy!(user, *args, **kwargs, &block)
74
+ Context.new(user: user).policy!(*args, **kwargs, &block)
170
75
  end
171
76
  end
172
77
  end