Set up Pundit RSpec testing DSL
mainPundit provides a mini-DSL for writing expressive policy tests in RSpec. To use it, require pundit/rspec in your spec_helper.rb.
require "pundit/rspec"repository·main·Indexed 27 days ago
https://github.com/varvet/punditA lightweight authorization library for Ruby that uses plain Ruby classes (Policies) to manage permissions. It provides helpers for controllers and views to check if users are allowed to perform specific actions on objects, supports policy scopes for filtering collections, and includes a dedicated RSpec testing DSL.
Pundit provides a mini-DSL for writing expressive policy tests in RSpec. To use it, require pundit/rspec in your spec_helper.rb.
require "pundit/rspec"Pundit caches the user context. When switching users in a session, you must call pundit_reset! to ensure the new user's permissions are applied correctly.
class ApplicationController
include Pundit::Authorization
def switch_user_to(user)
terminate_session if authenticated?
start_new_session_for user
pundit_reset!
end
endauthorize or policy_scope helpers.Add pundit to your Gemfile using bundle add pundit. To integrate it into a Rails application, include Pundit::Authorization in your ApplicationController.
You can also run the Pundit generator to create an ApplicationPolicy with default settings.
bundle add punditclass ApplicationController < ActionController::Base
include Pundit::Authorization
endrails g pundit:installIf you need to authorize something that doesn't have a corresponding model class (like a Dashboard), use a symbol. The policy must still accept two arguments in initialize, where the second argument will be the symbol passed to authorize.
# app/policies/dashboard_policy.rb
class DashboardPolicy
attr_reader :user
def initialize(user, _record)
@user = user
end
def show?
user.admin?
end
end
# In controllers
def show
authorize :dashboard, :show?
end
# In views
<% if policy(:dashboard).show? %>
<%= link_to 'Dashboard', dashboard_path %>
<% end %>Use the Rails generator to create a new policy class for a given model.
rails g pundit:policy postIn closed systems where only logged-in users can perform actions, you can prevent nil user errors by raising a Pundit::NotAuthorizedError in your ApplicationPolicy constructor and the Scope constructor.
class ApplicationPolicy
def initialize(user, record)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
@user = user
@record = record
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
@user = user
@scope = scope
end
end
endPundit uses plain Ruby classes to define authorization logic. By convention, these classes are placed in app/policies and named after the model they authorize, suffixed with Policy (e.g., PostPolicy for a Post model).
Each policy class typically:
initialize: a user (retrieved via current_user) and a record (the model object being authorized).? (e.g., update?) that return a boolean. If inheriting from the generated ApplicationPolicy, the model object is referred to as record.class PostPolicy < ApplicationPolicy
def update?
user.admin? or not record.published?
end
endTo ensure you don't forget to call authorize or policy_scope, use Pundit's verification methods in an after_action hook in your ApplicationController. These are development aids and do not provide security themselves.
verify_authorized: Raises an error if authorize was not called during the action.verify_policy_scoped: Raises an error if policy_scope was not called.To bypass these checks for specific actions or conditions, use skip_authorization or skip_policy_scope.
class ApplicationController < ActionController::Base
include Pundit::Authorization
after_action :verify_authorized
end
# To bypass verification conditionally:
def show
record = Record.find_by(attribute: "value")
if record.present?
authorize record
else
skip_authorization
end
endTo prevent rubocop-rspec from failing when it encounters the Pundit permissions construct, ensure you are using rubocop-rspec 2.0 or newer and inherit the Pundit configuration in your .rubocop.yml.
inherit_gem:
pundit: config/rubocop-rspec.ymlYou can use the query, record, and policy properties of Pundit::NotAuthorizedError to generate localized error messages via I18n.
# In ApplicationController
def user_not_authorized(exception)
policy_name = exception.policy.class.to_s.underscore
flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
redirect_back_or_to(root_path)
end
end
# In config/locales/en.yml
en:
pundit:
default: 'You cannot perform this action.'
post_policy:
update?: 'You cannot edit this post!'
create?: 'You cannot create posts!'Pundit raises Pundit::NotAuthorizedError when authorization fails. You can rescue this in your ApplicationController to provide custom feedback or redirects.
To globally handle these as 403 Forbidden errors, add this to application.rb:
config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbiddenclass ApplicationController < ActionController::Base
include Pundit::Authorization
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:alert] = "You are not authorized to perform this action."
redirect_back_or_to(root_path)
end
end