From outlaw-skills
Review and update existing Rails controllers and generate new controllers following professional patterns and best practices. Covers RESTful conventions, authorization patterns, proper error handling, and maintainable code organization.
How this skill is triggered — by the user, by Claude, or both
Slash command
/outlaw-skills:controller-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> [!IMPORTANT] Controllers should nearly always look just like the examples below. Deviations should be rare and justified.
[!IMPORTANT] Controllers should nearly always look just like the examples below. Deviations should be rare and justified. Controllers should be skimmable. The patterns below are designed with that in mind.
class ResourcesController < ApplicationController
before_action :set_resource, except: %i[index new create]
def index
@resources = policy_scope(Resource)
end
def new
@resource = authorize Resource.new
end
def create
@resource = authorize Resource.new(resource_params)
if @resource.save
redirect_to @resource, notice: 'Successfully Created Resource'
else
render :new, status: :unprocessable_content
end
end
def update
if @resource.update(resource_params)
redirect_to @resource, notice: 'Successfully Updated Resource'
else
render :edit, status: :unprocessable_content
end
end
def destroy
@resource.destroy
redirect_to resources_url, notice: 'Successfully Deleted Resource'
end
private
def set_resource
@resource = authorize Resource.find(params[:id])
end
def resource_params
params.expect(resource: %i[attr1 attr2])
end
end
class ResourcesController < ApplicationController
# NOTICE: Parent/Child resource setters use inverse conditions.
# Only one resource setter should run per action and they
# will almost always split on INDEX/NEW/CREATE
before_action :set_parent_resource, only: %i[index new create]
before_action :set_resource, except: %i[index new create]
def index
@resources = policy_scope(@parent_resource.resources)
end
def new
@resource = authorize @parent_resource.resources.new
end
def create
@resource = authorize @parent_resource.resources.new(resource_params)
if @resource.save
redirect_to @resource, notice: 'Successfully Created Resource'
else
render :new, status: :unprocessable_content
end
end
def update
if @resource.update(resource_params)
redirect_to @resource, notice: 'Successfully Updated Resource'
else
render :edit, status: :unprocessable_content
end
end
def destroy
@resource.destroy
redirect_to resources_url, notice: 'Successfully Deleted Resource'
end
private
# NOTE: Never authorize the parent resource.
def set_parent_resource
@parent_resource = ParentResource.find(params[:parent_resource_id])
end
# NOTE: Always authorize the child resource in the set method.
def set_resource
@resource = authorize Resource.find(params[:id])
end
def resource_params
params.expect(resource: %i[attr1 attr2])
end
end
Rules:
policy_scope() for collections (index)authorize ClassName.new() for new records (new, create)authorize in set_* methods for existing recordsdef edit; end is unnecessary and adds noise.notice or alert flash message inline.:unprocessable_content (422) - This is a Requirement for Turbo to work properly.save & update with conditionals, not save! or update! with exception handling. Exceptions are for truly exceptional circumstances, not failed validations.Pattern: Controllers should generally have either one or two before_action "set_*" hooks.
# Resource loading (most common)
before_action :set_product, except: %i[index new create]
# Parent resource loading (The controller should be named after the child resource, not the parent)
# Conditions are precisely inverted, so deviations are easily noted when skimming. That's a crucial point for hooks, generally!
before_action :set_company, only: %i[index new create]
before_action :set_employee, except: %i[index new create]
Pattern: Define permitted attributes in a private <resource-name>_params method.
def resource_params
params.expect(
resource: [
:name,
:description,
setting_attributes: %i[id name _destroy], # singular for has_one or some kind of aggregate value object.
addresses_attributes: [%i[id street city state zip _destroy]], # plural for has_many, note the extra array for multiple nested records.
]
)
end
Rules:
params.expect(model: [...])accepts_nested_attributes_for or <value-object-method-name>_attributes= definitions on the model.id & _destroy for updates and deletions.Pattern: Consistent, user-friendly messaging.
# Success (notice:)
redirect_to @product, notice: 'Successfully Created Product'
redirect_to @product, notice: 'Successfully Updated Product'
redirect_to products_url, notice: 'Successfully Deleted Product'
# Errors (alert:)
redirect_to products_path, alert: 'Must be pending to submit.'
redirect_to products_path, alert: 'Cannot delete active product.'
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub outlawandy/skills --plugin outlaw-skills