pundit 2.4.0 → 2.5.1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -42
  3. data/README.md +31 -1
  4. data/lib/generators/pundit/install/install_generator.rb +3 -1
  5. data/lib/generators/pundit/policy/policy_generator.rb +3 -1
  6. data/lib/generators/rspec/policy_generator.rb +4 -1
  7. data/lib/generators/test_unit/policy_generator.rb +4 -1
  8. data/lib/pundit/authorization.rb +170 -77
  9. data/lib/pundit/cache_store/legacy_store.rb +10 -0
  10. data/lib/pundit/cache_store/null_store.rb +12 -0
  11. data/lib/pundit/cache_store.rb +24 -0
  12. data/lib/pundit/context.rb +89 -26
  13. data/lib/pundit/error.rb +71 -0
  14. data/lib/pundit/helper.rb +16 -0
  15. data/lib/pundit/policy_finder.rb +33 -1
  16. data/lib/pundit/railtie.rb +20 -0
  17. data/lib/pundit/rspec.rb +69 -6
  18. data/lib/pundit/version.rb +2 -1
  19. data/lib/pundit.rb +27 -61
  20. metadata +19 -179
  21. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
  22. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -26
  23. data/.github/PULL_REQUEST_TEMPLATE/gem_release_template.md +0 -8
  24. data/.github/pull_request_template.md +0 -9
  25. data/.github/workflows/main.yml +0 -112
  26. data/.github/workflows/push_gem.yml +0 -33
  27. data/.gitignore +0 -19
  28. data/.rubocop.yml +0 -63
  29. data/.yardopts +0 -1
  30. data/CODE_OF_CONDUCT.md +0 -28
  31. data/CONTRIBUTING.md +0 -31
  32. data/Gemfile +0 -8
  33. data/Rakefile +0 -20
  34. data/config/rubocop-rspec.yml +0 -5
  35. data/pundit.gemspec +0 -35
  36. data/spec/authorization_spec.rb +0 -274
  37. data/spec/dsl_spec.rb +0 -30
  38. data/spec/generators_spec.rb +0 -43
  39. data/spec/policies/post_policy_spec.rb +0 -49
  40. data/spec/policy_finder_spec.rb +0 -187
  41. data/spec/pundit_spec.rb +0 -448
  42. data/spec/spec_helper.rb +0 -352
  43. /data/lib/generators/pundit/install/templates/{application_policy.rb → application_policy.rb.tt} +0 -0
  44. /data/lib/generators/pundit/policy/templates/{policy.rb → policy.rb.tt} +0 -0
  45. /data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
  46. /data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
@@ -2,12 +2,22 @@
2
2
 
3
3
  module Pundit
4
4
  module CacheStore
5
+ # A cache store that uses only the record as a cache key, and ignores the user.
6
+ #
7
+ # The original cache mechanism used by Pundit.
8
+ #
5
9
  # @api private
10
+ # @since v2.3.2
6
11
  class LegacyStore
12
+ # @since v2.3.2
7
13
  def initialize(hash = {})
8
14
  @store = hash
9
15
  end
10
16
 
17
+ # A cache store that uses only the record as a cache key, and ignores the user.
18
+ #
19
+ # @note `nil` results are not cached.
20
+ # @since v2.3.2
11
21
  def fetch(user:, record:)
12
22
  _ = user
13
23
  @store[record] ||= yield
@@ -2,14 +2,26 @@
2
2
 
3
3
  module Pundit
4
4
  module CacheStore
5
+ # A cache store that does not cache anything.
6
+ #
7
+ # Use `NullStore.instance` to get the singleton instance, it is thread-safe.
8
+ #
9
+ # @see Pundit::Context#initialize
5
10
  # @api private
11
+ # @since v2.3.2
6
12
  class NullStore
7
13
  @instance = new
8
14
 
9
15
  class << self
16
+ # @since v2.3.2
17
+ # @return [NullStore] the singleton instance
10
18
  attr_reader :instance
11
19
  end
12
20
 
21
+ # Always yields, does not cache anything.
22
+ # @yield
23
+ # @return [any] whatever the block returns.
24
+ # @since v2.3.2
13
25
  def fetch(*, **)
14
26
  yield
