Implements policy-based authorization with Pundit for resource access control. Use when adding authorization rules, checking permissions, restricting actions, role-based access, or when user mentions Pundit, policies, authorization, or permissions.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Implements policy-based authorization with Pundit for resource access control. Use when adding authorization rules, checking permissions, restricting actions, role-based access, or when user mentions Pundit, policies, authorization, or permissions.
Authorization Progress:
- [ ] Step 1: Write policy test (RED)
- [ ] Step 2: Run test (fails)
- [ ] Step 3: Implement policy
- [ ] Step 4: Run test (GREEN)
- [ ] Step 5: Add policy to controller
- [ ] Step 6: Test integration
Base Policy
# app/policies/application_policy.rbclassApplicationPolicyattr_reader:user, :recorddefinitialize(user, record)
@user = user
@record = record
create?
update?
()
= user
= scope
,
,
end
def
index?
false
end
def
show?
false
end
def
create?
false
end
def
new?
end
def
update?
false
end
def
edit?
end
def
destroy?
false
end
class
Scope
def
initialize
user, scope
@user
@scope
end
def
resolve
raise
NotImplementedError
"Define #resolve in #{self.class}"
end
private
attr_reader
:user
:scope
end
end
Policy Testing (Minitest)
Basic Policy Test
# test/policies/event_policy_test.rbrequire"test_helper"classEventPolicyTest < ActiveSupport::TestCase
setup do@account = accounts(:one)
@user = users(:one) # belongs to @account@other_user = users(:other_account) # different account@event = events(:one) # belongs to @accountend# -- index --
test "index? permits any authenticated user"do
policy = EventPolicy.new(@user, Event)
assert policy.index?
end# -- show --
test "show? permits user from same account"do
policy = EventPolicy.new(@user, @event)
assert policy.show?
end
test "show? denies user from different account"do
policy = EventPolicy.new(@other_user, @event)
assert_not policy.show?
end# -- create --
test "create? permits user from same account"do
new_event = Event.new(account:@account)
policy = EventPolicy.new(@user, new_event)
assert policy.create?
end# -- update --
test "update? permits user from same account"do
policy = EventPolicy.new(@user, @event)
assert policy.update?
end
test "update? denies user from different account"do
policy = EventPolicy.new(@other_user, @event)
assert_not policy.update?
end# -- destroy --
test "destroy? permits user from same account"do
policy = EventPolicy.new(@user, @event)
assert policy.destroy?
end
test "destroy? denies user from different account"do
policy = EventPolicy.new(@other_user, @event)
assert_not policy.destroy?
end# -- Scope --
test "Scope returns events for user account only"do
scope = EventPolicy::Scope.new(@user, Event).resolve
scope.each do |event|
assert_equal @user.account_id, event.account_id
endend
test "Scope excludes other account events"do
other_event = events(:other_account)
scope = EventPolicy::Scope.new(@user, Event).resolve
assert_not_includes scope, other_event
endend
Role-Based Policy Test
# test/policies/event_policy_test.rb (role-based extension)classEventPolicyRoleTest < ActiveSupport::TestCase
setup do@admin = users(:admin)
@member = users(:one)
@event = events(:one)
end
test "destroy? permits admin"do
policy = EventPolicy.new(@admin, @event)
assert policy.destroy?
end
test "publish? permits owner for draft events"do@event.update(status::draft)
policy = EventPolicy.new(@member, @event)
assert policy.publish?
end
test "publish? denies for non-draft events"do@event.update(status::published)
policy = EventPolicy.new(@member, @event)
assert_not policy.publish?
endend
# test/controllers/events_controller_test.rbrequire"test_helper"classEventsControllerTest < ActionDispatch::IntegrationTest
setup do@user = users(:one)
@other_user = users(:other_account)
@event = events(:one) # belongs to @user's account@other_event = events(:other_account)
sign_in @userend
test "allows access to own events"do
get event_path(@event)
assert_response :successend
test "denies access to other account events"do
get event_path(@other_event)
assert_redirected_to root_path
end
test "allows deletion of own events"do
assert_difference("Event.count", -1) do
delete event_path(@event)
end
assert_redirected_to events_path
end
test "denies deletion of other account events"do
assert_no_difference("Event.count") do
delete event_path(@other_event)
end
assert_redirected_to root_path
endend
View Integration
<%# app/views/events/show.html.erb %>
<h1><%= @event.name %></h1>
<% if policy(@event).edit? %>
<%= link_to t("common.edit"), edit_event_path(@event) %>
<% end %>
<% if policy(@event).destroy? %>
<%= button_to t("common.delete"), @event, method: :delete,
data: { confirm: t("common.confirm_delete") } %>
<% end %>