Curated library of 28 public AI agent skills for Ruby on Rails development. Organized by category: testing, code-quality, engines, infrastructure, api, and context. Covers code review, architecture, security, testing (RSpec), engines, Hotwire, and TDD automation. Shared Ruby skills (YARD docs, DDD, service objects) have moved to ruby-core-skills. Repository agents remain documented in GitHub but are intentionally excluded from the Tessl tile.
93
95%
Does it follow best practices?
Impact
93%
1.78xAverage score across 28 eval scenarios
Passed
No known issues
Complete code examples for Pundit and CanCanCan implementations.
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
def update?
user.admin? || record.user_id == user.id
end
def destroy?
user.admin?
end
def create?
user.present?
end
end# app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: [:update, :destroy]
def update
authorize @post
if @post.update(post_params)
redirect_to @post, notice: 'Updated successfully'
else
render :edit, status: :unprocessable_entity
end
end
def destroy
authorize @post
@post.destroy
redirect_to posts_path, notice: 'Deleted successfully'
end
private
def set_post
@post = Post.find(params[:id])
end
end# app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.admin?
can :manage, :all
else
can :read, :all
can :update, Post, user_id: user.id
can :destroy, Post, user_id: user.id
can :create, Post
end
end
end# app/controllers/posts_controller.rb
class PostsController < ApplicationController
load_and_authorize_resource
def update
if @post.update(post_params)
redirect_to @post, notice: 'Updated successfully'
else
render :edit, status: :unprocessable_entity
end
end
end# spec/policies/post_policy_spec.rb
require 'rails_helper'
RSpec.describe PostPolicy do
subject { described_class.new(user, post) }
let(:post) { create(:post, user: owner) }
let(:owner) { create(:user) }
context 'as the post owner' do
let(:user) { owner }
it { is_expected.to permit_action(:update) }
it { is_expected.to permit_action(:destroy) }
it { is_expected.to permit_action(:create) }
end
context 'as a different user' do
let(:user) { create(:user) }
it { is_expected.not_to permit_action(:update) }
it { is_expected.not_to permit_action(:destroy) }
it { is_expected.to permit_action(:create) }
end
context 'as an admin' do
let(:user) { create(:user, :admin) }
it { is_expected.to permit_action(:update) }
it { is_expected.to permit_action(:destroy) }
it { is_expected.to permit_action(:create) }
end
context 'as a guest' do
let(:user) { nil }
it { is_expected.not_to permit_action(:update) }
it { is_expected.not_to permit_action(:destroy) }
it { is_expected.not_to permit_action(:create) }
end
end# spec/requests/posts_spec.rb
require 'rails_helper'
RSpec.describe 'PATCH /posts/:id', type: :request do
let(:post) { create(:post, user: owner) }
let(:owner) { create(:user) }
it 'allows the owner to update' do
sign_in owner
patch post_path(post), params: { post: { title: 'New Title' } }
expect(response).to have_http_status(:ok)
expect(post.reload.title).to eq('New Title')
end
it 'denies a guest' do
patch post_path(post), params: { post: { title: 'New Title' } }
expect(response).to have_http_status(:unauthorized)
end
it 'denies a different user' do
sign_in create(:user)
patch post_path(post), params: { post: { title: 'New Title' } }
expect(response).to have_http_status(:forbidden)
end
it 'allows an admin to update any post' do
sign_in create(:user, :admin)
patch post_path(post), params: { post: { title: 'Admin Updated' } }
expect(response).to have_http_status(:ok)
end
endUse this shape in the final implementation report after automated specs pass.
# Rails console — Pundit
user = User.find_by!(email: 'member@example.com')
post = Post.find_by!(slug: 'admin-only')
Pundit.authorize(user, post, :update?)
# raises Pundit::NotAuthorizedError# Rails console — CanCanCan
user = User.find_by!(email: 'member@example.com')
post = Post.find_by!(slug: 'admin-only')
ability = Ability.new(user)
ability.authorize! :update, post
# raises CanCan::AccessDenied# HTTP check
curl -i -X PATCH http://localhost:3000/posts/admin-only \
-H "Authorization: Bearer <token_for_user_without_permission>"
# Expected: 403 Forbidden, or the app's configured denied-access response# spec/support/shared_examples/authorization.rb
RSpec.shared_examples 'requires authentication' do |action|
it "requires authentication for #{action}" do
public_send(action)
expect(response).to redirect_to(new_user_session_path)
end
end
RSpec.shared_examples 'requires authorization' do |action, resource_owner_method|
it "requires authorization for #{action}" do
other_user = create(:user)
resource = create(described_class.controller_name.singularize, user: other_user)
sign_in create(:user)
public_send(action, params: { id: resource.id })
expect(response).to have_http_status(:forbidden)
end
end# spec/controllers/posts_controller_spec.rb
require 'rails_helper'
RSpec.describe PostsController do
it_behaves_like 'requires authentication', :get, :index
it_behaves_like 'requires authorization', :patch, :update
endagents
docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
skills
api
generate-api-collection
implement-graphql
code-quality
apply-code-conventions
apply-stack-conventions
assets
snippets
code-review
refactor-code
review-architecture
security-check
context
load-context
setup-environment
engines
create-engine
create-engine-installer
document-engine
extract-engine
release-engine
review-engine
test-engine
upgrade-engine
infrastructure
implement-background-job
implement-hotwire
optimize-performance
review-migration
seed-database
version-api
testing
plan-tests
test-service
write-tests