15
27
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pundit
4
+ # Namespace for cache store implementations.
5
+ #
6
+ # Cache stores are used to cache policy lookups, so you get the same policy
7
+ # instance for the same record.
8
+ # @since v2.3.2
9
+ module CacheStore
10
+ # @!group Cache Store Interface
11
+
12
+ # @!method fetch(user:, record:, &block)
13
+ # Looks up a stored policy or generate a new one.
14
+ #
15
+ # @since v2.3.2
16
+ # @note This is a method template, but the method does not exist in this module.
17
+ # @param user [Object] the user that initiated the action
18
+ # @param record [Object] the object being accessed
19
+ # @param block [Proc] the block to execute if missing
20
+ # @return [Object] the policy
21
+
22
+ # @!endgroup
23
+ end
24
+ end
@@ -1,27 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pundit
4
+ # {Pundit::Context} is intended to be created once per request and user, and
5
+ # it is then used to perform authorization checks throughout the request.
6
+ #
7
+ # @example Using Sinatra
8
+ # helpers do
9
+ # def current_user = ...
10
+ #
11
+ # def pundit
12
+ # @pundit ||= Pundit::Context.new(user: current_user)
13
+ # end
14
+ # end
15
+ #
16
+ # get "/posts/:id" do |id|
17
+ # pundit.authorize(Post.find(id), query: :show?)
18
+ # end
19
+ #
20
+ # @example Using [Roda](https://roda.jeremyevans.net/index.html)
21
+ # route do |r|
22
+ # context = Pundit::Context.new(user:)
23
+ #
24
+ # r.get "posts", Integer do |id|
25
+ # context.authorize(Post.find(id), query: :show?)
26
+ # end
27
+ # end
28
+ #
29
+ # @since v2.3.2
4
30
  class Context
31
+ # @see Pundit::Authorization#pundit
32
+ # @param user later passed to policies and scopes
33
+ # @param policy_cache [#fetch] cache store for policies (see e.g. {CacheStore::NullStore})
34
+ # @since v2.3.2
5
35
  def initialize(user:, policy_cache: CacheStore::NullStore.instance)
6
36
  @user = user
7
37
  @policy_cache = policy_cache
8
38
  end
9
39
 
40
+ # @api public
41
+ # @see #initialize
42
+ # @since v2.3.2
10
43
  attr_reader :user
11
44
 
12
45
  # @api private
46
+ # @see #initialize
47
+ # @since v2.3.2
13
48
  attr_reader :policy_cache
14
49
 
50
+ # @!group Policies
51
+
15
52
  # Retrieves the policy for the given record, initializing it with the
16
53
  # record and user and finally throwing an error if the user is not
17
54
  # authorized to perform the given action.
18
55
  #
19
- # @param user [Object] the user that initiated the action
20
56
  # @param possibly_namespaced_record [Object, Array] the object we're checking permissions of
21
57
  # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
22
58
  # @param policy_class [Class] the policy class we want to force use of
23
59
  # @raise [NotAuthorizedError] if the given query method returned false
24
60
  # @return [Object] Always returns the passed object record
61
+ # @since v2.3.2
25
62
  def authorize(possibly_namespaced_record, query:, policy_class:)
26
63
  record = pundit_model(possibly_namespaced_record)
27
64
  policy = if policy_class
@@ -35,13 +72,40 @@ module Pundit
35
72
  record
36
73
  end
37
74
 
75
+ # Retrieves the policy for the given record.
76
+ #
77
+ # @see https://github.com/varvet/pundit#policies
78
+ # @param record [Object] the object we're retrieving the policy for
79
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
80
+ # @return [Object, nil] instance of policy class with query methods
81
+ # @since v2.3.2
82
+ def policy(record)
83
+ cached_find(record, &:policy)
84
+ end
85
+
86
+ # Retrieves the policy for the given record, or raises if not found.
87
+ #
88
+ # @see https://github.com/varvet/pundit#policies
89
+ # @param record [Object] the object we're retrieving the policy for
90
+ # @raise [NotDefinedError] if the policy cannot be found
91
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
92
+ # @return [Object] instance of policy class with query methods
93
+ # @since v2.3.2
94
+ def policy!(record)
95
+ cached_find(record, &:policy!)
96
+ end
97
+
98
+ # @!endgroup
99
+
100
+ # @!group Scopes
101
+
38
102
  # Retrieves the policy scope for the given record.
39
103
  #
40
104
  # @see https://github.com/varvet/pundit#scopes
41
- # @param user [Object] the user that initiated the action
42
105
  # @param scope [Object] the object we're retrieving the policy scope for
43
106
  # @raise [InvalidConstructorError] if the policy constructor called incorrectly
44
107
  # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
108
+ # @since v2.3.2
45
109
  def policy_scope(scope)
46
110
  policy_scope_class = policy_finder(scope).scope
47
111
  return unless policy_scope_class
@@ -58,14 +122,13 @@ module Pundit
58
122
  # Retrieves the policy scope for the given record. Raises if not found.
59
123
  #
60
124
  # @see https://github.com/varvet/pundit#scopes
61
- # @param user [Object] the user that initiated the action
62
125
  # @param scope [Object] the object we're retrieving the policy scope for
63
126
  # @raise [NotDefinedError] if the policy scope cannot be found
64
127
  # @raise [InvalidConstructorError] if the policy constructor called incorrectly
65
128
  # @return [Scope{#resolve}] instance of scope class which can resolve to a scope
129
+ # @since v2.3.2
66
130
  def policy_scope!(scope)
67
131
  policy_scope_class = policy_finder(scope).scope!
68
- return unless policy_scope_class
69
132
 
70
133
  begin
71
134
  policy_scope = policy_scope_class.new(user, pundit_model(scope))
@@ -76,31 +139,22 @@ module Pundit
76
139
  policy_scope.resolve
77
140
  end
78
141
 
