New Upstream Release - ruby-pundit

Ready changes

Summary

Merged new upstream version: 2.3.0 (was: 2.1.0).

Resulting package

Built on 2023-01-07T18:23 (took 10m3s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases ruby-pundit

Lintian Result

Diff

diff --git a/.gitignore b/.gitignore
index 1b71c6b..f3dcde2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 *.rbc
 .bundle
 .config
+.coverage
 .yardoc
 Gemfile.lock
 InstalledFiles
diff --git a/.rubocop.yml b/.rubocop.yml
index 6306ca1..249d675 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,10 +1,9 @@
 AllCops:
-  DisplayCopNames: true
-  TargetRubyVersion: 2.2
+  TargetRubyVersion: 2.6
   Exclude:
-    - "gemfiles/**/*"
-    - "vendor/**/*"
-    - "lib/generators/**/*"
+    - "lib/generators/**/templates/**/*"
+  SuggestExtensions: false
+  NewCops: disable
 
 Metrics/BlockLength:
   Exclude:
@@ -18,7 +17,7 @@ Metrics/ModuleLength:
   Exclude:
     - "**/*_spec.rb"
 
-Metrics/LineLength:
+Layout/LineLength:
   Max: 120
 
 Metrics/AbcSize:
@@ -30,7 +29,10 @@ Metrics/CyclomaticComplexity:
 Metrics/PerceivedComplexity:
   Enabled: false
 
-Layout/AlignParameters:
+Gemspec/RequiredRubyVersion:
+ Enabled: false
+
+Layout/ParameterAlignment:
   EnforcedStyle: with_fixed_indentation
 
 Layout/CaseIndentation:
@@ -40,15 +42,9 @@ Layout/CaseIndentation:
     - end
   IndentOneStep: true
 
-Layout/AccessModifierIndentation:
-  EnforcedStyle: outdent
-
 Layout/EndAlignment:
   EnforcedStyleAlignWith: variable
 
-Style/FrozenStringLiteralComment:
-  Enabled: true
-
 Style/PercentLiteralDelimiters:
   PreferredDelimiters:
     '%w': "[]"
@@ -72,5 +68,5 @@ Style/Not:
 Style/DoubleNegation:
   Enabled: false
 
-Documentation:
+Style/Documentation:
   Enabled: false # TODO: Enable again once we have more docs
diff --git a/.travis.yml b/.travis.yml
index e2f943e..184a9e6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,21 +1,26 @@
 language: ruby
-before_install:
-  - gem install bundler -v 1.17.3
+dist: focal
 
 matrix:
   include:
-    - rvm: 2.5.1 # Pre-installed Ruby version
+    - name: "RuboCop lint on pre-installed Ruby version"
+      rvm: 2.7.1 # Pre-installed Ruby version
+      before_install:
+        - gem install bundler
       script: bundle exec rake rubocop # ONLY lint once, first
-    - rvm: 2.1
-    - rvm: 2.2
-    - rvm: 2.3.5
-    - rvm: 2.4.6
-    - rvm: 2.5.5
-    - rvm: 2.6.3
-    - rvm: jruby-9.1.8.0
-      env:
-        - JRUBY_OPTS="--debug"
-      jdk: openjdk8
-    - rvm: jruby-9.2.8.0
+    - rvm: 2.6.7
+      before_script:
+        - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
+        - chmod +x ./cc-test-reporter
+        - ./cc-test-reporter before-build
+      after_script:
+        - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
+    - rvm: 2.7.3
+    - rvm: 3.0.1
+    - rvm: 3.1.0
+    - rvm: jruby-9.2.17.0
       env:
         - JRUBY_OPTS="--debug"
+    - rvm: truffleruby-head
+  allow_failures:
+    - rvm: truffleruby-head
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5217ac1..279fba1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,51 @@
 # Pundit
 
+## 2.3.0 (2022-12-19)
+
+### Added
+
+- add support for rubocop-rspec syntax extensions (#745)
+
+## 2.2.0 (2022-02-11)
+
+### Fixed
+
+- Using `policy_class` and a namespaced record now passes only the record when instantiating the policy. (#697, #689, #694, #666)
+
+### Changed
+
+- Require users to explicitly define Scope#resolve in generated policies (#711, #722)
+
+### Deprecated
+
+- Deprecate `include Pundit` in favor of `include Pundit::Authorization` (#621)
+
+## 2.1.1 (2021-08-13)
+
+Friday 13th-release!
+
+Careful! The bugfix below (#626) could break existing code. If you rely on the
+return value for `authorize` and namespaced policies you might need to do some
+changes.
+
+### Fixed
+
+- `.authorize` and `#authorize` return the instance, even for namespaced
+  policies (#626)
+
+### Changed
+
+- Generate application scope with `protected` attr_readers. (#616)
+
+### Removed
+
+- Dropped support for Ruby end-of-life versions: 2.1 and 2.2. (#604)
+- Dropped support for Ruby end-of-life versions: 2.3 (#633)
+- Dropped support for Ruby end-of-life versions: 2.4, 2.5 and JRuby 9.1 (#676)
+- Dropped support for RSpec 2 (#615)
+
+## 2.1.0 (2019-08-14)
+
 ### Fixed
 
 - Avoid name clashes with the Error class. (#590)
diff --git a/README.md b/README.md
index 3aa7774..4ce649e 100644
--- a/README.md
+++ b/README.md
@@ -7,11 +7,11 @@
 
 Pundit provides a set of helpers which guide you in leveraging regular Ruby
 classes and object oriented design patterns to build a simple, robust and
-scaleable authorization system.
+scalable authorization system.
 
 Links:
 
-- [API documentation](http://www.rubydoc.info/gems/pundit)
+- [API documentation for the most recent version](http://www.rubydoc.info/gems/pundit)
 - [Source Code](https://github.com/varvet/pundit)
 - [Contributing](https://github.com/varvet/pundit/blob/master/CONTRIBUTING.md)
 - [Code of Conduct](https://github.com/varvet/pundit/blob/master/CODE_OF_CONDUCT.md)
@@ -22,16 +22,17 @@ Sponsored by:
 
 ## Installation
 
-``` ruby
-gem "pundit"
+> **Please note** that the README on GitHub is accurate with the _latest code on GitHub_. You are most likely using a released version of Pundit, so please refer to the [documentation for the latest released version of Pundit](https://www.rubydoc.info/gems/pundit).
+
+``` sh
+bundle add pundit
 ```
 
-Include Pundit in your application controller:
+Include `Pundit::Authorization` in your application controller:
 
 ``` ruby
 class ApplicationController < ActionController::Base
-  include Pundit
-  protect_from_forgery
+  include Pundit::Authorization
 end
 ```
 
@@ -61,7 +62,7 @@ class PostPolicy
   end
 
   def update?
-    user.admin? or not post.published?
+    user.admin? || !post.published?
   end
 end
 ```
@@ -165,13 +166,18 @@ def admin_list
 end
 ```
 
-`authorize` returns the object passed to it, so you can chain it like this:
+`authorize` returns the instance passed to it, so you can chain it like this:
 
 Controller:
 ```ruby
 def show
   @user = authorize User.find(params[:id])
 end
+
+# return the record even for namespaced policies
+def show
+  @user = authorize [:admin, User.find(params[:id])]
+end
 ```
 
 You can easily get a hold of an instance of the policy through the `policy`
@@ -190,8 +196,17 @@ you can retrieve it by passing a symbol.
 
 ```ruby
 # app/policies/dashboard_policy.rb
-class DashboardPolicy < Struct.new(:user, :dashboard)
-  # ...
+class DashboardPolicy
+  attr_reader :user
+
+  # _record in this example will just be :dashboard
+  def initialize(user, _record)
+    @user = user
+  end
+
+  def show?
+    user.admin?
+  end
 end
 ```
 
@@ -201,7 +216,10 @@ is what is passed as the record to `authorize` below.
 
 ```ruby
 # In controllers
-authorize :dashboard, :show?
+def show
+  authorize :dashboard, :show?
+  ...
+end
 ```
 
 ```erb
@@ -220,8 +238,6 @@ define a class called a policy scope. It can look something like this:
 ``` ruby
 class PostPolicy < ApplicationPolicy
   class Scope
-    attr_reader :user, :scope
-
     def initialize(user, scope)
       @user  = user
       @scope = scope
@@ -234,6 +250,10 @@ class PostPolicy < ApplicationPolicy
         scope.where(published: true)
       end
     end
+
+    private
+
+    attr_reader :user, :scope
   end
 
   def update?
@@ -296,13 +316,11 @@ def index
 end
 ```
 
-Just as with your policy, this will automatically infer that you want to use
-the `PostPolicy::Scope` class, it will instantiate this class and call
-`resolve` on the instance. In this case it is a shortcut for doing:
+In this case it is a shortcut for doing:
 
 ``` ruby
 def index
-  @posts = PostPolicy::Scope.new(current_user, Post).resolve
+  @publications = PublicationPolicy::Scope.new(current_user, Post).resolve
 end
 ```
 
@@ -330,7 +348,7 @@ that you haven't forgotten to authorize the action. For example:
 
 ``` ruby
 class ApplicationController < ActionController::Base
-  include Pundit
+  include Pundit::Authorization
   after_action :verify_authorized
 end
 ```
@@ -343,7 +361,7 @@ authorize individual instances.
 
 ``` ruby
 class ApplicationController < ActionController::Base
-  include Pundit
+  include Pundit::Authorization
   after_action :verify_authorized, except: :index
   after_action :verify_policy_scoped, only: :index
 end
@@ -391,6 +409,16 @@ class Post
 end
 ```
 
+Alternatively, you can declare an instance method:
+
+``` ruby
+class Post
+  def policy_class
+    PostablePolicy
+  end
+end
+```
+
 ## Just plain old Ruby
 
 As you can see, Pundit doesn't do anything you couldn't have easily done
@@ -476,8 +504,7 @@ method in every controller.
 
 ```ruby
 class ApplicationController < ActionController::Base
-  protect_from_forgery
-  include Pundit
+  include Pundit::Authorization
 
   rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
 
@@ -485,7 +512,7 @@ class ApplicationController < ActionController::Base
 
   def user_not_authorized
     flash[:alert] = "You are not authorized to perform this action."
-    redirect_to(request.referrer || root_path)
+    redirect_back(fallback_location: root_path)
   end
 end
 ```
@@ -514,7 +541,7 @@ class ApplicationController < ActionController::Base
    policy_name = exception.policy.class.to_s.underscore
 
    flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
-   redirect_to(request.referrer || root_path)
+   redirect_back(fallback_url: root_path)
  end
 end
 ```
@@ -597,8 +624,7 @@ class Admin::PostController < AdminController
   end
 
   def show
-    post = Post.find(params[:id])
-    authorize(post)
+    post = authorize Post.find(params[:id])
   end
 end
 ```
@@ -631,7 +657,7 @@ class UserContext
 end
 
 class ApplicationController
-  include Pundit
+  include Pundit::Authorization
 
   def pundit_user
     UserContext.new(current_user, request.ip)
@@ -641,9 +667,8 @@ end
 
 ## Strong parameters
 
-In Rails 4 (or Rails 3.2 with the
-[strong_parameters](https://github.com/rails/strong_parameters) gem),
-mass-assignment protection is handled in the controller.  With Pundit you can
+In Rails,
+mass-assignment protection is handled in the controller. With Pundit you can
 control which attributes a user has access to update via your policies. You can
 set up a `permitted_attributes` method in your policy like this:
 
@@ -667,7 +692,7 @@ You can now retrieve these attributes from the policy:
 class PostsController < ApplicationController
   def update
     @post = Post.find(params[:id])
-    if @post.update_attributes(post_params)
+    if @post.update(post_params)
       redirect_to @post
     else
       render :edit
@@ -689,7 +714,7 @@ However, this is a bit cumbersome, so Pundit provides a convenient helper method
 class PostsController < ApplicationController
   def update
     @post = Post.find(params[:id])
-    if @post.update_attributes(permitted_attributes(@post))
+    if @post.update(permitted_attributes(@post))
       redirect_to @post
     else
       render :edit
@@ -777,11 +802,22 @@ An alternative approach to Pundit policy specs is scoping them to a user context
 
 Pundit does not provide a DSL for testing scopes. Just test it like a regular Ruby class!
 
+### Linting with RuboCop RSpec
+
+When you lint your RSpec spec files with `rubocop-rspec`, it will fail to properly detect RSpec constructs that Pundit defines, `permissions`.
+Make sure to use `rubocop-rspec` 2.0 or newer and add the following to your `.rubocop.yml`:
+
+```yaml
+inherit_gem:
+  pundit: config/rubocop-rspec.yml
+```
+
 # External Resources
 
 - [RailsApps Example Application: Pundit and Devise](https://github.com/RailsApps/rails-devise-pundit)
 - [Migrating to Pundit from CanCan](http://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/)
 - [Testing Pundit Policies with RSpec](http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/)
+- [Testing Pundit with Minitest](https://github.com/varvet/pundit/issues/204#issuecomment-60166450)
 - [Using Pundit outside of a Rails controller](https://github.com/varvet/pundit/pull/136)
 - [Straightforward Rails Authorization with Pundit](http://www.sitepoint.com/straightforward-rails-authorization-with-pundit/)
 
diff --git a/config/rubocop-rspec.yml b/config/rubocop-rspec.yml
new file mode 100644
index 0000000..6f4c7e1
--- /dev/null
+++ b/config/rubocop-rspec.yml
@@ -0,0 +1,5 @@
+RSpec:
+  Language:
+    ExampleGroups:
+      Regular:
+        - permissions
diff --git a/debian/changelog b/debian/changelog
index 4a94efe..9b658ac 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,9 +1,10 @@
-ruby-pundit (2.1.0-2) UNRELEASED; urgency=medium
+ruby-pundit (2.3.0-1) UNRELEASED; urgency=medium
 
   * Update standards version to 4.5.1, no changes needed.
   * Update standards version to 4.6.2, no changes needed.
+  * New upstream release.
 
- -- Debian Janitor <janitor@jelmer.uk>  Sat, 11 Sep 2021 04:33:55 -0000
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 07 Jan 2023 18:14:22 -0000
 
 ruby-pundit (2.1.0-1) unstable; urgency=medium
 
diff --git a/lib/generators/pundit/install/install_generator.rb b/lib/generators/pundit/install/install_generator.rb
index 2aa26c3..de87aa9 100644
--- a/lib/generators/pundit/install/install_generator.rb
+++ b/lib/generators/pundit/install/install_generator.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
 module Pundit
   module Generators
     class InstallGenerator < ::Rails::Generators::Base
-      source_root File.expand_path('templates', __dir__)
+      source_root File.expand_path("templates", __dir__)
 
       def copy_application_policy
-        template 'application_policy.rb', 'app/policies/application_policy.rb'
+        template "application_policy.rb", "app/policies/application_policy.rb"
       end
     end
   end
diff --git a/lib/generators/pundit/install/templates/application_policy.rb b/lib/generators/pundit/install/templates/application_policy.rb
index eefe976..e000cba 100644
--- a/lib/generators/pundit/install/templates/application_policy.rb
+++ b/lib/generators/pundit/install/templates/application_policy.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 class ApplicationPolicy
   attr_reader :user, :record
 
@@ -35,15 +37,17 @@ class ApplicationPolicy
   end
 
   class Scope
-    attr_reader :user, :scope
-
     def initialize(user, scope)
       @user = user
       @scope = scope
     end
 
     def resolve
-      scope.all
+      raise NotImplementedError, "You must define #resolve in #{self.class}"
     end
+
+    private
+
+    attr_reader :user, :scope
   end
 end
diff --git a/lib/generators/pundit/policy/policy_generator.rb b/lib/generators/pundit/policy/policy_generator.rb
index 95bcc28..b4734fc 100644
--- a/lib/generators/pundit/policy/policy_generator.rb
+++ b/lib/generators/pundit/policy/policy_generator.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
 module Pundit
   module Generators
     class PolicyGenerator < ::Rails::Generators::NamedBase
-      source_root File.expand_path('templates', __dir__)
+      source_root File.expand_path("templates", __dir__)
 
       def create_policy
-        template 'policy.rb', File.join('app/policies', class_path, "#{file_name}_policy.rb")
+        template "policy.rb", File.join("app/policies", class_path, "#{file_name}_policy.rb")
       end
 
       hook_for :test_framework
diff --git a/lib/generators/pundit/policy/templates/policy.rb b/lib/generators/pundit/policy/templates/policy.rb
index 8eb9def..6798550 100644
--- a/lib/generators/pundit/policy/templates/policy.rb
+++ b/lib/generators/pundit/policy/templates/policy.rb
@@ -1,9 +1,10 @@
 <% module_namespacing do -%>
 class <%= class_name %>Policy < ApplicationPolicy
   class Scope < Scope
-    def resolve
-      scope.all
-    end
+    # NOTE: Be explicit about which records you allow access to!
+    # def resolve
+    #   scope.all
+    # end
   end
 end
 <% end -%>
diff --git a/lib/generators/rspec/policy_generator.rb b/lib/generators/rspec/policy_generator.rb
index 24db961..026e60d 100644
--- a/lib/generators/rspec/policy_generator.rb
+++ b/lib/generators/rspec/policy_generator.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
 module Rspec
   module Generators
     class PolicyGenerator < ::Rails::Generators::NamedBase
-      source_root File.expand_path('templates', __dir__)
+      source_root File.expand_path("templates", __dir__)
 
       def create_policy_spec
-        template 'policy_spec.rb', File.join('spec/policies', class_path, "#{file_name}_policy_spec.rb")
+        template "policy_spec.rb", File.join("spec/policies", class_path, "#{file_name}_policy_spec.rb")
       end
     end
   end
diff --git a/lib/generators/test_unit/policy_generator.rb b/lib/generators/test_unit/policy_generator.rb
index c22f4fa..89c375f 100644
--- a/lib/generators/test_unit/policy_generator.rb
+++ b/lib/generators/test_unit/policy_generator.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
 module TestUnit
   module Generators
     class PolicyGenerator < ::Rails::Generators::NamedBase
-      source_root File.expand_path('templates', __dir__)
+      source_root File.expand_path("templates", __dir__)
 
       def create_policy_test
-        template 'policy_test.rb', File.join('test/policies', class_path, "#{file_name}_policy_test.rb")
+        template "policy_test.rb", File.join("test/policies", class_path, "#{file_name}_policy_test.rb")
       end
     end
   end
diff --git a/lib/pundit.rb b/lib/pundit.rb
index 0262f96..99b3502 100644
--- a/lib/pundit.rb
+++ b/lib/pundit.rb
@@ -7,6 +7,7 @@ require "active_support/core_ext/string/inflections"
 require "active_support/core_ext/object/blank"
 require "active_support/core_ext/module/introspection"
 require "active_support/dependencies/autoload"
+require "pundit/authorization"
 
 # @api private
 # To avoid name clashes with common Error naming when mixing in Pundit,
@@ -15,7 +16,7 @@ class Pundit::Error < StandardError; end # rubocop:disable Style/ClassAndModuleC
 
 # @api public
 module Pundit
-  SUFFIX = "Policy".freeze
+  SUFFIX = "Policy"
 
   # @api private
   module Generators; end
@@ -53,7 +54,12 @@ module Pundit
   # Error that will be raised if a policy or policy scope is not defined.
   class NotDefinedError < Error; end
 
-  extend ActiveSupport::Concern
+  def self.included(base)
+    ActiveSupport::Deprecation.warn <<~WARNING
+      'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead.
+    WARNING
+    base.include Authorization
+  end
 
   class << self
     # Retrieves the policy for the given record, initializing it with the
@@ -61,13 +67,19 @@ module Pundit
     # authorized to perform the given action.
     #
     # @param user [Object] the user that initiated the action
-    # @param record [Object] the object we're checking permissions of
+    # @param possibly_namespaced_record [Object, Array] the object we're checking permissions of
     # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
     # @param policy_class [Class] the policy class we want to force use of
+    # @param cache [#[], #[]=] a Hash-like object to cache the found policy instance in
     # @raise [NotAuthorizedError] if the given query method returned false
     # @return [Object] Always returns the passed object record
-    def authorize(user, record, query, policy_class: nil)
-      policy = policy_class ? policy_class.new(user, record) : policy!(user, record)
+    def authorize(user, possibly_namespaced_record, query, policy_class: nil, cache: {})
+      record = pundit_model(possibly_namespaced_record)
+      policy = if policy_class
+        policy_class.new(user, record)
+      else
+        cache[possibly_namespaced_record] ||= policy!(user, possibly_namespaced_record)
+      end
 
       raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
 
@@ -124,7 +136,7 @@ module Pundit
     # @return [Object, nil] instance of policy class with query methods
     def policy(user, record)
       policy = PolicyFinder.new(record).policy
-      policy.new(user, pundit_model(record)) if policy
+      policy&.new(user, pundit_model(record))
     rescue ArgumentError
       raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
     end
@@ -144,7 +156,7 @@ module Pundit
       raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
     end
 
-  private
+    private
 
     def pundit_model(record)
       record.is_a?(Array) ? record.last : record
@@ -157,169 +169,4 @@ module Pundit
       pundit_policy_scope(scope)
     end
   end
-
-  included do
-    helper Helper if respond_to?(:helper)
-    if respond_to?(:helper_method)
-      helper_method :policy
-      helper_method :pundit_policy_scope
-      helper_method :pundit_user
-    end
-  end
-
-protected
-
-  # @return [Boolean] whether authorization has been performed, i.e. whether
-  #                   one {#authorize} or {#skip_authorization} has been called
-  def pundit_policy_authorized?
-    !!@_pundit_policy_authorized
-  end
-
-  # @return [Boolean] whether policy scoping has been performed, i.e. whether
-  #                   one {#policy_scope} or {#skip_policy_scope} has been called
-  def pundit_policy_scoped?
-    !!@_pundit_policy_scoped
-  end
-
-  # Raises an error if authorization has not been performed, usually used as an
-  # `after_action` filter to prevent programmer error in forgetting to call
-  # {#authorize} or {#skip_authorization}.
-  #
-  # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
-  # @raise [AuthorizationNotPerformedError] if authorization has not been performed
-  # @return [void]
-  def verify_authorized
-    raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
-  end
-
-  # Raises an error if policy scoping has not been performed, usually used as an
-  # `after_action` filter to prevent programmer error in forgetting to call
-  # {#policy_scope} or {#skip_policy_scope} in index actions.
-  #
-  # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
-  # @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
-  # @return [void]
-  def verify_policy_scoped
-    raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
-  end
-
-  # Retrieves the policy for the given record, initializing it with the record
-  # and current user and finally throwing an error if the user is not
-  # authorized to perform the given action.
-  #
-  # @param record [Object] the object we're checking permissions of
-  # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
-  #   If omitted then this defaults to the Rails controller action name.
-  # @param policy_class [Class] the policy class we want to force use of
-  # @raise [NotAuthorizedError] if the given query method returned false
-  # @return [Object] Always returns the passed object record
-  def authorize(record, query = nil, policy_class: nil)
-    query ||= "#{action_name}?"
-
-    @_pundit_policy_authorized = true
-
-    policy = policy_class ? policy_class.new(pundit_user, record) : policy(record)
-
-    raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
-
-    record
-  end
-
-  # Allow this action not to perform authorization.
-  #
-  # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
-  # @return [void]
-  def skip_authorization
-    @_pundit_policy_authorized = true
-  end
-
-  # Allow this action not to perform policy scoping.
-  #
-  # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
-  # @return [void]
-  def skip_policy_scope
-    @_pundit_policy_scoped = true
-  end
-
-  # Retrieves the policy scope for the given record.
-  #
-  # @see https://github.com/varvet/pundit#scopes
-  # @param scope [Object] the object we're retrieving the policy scope for
-  # @param policy_scope_class [Class] the policy scope class we want to force use of
-  # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
-  def policy_scope(scope, policy_scope_class: nil)
-    @_pundit_policy_scoped = true
-    policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
-  end
-
-  # Retrieves the policy for the given record.
-  #
-  # @see https://github.com/varvet/pundit#policies
-  # @param record [Object] the object we're retrieving the policy for
-  # @return [Object, nil] instance of policy class with query methods
-  def policy(record)
-    policies[record] ||= Pundit.policy!(pundit_user, record)
-  end
-
-  # Retrieves a set of permitted attributes from the policy by instantiating
-  # the policy class for the given record and calling `permitted_attributes` on
-  # it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers
-  # what key the record should have in the params hash and retrieves the
-  # permitted attributes from the params hash under that key.
-  #
-  # @see https://github.com/varvet/pundit#strong-parameters
-  # @param record [Object] the object we're retrieving permitted attributes for
-  # @param action [Symbol, String] the name of the action being performed on the record (e.g. `:update`).
-  #   If omitted then this defaults to the Rails controller action name.
-  # @return [Hash{String => Object}] the permitted attributes
-  def permitted_attributes(record, action = action_name)
-    policy = policy(record)
-    method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
-      "permitted_attributes_for_#{action}"
-    else
-      "permitted_attributes"
-    end
-    pundit_params_for(record).permit(*policy.public_send(method_name))
-  end
-
-  # Retrieves the params for the given record.
-  #
-  # @param record [Object] the object we're retrieving params for
-  # @return [ActionController::Parameters] the params
-  def pundit_params_for(record)
-    params.require(PolicyFinder.new(record).param_key)
-  end
-
-  # Cache of policies. You should not rely on this method.
-  #
-  # @api private
-  # rubocop:disable Naming/MemoizedInstanceVariableName
-  def policies
-    @_pundit_policies ||= {}
-  end
-  # rubocop:enable Naming/MemoizedInstanceVariableName
-
-  # Cache of policy scope. You should not rely on this method.
-  #
-  # @api private
-  # rubocop:disable Naming/MemoizedInstanceVariableName
-  def policy_scopes
-    @_pundit_policy_scopes ||= {}
-  end
-  # rubocop:enable Naming/MemoizedInstanceVariableName
-
-  # Hook method which allows customizing which user is passed to policies and
-  # scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
-  #
-  # @see https://github.com/varvet/pundit#customize-pundit-user
-  # @return [Object] the user object to be used with pundit
-  def pundit_user
-    current_user
-  end
-
-private
-
-  def pundit_policy_scope(scope)
-    policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
-  end
 end
diff --git a/lib/pundit/authorization.rb b/lib/pundit/authorization.rb
new file mode 100644
index 0000000..1231f2a
--- /dev/null
+++ b/lib/pundit/authorization.rb
@@ -0,0 +1,168 @@
+# frozen_string_literal: true
+
+module Pundit
+  module Authorization
+    extend ActiveSupport::Concern
+
+    included do
+      helper Helper if respond_to?(:helper)
+      if respond_to?(:helper_method)
+        helper_method :policy
+        helper_method :pundit_policy_scope
+        helper_method :pundit_user
+      end
+    end
+
+    protected
+
+    # @return [Boolean] whether authorization has been performed, i.e. whether
+    #                   one {#authorize} or {#skip_authorization} has been called
+    def pundit_policy_authorized?
+      !!@_pundit_policy_authorized
+    end
+
+    # @return [Boolean] whether policy scoping has been performed, i.e. whether
+    #                   one {#policy_scope} or {#skip_policy_scope} has been called
+    def pundit_policy_scoped?
+      !!@_pundit_policy_scoped
+    end
+
+    # Raises an error if authorization has not been performed, usually used as an
+    # `after_action` filter to prevent programmer error in forgetting to call
+    # {#authorize} or {#skip_authorization}.
+    #
+    # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
+    # @raise [AuthorizationNotPerformedError] if authorization has not been performed
+    # @return [void]
+    def verify_authorized
+      raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
+    end
+
+    # Raises an error if policy scoping has not been performed, usually used as an
+    # `after_action` filter to prevent programmer error in forgetting to call
+    # {#policy_scope} or {#skip_policy_scope} in index actions.
+    #
+    # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
+    # @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
+    # @return [void]
+    def verify_policy_scoped
+      raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
+    end
+
+    # Retrieves the policy for the given record, initializing it with the record
+    # and current user and finally throwing an error if the user is not
+    # authorized to perform the given action.
+    #
+    # @param record [Object, Array] the object we're checking permissions of
+    # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
+    #   If omitted then this defaults to the Rails controller action name.
+    # @param policy_class [Class] the policy class we want to force use of
+    # @raise [NotAuthorizedError] if the given query method returned false
+    # @return [Object] Always returns the passed object record
+    def authorize(record, query = nil, policy_class: nil)
+      query ||= "#{action_name}?"
+
+      @_pundit_policy_authorized = true
+
+      Pundit.authorize(pundit_user, record, query, policy_class: policy_class, cache: policies)
+    end
+
+    # Allow this action not to perform authorization.
+    #
+    # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
+    # @return [void]
+    def skip_authorization
+      @_pundit_policy_authorized = :skipped
+    end
+
+    # Allow this action not to perform policy scoping.
+    #
+    # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
+    # @return [void]
+    def skip_policy_scope
+      @_pundit_policy_scoped = :skipped
+    end
+
+    # Retrieves the policy scope for the given record.
+    #
+    # @see https://github.com/varvet/pundit#scopes
+    # @param scope [Object] the object we're retrieving the policy scope for
+    # @param policy_scope_class [Class] the policy scope class we want to force use of
+    # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
+    def policy_scope(scope, policy_scope_class: nil)
+      @_pundit_policy_scoped = true
+      policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
+    end
+
+    # Retrieves the policy for the given record.
+    #
+    # @see https://github.com/varvet/pundit#policies
+    # @param record [Object] the object we're retrieving the policy for
+    # @return [Object, nil] instance of policy class with query methods
+    def policy(record)
+      policies[record] ||= Pundit.policy!(pundit_user, record)
+    end
+
+    # Retrieves a set of permitted attributes from the policy by instantiating
+    # the policy class for the given record and calling `permitted_attributes` on
+    # it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers
+    # what key the record should have in the params hash and retrieves the
+    # permitted attributes from the params hash under that key.
+    #
+    # @see https://github.com/varvet/pundit#strong-parameters
+    # @param record [Object] the object we're retrieving permitted attributes for
+    # @param action [Symbol, String] the name of the action being performed on the record (e.g. `:update`).
+    #   If omitted then this defaults to the Rails controller action name.
+    # @return [Hash{String => Object}] the permitted attributes
+    def permitted_attributes(record, action = action_name)
+      policy = policy(record)
+      method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
+        "permitted_attributes_for_#{action}"
+      else
+        "permitted_attributes"
+      end
+      pundit_params_for(record).permit(*policy.public_send(method_name))
+    end
+
+    # Retrieves the params for the given record.
+    #
+    # @param record [Object] the object we're retrieving params for
+    # @return [ActionController::Parameters] the params
+    def pundit_params_for(record)
+      params.require(PolicyFinder.new(record).param_key)
+    end
+
+    # Cache of policies. You should not rely on this method.
+    #
+    # @api private
+    # rubocop:disable Naming/MemoizedInstanceVariableName
+    def policies
+      @_pundit_policies ||= {}
+    end
+    # rubocop:enable Naming/MemoizedInstanceVariableName
+
+    # Cache of policy scope. You should not rely on this method.
+    #
+    # @api private
+    # rubocop:disable Naming/MemoizedInstanceVariableName
+    def policy_scopes
+      @_pundit_policy_scopes ||= {}
+    end
+    # rubocop:enable Naming/MemoizedInstanceVariableName
+
+    # Hook method which allows customizing which user is passed to policies and
+    # scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
+    #
+    # @see https://github.com/varvet/pundit#customize-pundit-user
+    # @return [Object] the user object to be used with pundit
+    def pundit_user
+      current_user
+    end
+
+    private
+
+    def pundit_policy_scope(scope)
+      policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
+    end
+  end
+end
diff --git a/lib/pundit/policy_finder.rb b/lib/pundit/policy_finder.rb
index df072d9..40467d9 100644
--- a/lib/pundit/policy_finder.rb
+++ b/lib/pundit/policy_finder.rb
@@ -68,7 +68,7 @@ module Pundit
       end
     end
 
-  private
+    private
 
     def find(subject)
       if subject.is_a?(Array)
diff --git a/lib/pundit/rspec.rb b/lib/pundit/rspec.rb
index 687cf17..243ebec 100644
--- a/lib/pundit/rspec.rb
+++ b/lib/pundit/rspec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "active_support/core_ext/array/conversions"
-
 module Pundit
   module RSpec
     module Matchers
@@ -74,17 +72,9 @@ module Pundit
 end
 
 RSpec.configure do |config|
-  if RSpec::Core::Version::STRING.split(".").first.to_i >= 3
-    config.include(
-      Pundit::RSpec::PolicyExampleGroup,
-      type: :policy,
-      file_path: %r{spec/policies}
-    )
-  else
-    config.include(
-      Pundit::RSpec::PolicyExampleGroup,
-      type: :policy,
-      example_group: { file_path: %r{spec/policies} }
-    )
-  end
+  config.include(
+    Pundit::RSpec::PolicyExampleGroup,
+    type: :policy,
+    file_path: %r{spec/policies}
+  )
 end
diff --git a/lib/pundit/version.rb b/lib/pundit/version.rb
index 7869632..e15152f 100644
--- a/lib/pundit/version.rb
+++ b/lib/pundit/version.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 module Pundit
-  VERSION = "2.1.0".freeze
+  VERSION = "2.3.0"
 end
diff --git a/pundit.gemspec b/pundit.gemspec
index 5117319..44c8e8f 100644
--- a/pundit.gemspec
+++ b/pundit.gemspec
@@ -24,8 +24,10 @@ Gem::Specification.new do |gem|
   gem.add_development_dependency "activemodel", ">= 3.0.0"
   gem.add_development_dependency "bundler"
   gem.add_development_dependency "pry"
+  gem.add_development_dependency "railties", ">= 3.0.0"
   gem.add_development_dependency "rake"
-  gem.add_development_dependency "rspec", ">= 2.0.0"
-  gem.add_development_dependency "rubocop", "0.57.2"
+  gem.add_development_dependency "rspec", ">= 3.0.0"
+  gem.add_development_dependency "rubocop", "1.24.0"
+  gem.add_development_dependency "simplecov", ">= 0.17.0"
   gem.add_development_dependency "yard"
 end
diff --git a/spec/authorization_spec.rb b/spec/authorization_spec.rb
new file mode 100644
index 0000000..2995bfd
--- /dev/null
+++ b/spec/authorization_spec.rb
@@ -0,0 +1,258 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe Pundit::Authorization do
+  let(:controller) { Controller.new(user, "update", {}) }
+  let(:user) { double }
+  let(:post) { Post.new(user) }
+  let(:customer_post) { Customer::Post.new(user) }
+  let(:comment) { Comment.new }
+  let(:article) { Article.new }
+  let(:article_tag) { ArticleTag.new }
+  let(:wiki) { Wiki.new }
+
+  describe "#verify_authorized" do
+    it "does nothing when authorized" do
+      controller.authorize(post)
+      controller.verify_authorized
+    end
+
+    it "raises an exception when not authorized" do
+      expect { controller.verify_authorized }.to raise_error(Pundit::AuthorizationNotPerformedError)
+    end
+  end
+
+  describe "#verify_policy_scoped" do
+    it "does nothing when policy_scope is used" do
+      controller.policy_scope(Post)
+      controller.verify_policy_scoped
+    end
+
+    it "raises an exception when policy_scope is not used" do
+      expect { controller.verify_policy_scoped }.to raise_error(Pundit::PolicyScopingNotPerformedError)
+    end
+  end
+
+  describe "#pundit_policy_authorized?" do
+    it "is true when authorized" do
+      controller.authorize(post)
+      expect(controller.pundit_policy_authorized?).to be true
+    end
+
+    it "is false when not authorized" do
+      expect(controller.pundit_policy_authorized?).to be false
+    end
+  end
+
+  describe "#pundit_policy_scoped?" do
+    it "is true when policy_scope is used" do
+      controller.policy_scope(Post)
+      expect(controller.pundit_policy_scoped?).to be true
+    end
+
+    it "is false when policy scope is not used" do
+      expect(controller.pundit_policy_scoped?).to be false
+    end
+  end
+
+  describe "#authorize" do
+    it "infers the policy name and authorizes based on it" do
+      expect(controller.authorize(post)).to be_truthy
+    end
+
+    it "returns the record on successful authorization" do
+      expect(controller.authorize(post)).to eq(post)
+    end
+
+    it "returns the record when passed record with namespace " do
+      expect(controller.authorize([:project, comment], :update?)).to eq(comment)
+    end
+
+    it "returns the record when passed record with nested namespace " do
+      expect(controller.authorize([:project, :admin, comment], :update?)).to eq(comment)
+    end
+
+    it "returns the policy name symbol when passed record with headless policy" do
+      expect(controller.authorize(:publication, :create?)).to eq(:publication)
+    end
+
+    it "returns the class when passed record not a particular instance" do
+      expect(controller.authorize(Post, :show?)).to eq(Post)
+    end
+
+    it "can be given a different permission to check" do
+      expect(controller.authorize(post, :show?)).to be_truthy
+      expect { controller.authorize(post, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
+    end
+
+    it "can be given a different policy class" do
+      expect(controller.authorize(post, :create?, policy_class: PublicationPolicy)).to be_truthy
+    end
+
+    it "works with anonymous class policies" do
+      expect(controller.authorize(article_tag, :show?)).to be_truthy
+      expect { controller.authorize(article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
+    end
+
+    it "throws an exception when the permission check fails" do
+      expect { controller.authorize(Post.new) }.to raise_error(Pundit::NotAuthorizedError)
+    end
+
+    it "throws an exception when a policy cannot be found" do
+      expect { controller.authorize(Article) }.to raise_error(Pundit::NotDefinedError)
+    end
+
+    it "caches the policy" do
+      expect(controller.policies[post]).to be_nil
+      controller.authorize(post)
+      expect(controller.policies[post]).not_to be_nil
+    end
+
+    it "raises an error when the given record is nil" do
+      expect { controller.authorize(nil, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
+    end
+
+    it "raises an error with a invalid policy constructor" do
+      expect { controller.authorize(wiki, :destroy?) }.to raise_error(Pundit::InvalidConstructorError)
+    end
+  end
+
+  describe "#skip_authorization" do
+    it "disables authorization verification" do
+      controller.skip_authorization
+      expect { controller.verify_authorized }.not_to raise_error
+    end
+  end
+
+  describe "#skip_policy_scope" do
+    it "disables policy scope verification" do
+      controller.skip_policy_scope
+      expect { controller.verify_policy_scoped }.not_to raise_error
+    end
+  end
+
+  describe "#pundit_user" do
+    it "returns the same thing as current_user" do
+      expect(controller.pundit_user).to eq controller.current_user
+    end
+  end
+
+  describe "#policy" do
+    it "returns an instantiated policy" do
+      policy = controller.policy(post)
+      expect(policy.user).to eq user
+      expect(policy.post).to eq post
+    end
+
+    it "throws an exception if the given policy can't be found" do
+      expect { controller.policy(article) }.to raise_error(Pundit::NotDefinedError)
+    end
+
+    it "raises an error with a invalid policy constructor" do
+      expect { controller.policy(wiki) }.to raise_error(Pundit::InvalidConstructorError)
+    end
+
+    it "allows policy to be injected" do
+      new_policy = OpenStruct.new
+      controller.policies[post] = new_policy
+
+      expect(controller.policy(post)).to eq new_policy
+    end
+  end
+
+  describe "#policy_scope" do
+    it "returns an instantiated policy scope" do
+      expect(controller.policy_scope(Post)).to eq :published
+    end
+
+    it "allows policy scope class to be overriden" do
+      expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).to eq :published
+    end
+
+    it "throws an exception if the given policy can't be found" do
+      expect { controller.policy_scope(Article) }.to raise_error(Pundit::NotDefinedError)
+    end
+
+    it "raises an error with a invalid policy scope constructor" do
+      expect { controller.policy_scope(Wiki) }.to raise_error(Pundit::InvalidConstructorError)
+    end
+
+    it "allows policy_scope to be injected" do
+      new_scope = OpenStruct.new
+      controller.policy_scopes[Post] = new_scope
+
+      expect(controller.policy_scope(Post)).to eq new_scope
+    end
+  end
+
+  describe "#permitted_attributes" do
+    it "checks policy for permitted attributes" do
+      params = ActionController::Parameters.new(
+        post: {
+          title: "Hello",
+          votes: 5,
+          admin: true
+        }
+      )
+
+      action = "update"
+
+      expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq(
+        "title" => "Hello",
+        "votes" => 5
+      )
+      expect(Controller.new(double, action, params).permitted_attributes(post).to_h).to eq("votes" => 5)
+    end
+
+    it "checks policy for permitted attributes for record of a ActiveModel type" do
+      params = ActionController::Parameters.new(
+        customer_post: {
+          title: "Hello",
+          votes: 5,
+          admin: true
+        }
+      )
+
+      action = "update"
+
+      expect(Controller.new(user, action, params).permitted_attributes(customer_post).to_h).to eq(
+        "title" => "Hello",
+        "votes" => 5
+      )
+      expect(Controller.new(double, action, params).permitted_attributes(customer_post).to_h).to eq(
+        "votes" => 5
+      )
+    end
+  end
+
+  describe "#permitted_attributes_for_action" do
+    it "is checked if it is defined in the policy" do
+      params = ActionController::Parameters.new(
+        post: {
+          title: "Hello",
+          body: "blah",
+          votes: 5,
+          admin: true
+        }
+      )
+
+      action = "revise"
+      expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq("body" => "blah")
+    end
+
+    it "can be explicitly set" do
+      params = ActionController::Parameters.new(
+        post: {
+          title: "Hello",
+          body: "blah",
+          votes: 5,
+          admin: true
+        }
+      )
+
+      action = "update"
+      expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah")
+    end
+  end
+end
diff --git a/spec/generators_spec.rb b/spec/generators_spec.rb
new file mode 100644
index 0000000..5f15877
--- /dev/null
+++ b/spec/generators_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+require "tmpdir"
+
+require "rails/generators"
+require "generators/pundit/install/install_generator"
+require "generators/pundit/policy/policy_generator"
+
+RSpec.describe "generators" do
+  before(:all) do
+    @tmpdir = Dir.mktmpdir
+
+    Dir.chdir(@tmpdir) do
+      Pundit::Generators::InstallGenerator.new([], { quiet: true }).invoke_all
+      Pundit::Generators::PolicyGenerator.new(%w[Widget], { quiet: true }).invoke_all
+
+      require "./app/policies/application_policy"
+      require "./app/policies/widget_policy"
+    end
+  end
+
+  after(:all) do
+    FileUtils.remove_entry(@tmpdir)
+  end
+
+  describe "WidgetPolicy", type: :policy do
+    permissions :index?, :show?, :create?, :new?, :update?, :edit?, :destroy? do
+      it "has safe defaults" do
+        expect(WidgetPolicy).not_to permit(double("User"), double("Widget"))
+      end
+    end
+
+    describe "WidgetPolicy::Scope" do
+      describe "#resolve" do
+        it "raises a descriptive error" do
+          scope = WidgetPolicy::Scope.new(double("User"), double("User.all"))
+          expect { scope.resolve }.to raise_error(NotImplementedError, /WidgetPolicy::Scope/)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/policies/post_policy_spec.rb b/spec/policies/post_policy_spec.rb
index 1d19ef9..54e301e 100644
--- a/spec/policies/post_policy_spec.rb
+++ b/spec/policies/post_policy_spec.rb
@@ -2,7 +2,7 @@
 
 require "spec_helper"
 
-describe PostPolicy do
+RSpec.describe PostPolicy do
   let(:user) { double }
   let(:own_post) { double(user: user) }
   let(:other_post) { double(user: double) }
diff --git a/spec/policy_finder_spec.rb b/spec/policy_finder_spec.rb
index 76533c7..11eb25a 100644
--- a/spec/policy_finder_spec.rb
+++ b/spec/policy_finder_spec.rb
@@ -2,7 +2,8 @@
 
 require "spec_helper"
 
-describe Pundit::PolicyFinder do
+class Foo; end
+RSpec.describe Pundit::PolicyFinder do
   let(:user) { double }
   let(:post) { Post.new(user) }
   let(:comment) { CommentFourFiveSix.new }
@@ -24,37 +25,99 @@ describe Pundit::PolicyFinder do
   end
 
   describe "#policy" do
-    subject { described_class.new(post) }
+    context "with an instance" do
+      it "returns the associated policy" do
+        object = described_class.new(post)
+
+        expect(object.policy).to eq PostPolicy
+      end
+    end
+
+    context "with an array of symbols" do
+      it "returns the associated namespaced policy" do
+        object = described_class.new(%i[project post])
+
+        expect(object.policy).to eq Project::PostPolicy
+      end
+    end
+
+    context "with an array of a symbol and an instance" do
+      it "returns the associated namespaced policy" do
+        object = described_class.new([:project, post])
 
-    it "returns a policy" do
-      expect(subject.policy).to eq PostPolicy
+        expect(object.policy).to eq Project::PostPolicy
+      end
     end
 
-    context "with a string" do
-      it "returns a policy" do
-        allow(subject).to receive(:find).and_return "PostPolicy"
-        expect(subject.policy).to eq PostPolicy
+    context "with an array of a symbol and a class with a specified policy class" do
+      it "returns the associated namespaced policy" do
+        object = described_class.new([:project, Customer::Post])
+
+        expect(object.policy).to eq Project::PostPolicy
+      end
+    end
+
+    context "with an array of a symbol and a class with a specified model name" do
+      it "returns the associated namespaced policy" do
+        object = described_class.new([:project, CommentsRelation])
+
+        expect(object.policy).to eq Project::CommentPolicy
       end
     end
 
     context "with a class" do
-      it "returns a policy" do
-        allow(subject).to receive(:find).and_return PostPolicy
-        expect(subject.policy).to eq PostPolicy
+      it "returns the associated policy" do
+        object = described_class.new(Post)
+
+        expect(object.policy).to eq PostPolicy
+      end
+    end
+
+    context "with a class which has a specified policy class" do
+      it "returns the associated policy" do
+        object = described_class.new(Customer::Post)
+
+        expect(object.policy).to eq PostPolicy
+      end
+    end
+
+    context "with an instance which has a specified policy class" do
+      it "returns the associated policy" do
+        object = described_class.new(Customer::Post.new(user))
+
+        expect(object.policy).to eq PostPolicy
+      end
+    end
+
+    context "with a class which has a specified model name" do
+      it "returns the associated policy" do
+        object = described_class.new(CommentsRelation)
+
+        expect(object.policy).to eq CommentPolicy
+      end
+    end
+
+    context "with an instance which has a specified policy class" do
+      it "returns the associated policy" do
+        object = described_class.new(CommentsRelation.new)
+
+        expect(object.policy).to eq CommentPolicy
       end
     end
 
     context "with nil" do
-      it "returns nil" do
-        allow(subject).to receive(:find).and_return nil
-        expect(subject.policy).to eq nil
+      it "returns a NilClassPolicy" do
+        object = described_class.new(nil)
+
+        expect(object.policy).to eq NilClassPolicy
       end
     end
 
-    context "with a string that can't be constantized" do
+    context "with a class that doesn't have an associated policy" do
       it "returns nil" do
-        allow(subject).to receive(:find).and_return "FooPolicy"
-        expect(subject.policy).to eq nil
+        object = described_class.new(Foo)
+
+        expect(object.policy).to eq nil
       end
     end
   end
diff --git a/spec/pundit_spec.rb b/spec/pundit_spec.rb
index 632f0c0..91aefc3 100644
--- a/spec/pundit_spec.rb
+++ b/spec/pundit_spec.rb
@@ -2,7 +2,7 @@
 
 require "spec_helper"
 
-describe Pundit do
+RSpec.describe Pundit do
   let(:user) { double }
   let(:post) { Post.new(user) }
   let(:customer_post) { Customer::Post.new(user) }
@@ -10,25 +10,48 @@ describe Pundit do
   let(:comment) { Comment.new }
   let(:comment_four_five_six) { CommentFourFiveSix.new }
   let(:article) { Article.new }
-  let(:controller) { Controller.new(user, "update", {}) }
   let(:artificial_blog) { ArtificialBlog.new }
   let(:article_tag) { ArticleTag.new }
-  let(:comments_relation) { CommentsRelation.new }
-  let(:empty_comments_relation) { CommentsRelation.new(true) }
+  let(:comments_relation) { CommentsRelation.new(empty: false) }
+  let(:empty_comments_relation) { CommentsRelation.new(empty: true) }
   let(:tag_four_five_six) { ProjectOneTwoThree::TagFourFiveSix.new(user) }
   let(:avatar_four_five_six) { ProjectOneTwoThree::AvatarFourFiveSix.new }
   let(:wiki) { Wiki.new }
-  let(:thread) { Thread.new }
 
   describe ".authorize" do
     it "infers the policy and authorizes based on it" do
       expect(Pundit.authorize(user, post, :update?)).to be_truthy
     end
 
+    it "returns the record on successful authorization" do
+      expect(Pundit.authorize(user, post, :update?)).to eq(post)
+    end
+
+    it "returns the record when passed record with namespace " do
+      expect(Pundit.authorize(user, [:project, comment], :update?)).to eq(comment)
+    end
+
+    it "returns the record when passed record with nested namespace " do
+      expect(Pundit.authorize(user, [:project, :admin, comment], :update?)).to eq(comment)
+    end
+
+    it "returns the policy name symbol when passed record with headless policy" do
+      expect(Pundit.authorize(user, :publication, :create?)).to eq(:publication)
+    end
+
+    it "returns the class when passed record not a particular instance" do
+      expect(Pundit.authorize(user, Post, :show?)).to eq(Post)
+    end
+
     it "can be given a different policy class" do
       expect(Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy)).to be_truthy
     end
 
+    it "can be given a different policy class using namespaces" do
+      expect(PublicationPolicy).to receive(:new).with(user, comment).and_call_original
+      expect(Pundit.authorize(user, [:project, comment], :create?, policy_class: PublicationPolicy)).to be_truthy
+    end
+
     it "works with anonymous class policies" do
       expect(Pundit.authorize(user, article_tag, :show?)).to be_truthy
       expect { Pundit.authorize(user, article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
@@ -46,6 +69,18 @@ describe Pundit do
       # rubocop:enable Style/MultilineBlockChain
     end
 
+    it "raises an error with a the record, query and action when the record is namespaced" do
+      # rubocop:disable Style/MultilineBlockChain
+      expect do
+        Pundit.authorize(user, [:project, :admin, comment], :destroy?)
+      end.to raise_error(Pundit::NotAuthorizedError, "not allowed to destroy? this Comment") do |error|
+        expect(error.query).to eq :destroy?
+        expect(error.record).to eq comment
+        expect(error.policy).to eq Pundit.policy(user, [:project, :admin, comment])
+      end
+      # rubocop:enable Style/MultilineBlockChain
+    end
+
     it "raises an error with a invalid policy constructor" do
       expect do
         Pundit.authorize(user, wiki, :update?)
@@ -360,231 +395,26 @@ describe Pundit do
     end
   end
 
-  describe "#verify_authorized" do
-    it "does nothing when authorized" do
-      controller.authorize(post)
-      controller.verify_authorized
-    end
-
-    it "raises an exception when not authorized" do
-      expect { controller.verify_authorized }.to raise_error(Pundit::AuthorizationNotPerformedError)
-    end
-  end
-
-  describe "#verify_policy_scoped" do
-    it "does nothing when policy_scope is used" do
-      controller.policy_scope(Post)
-      controller.verify_policy_scoped
-    end
+  describe ".included" do
+    it "includes Authorization module" do
+      klass = Class.new
 
-    it "raises an exception when policy_scope is not used" do
-      expect { controller.verify_policy_scoped }.to raise_error(Pundit::PolicyScopingNotPerformedError)
-    end
-  end
-
-  describe "#pundit_policy_authorized?" do
-    it "is true when authorized" do
-      controller.authorize(post)
-      expect(controller.pundit_policy_authorized?).to be true
-    end
-
-    it "is false when not authorized" do
-      expect(controller.pundit_policy_authorized?).to be false
-    end
-  end
-
-  describe "#pundit_policy_scoped?" do
-    it "is true when policy_scope is used" do
-      controller.policy_scope(Post)
-      expect(controller.pundit_policy_scoped?).to be true
-    end
-
-    it "is false when policy scope is not used" do
-      expect(controller.pundit_policy_scoped?).to be false
-    end
-  end
-
-  describe "#authorize" do
-    it "infers the policy name and authorizes based on it" do
-      expect(controller.authorize(post)).to be_truthy
-    end
-
-    it "returns the record on successful authorization" do
-      expect(controller.authorize(post)).to be(post)
-    end
-
-    it "can be given a different permission to check" do
-      expect(controller.authorize(post, :show?)).to be_truthy
-      expect { controller.authorize(post, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
-    end
-
-    it "can be given a different policy class" do
-      expect(controller.authorize(post, :create?, policy_class: PublicationPolicy)).to be_truthy
-    end
-
-    it "works with anonymous class policies" do
-      expect(controller.authorize(article_tag, :show?)).to be_truthy
-      expect { controller.authorize(article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
-    end
-
-    it "throws an exception when the permission check fails" do
-      expect { controller.authorize(Post.new) }.to raise_error(Pundit::NotAuthorizedError)
-    end
-
-    it "throws an exception when a policy cannot be found" do
-      expect { controller.authorize(Article) }.to raise_error(Pundit::NotDefinedError)
-    end
-
-    it "caches the policy" do
-      expect(controller.policies[post]).to be_nil
-      controller.authorize(post)
-      expect(controller.policies[post]).not_to be_nil
-    end
-
-    it "raises an error when the given record is nil" do
-      expect { controller.authorize(nil, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
-    end
-
-    it "raises an error with a invalid policy constructor" do
-      expect { controller.authorize(wiki, :destroy?) }.to raise_error(Pundit::InvalidConstructorError)
-    end
-  end
-
-  describe "#skip_authorization" do
-    it "disables authorization verification" do
-      controller.skip_authorization
-      expect { controller.verify_authorized }.not_to raise_error
-    end
-  end
-
-  describe "#skip_policy_scope" do
-    it "disables policy scope verification" do
-      controller.skip_policy_scope
-      expect { controller.verify_policy_scoped }.not_to raise_error
-    end
-  end
-
-  describe "#pundit_user" do
-    it "returns the same thing as current_user" do
-      expect(controller.pundit_user).to eq controller.current_user
-    end
-  end
-
-  describe "#policy" do
-    it "returns an instantiated policy" do
-      policy = controller.policy(post)
-      expect(policy.user).to eq user
-      expect(policy.post).to eq post
-    end
-
-    it "throws an exception if the given policy can't be found" do
-      expect { controller.policy(article) }.to raise_error(Pundit::NotDefinedError)
-    end
-
-    it "raises an error with a invalid policy constructor" do
-      expect { controller.policy(wiki) }.to raise_error(Pundit::InvalidConstructorError)
-    end
-
-    it "allows policy to be injected" do
-      new_policy = OpenStruct.new
-      controller.policies[post] = new_policy
-
-      expect(controller.policy(post)).to eq new_policy
-    end
-  end
-
-  describe "#policy_scope" do
-    it "returns an instantiated policy scope" do
-      expect(controller.policy_scope(Post)).to eq :published
-    end
-
-    it "allows policy scope class to be overriden" do
-      expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).to eq :published
-    end
-
-    it "throws an exception if the given policy can't be found" do
-      expect { controller.policy_scope(Article) }.to raise_error(Pundit::NotDefinedError)
-    end
+      ActiveSupport::Deprecation.silence do
+        klass.include Pundit
+      end
 
-    it "raises an error with a invalid policy scope constructor" do
-      expect { controller.policy_scope(Wiki) }.to raise_error(Pundit::InvalidConstructorError)
+      expect(klass).to include Pundit::Authorization
     end
 
-    it "allows policy_scope to be injected" do
-      new_scope = OpenStruct.new
-      controller.policy_scopes[Post] = new_scope
+    it "warns about deprecation" do
+      klass = Class.new
+      allow(ActiveSupport::Deprecation).to receive(:warn)
 
-      expect(controller.policy_scope(Post)).to eq new_scope
-    end
-  end
-
-  describe "#permitted_attributes" do
-    it "checks policy for permitted attributes" do
-      params = ActionController::Parameters.new(
-        post: {
-          title: "Hello",
-          votes: 5,
-          admin: true
-        }
-      )
-
-      action = "update"
-
-      expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq(
-        "title" => "Hello",
-        "votes" => 5
-      )
-      expect(Controller.new(double, action, params).permitted_attributes(post).to_h).to eq("votes" => 5)
-    end
-
-    it "checks policy for permitted attributes for record of a ActiveModel type" do
-      params = ActionController::Parameters.new(
-        customer_post: {
-          title: "Hello",
-          votes: 5,
-          admin: true
-        }
-      )
-
-      action = "update"
-
-      expect(Controller.new(user, action, params).permitted_attributes(customer_post).to_h).to eq(
-        "title" => "Hello",
-        "votes" => 5
-      )
-      expect(Controller.new(double, action, params).permitted_attributes(customer_post).to_h).to eq(
-        "votes" => 5
-      )
-    end
-  end
+      ActiveSupport::Deprecation.silence do
+        klass.include Pundit
+      end
 
-  describe "#permitted_attributes_for_action" do
-    it "is checked if it is defined in the policy" do
-      params = ActionController::Parameters.new(
-        post: {
-          title: "Hello",
-          body: "blah",
-          votes: 5,
-          admin: true
-        }
-      )
-
-      action = "revise"
-      expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq("body" => "blah")
-    end
-
-    it "can be explicitly set" do
-      params = ActionController::Parameters.new(
-        post: {
-          title: "Hello",
-          body: "blah",
-          votes: 5,
-          admin: true
-        }
-      )
-
-      action = "update"
-      expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah")
+      expect(ActiveSupport::Deprecation).to have_received(:warn).with start_with("'include Pundit' is deprecated")
     end
   end
 
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 3768762..192146f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,5 +1,10 @@
 # frozen_string_literal: true
 
+require "simplecov"
+SimpleCov.start do
+  add_filter "/spec/"
+end
+
 require "pundit"
 require "pundit/rspec"
 
@@ -11,22 +16,6 @@ require "active_support/core_ext"
 require "active_model/naming"
 require "action_controller/metal/strong_parameters"
 
-I18n.enforce_available_locales = false
-
-module PunditSpecHelper
-  extend RSpec::Matchers::DSL
-
-  matcher :be_truthy do
-    match do |actual|
-      actual
-    end
-  end
-end
-
-RSpec.configure do |config|
-  config.include PunditSpecHelper
-end
-
 class PostPolicy < Struct.new(:user, :post)
   class Scope < Struct.new(:user, :scope)
     def resolve
@@ -86,15 +75,12 @@ module Customer
     def self.policy_class
       PostPolicy
     end
-
-    def policy_class
-      self.class.policy_class
-    end
   end
 end
 
 class CommentScope
   attr_reader :original_object
+
   def initialize(original_object)
     @original_object = original_object
   end
@@ -129,7 +115,7 @@ class Comment
 end
 
 class CommentsRelation
-  def initialize(empty = false)
+  def initialize(empty: false)
     @empty = empty
   end
 
@@ -137,7 +123,7 @@ class CommentsRelation
     @empty
   end
 
-  def model_name
+  def self.model_name
     Comment.model_name
   end
 end
@@ -174,6 +160,10 @@ class CriteriaPolicy < Struct.new(:user, :criteria); end
 
 module Project
   class CommentPolicy < Struct.new(:user, :comment)
+    def update?
+      true
+    end
+
     class Scope < Struct.new(:user, :scope)
       def resolve
         scope
@@ -190,6 +180,18 @@ module Project
       end
     end
   end
+
+  module Admin
+    class CommentPolicy < Struct.new(:user, :comment)
+      def update?
+        true
+      end
+
+      def destroy?
+        false
+      end
+    end
+  end
 end
 
 class DenierPolicy < Struct.new(:user, :record)
@@ -199,11 +201,11 @@ class DenierPolicy < Struct.new(:user, :record)
 end
 
 class Controller
-  include Pundit
+  include Pundit::Authorization
   # Mark protected methods public so they may be called in test
-  # rubocop:disable Layout/AccessModifierIndentation, Style/AccessModifierDeclarations
-  public(*Pundit.protected_instance_methods)
-  # rubocop:enable Layout/AccessModifierIndentation, Style/AccessModifierDeclarations
+  # rubocop:disable Style/AccessModifierDeclarations
+  public(*Pundit::Authorization.protected_instance_methods)
+  # rubocop:enable Style/AccessModifierDeclarations
 
   attr_reader :current_user, :action_name, :params
 
@@ -231,6 +233,7 @@ class NilClassPolicy < Struct.new(:user, :record)
 end
 
 class Wiki; end
+
 class WikiPolicy
   class Scope
     # deliberate typo method
@@ -241,6 +244,7 @@ end
 class Thread
   def self.all; end
 end
+
 class ThreadPolicy < Struct.new(:user, :thread)
   class Scope < Struct.new(:user, :scope)
     def resolve

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/config/rubocop-rspec.yml
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/install/USAGE
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/install/install_generator.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/install/templates/application_policy.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/policy/USAGE
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/policy/policy_generator.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/policy/templates/policy.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/rspec/policy_generator.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/rspec/templates/policy_spec.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/test_unit/policy_generator.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/test_unit/templates/policy_test.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/pundit.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/pundit/authorization.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/pundit/policy_finder.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/pundit/rspec.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/pundit/version.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/pundit-2.3.0.gemspec

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/install/USAGE
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/install/install_generator.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/install/templates/application_policy.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/policy/USAGE
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/policy/policy_generator.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/policy/templates/policy.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/rspec/policy_generator.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/rspec/templates/policy_spec.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/test_unit/policy_generator.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/test_unit/templates/policy_test.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/pundit.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/pundit/policy_finder.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/pundit/rspec.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/pundit/version.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/pundit-2.1.0.gemspec

No differences were encountered in the control files

More details

Full run details