79
- # Retrieves the policy for the given record.
80
- #
81
- # @see https://github.com/varvet/pundit#policies
82
- # @param user [Object] the user that initiated the action
83
- # @param record [Object] the object we're retrieving the policy for
84
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
85
- # @return [Object, nil] instance of policy class with query methods
86
- def policy(record)
87
- cached_find(record, &:policy)
88
- end
89
-
90
- # Retrieves the policy for the given record. Raises if not found.
91
- #
92
- # @see https://github.com/varvet/pundit#policies
93
- # @param user [Object] the user that initiated the action
94
- # @param record [Object] the object we're retrieving the policy for
95
- # @raise [NotDefinedError] if the policy cannot be found
96
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
97
- # @return [Object] instance of policy class with query methods
98
- def policy!(record)
99
- cached_find(record, &:policy!)
100
- end
142
+ # @!endgroup
101
143
 
102
144
  private
103
145
 
146
+ # @!group Private Helpers
147
+
148
+ # Finds a cached policy for the given record, or yields to find one.
149
+ #
150
+ # @api private
151
+ # @param record [Object] the object we're retrieving the policy for
152
+ # @yield a policy finder if no policy was cached
153
+ # @yieldparam [PolicyFinder] policy_finder
154
+ # @yieldreturn [#new(user, model)]
155
+ # @return [Policy, nil] an instantiated policy
156
+ # @raise [InvalidConstructorError] if policy can't be instantated
157
+ # @since v2.3.2
104
158
  def cached_find(record)
105
159
  policy_cache.fetch(user: user, record: record) do
106
160
  klass = yield policy_finder(record)
@@ -116,10 +170,19 @@ module Pundit
116
170
  end
117
171
  end
118
172
 
173
+ # Return a policy finder for the given record.
174
+ #
175
+ # @api private
176
+ # @return [PolicyFinder]
177
+ # @since v2.3.2
119
178
  def policy_finder(record)
120
179
  PolicyFinder.new(record)
121
180
  end
122
181
 
182
+ # Given a possibly namespaced record, return the actual record.
183
+ #
184
+ # @api private
185
+ # @since v2.3.2
123
186
  def pundit_model(record)
124
187
  record.is_a?(Array) ? record.last : record
125
188
  end
@@ -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,12 +65,14 @@ 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
  #
75
+ # @since v1.1.0
59
76
  def param_key # rubocop:disable Metrics/AbcSize
60
77
  model = object.is_a?(Array) ? object.last : object
61
78
 
@@ -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,13 +1,31 @@
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)
8
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
9
24
  attr_writer :description
10
25
 
26
+ # Used to retrieve a suitable description for the Pundit `permit` matcher.
27
+ # @api private
28
+ # @private
11
29
  def description(user, record)
12
30
  return @description.call(user, record) if defined?(@description) && @description.respond_to?(:call)
13
31
 
@@ -32,15 +50,21 @@ module Pundit
32
50
  end
33
51
 
34
52
  failure_message_proc = lambda do |policy|
35
- was_were = @violating_permissions.count > 1 ? "were" : "was"
36
53
  "Expected #{policy} to grant #{permissions.to_sentence} on " \
37
- "#{record} but #{@violating_permissions.to_sentence} #{was_were} not granted"
54
+ "#{record} but #{@violating_permissions.to_sentence} #{was_or_were} not granted"
38
55
  end
39
56
 
40
57
  failure_message_when_negated_proc = lambda do |policy|
41
- was_were = @violating_permissions.count > 1 ? "were" : "was"
42
58
  "Expected #{policy} not to grant #{permissions.to_sentence} on " \
43
- "#{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
44
68
  end
45
69
 
46
70
  description do
@@ -53,21 +77,57 @@ module Pundit
53
77
  failure_message(&failure_message_proc)
54
78
  failure_message_when_negated(&failure_message_when_negated_proc)
55
79
  else
80
+ # :nocov:
81
+ # Compatibility with RSpec < 3.0, released 2014-06-01.
56
82
  match_for_should(&match_proc)
57
83
  match_for_should_not(&match_when_negated_proc)
58
84
  failure_message_for_should(&failure_message_proc)
59
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:
60
100
  end
61
101
 
62
102
  def permissions
63
- current_example = ::RSpec.respond_to?(:current_example) ? ::RSpec.current_example : example
64
- 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
65
108
  end
66
109
  end
67
110
  # rubocop:enable Metrics/BlockLength
68
111
  end
69
112
 
113
+ # Mixed in to all policy example groups to provide a DSL.
70
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]
71
131
  def permissions(*list, &block)
72
132
  metadata = { permissions: list, caller: caller }
73
133
 
@@ -81,6 +141,9 @@ module Pundit
81
141
  end
82
142
  end
83
143
 
144
+ # Mixed in to all policy example groups.
145
+ #
146
+ # @private not useful
84
147
  module PolicyExampleGroup
85
148
  include Pundit::RSpec::Matchers
86
149
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pundit
4
- VERSION = "2.4.0"
4
+ # The current version of Pundit.
5
+ VERSION = "2.5.1"
5
6
  end