controller-expert
Skill2 starsChanged 34 days agoMITView on GitHub
Expert at writing Rails 8 controllers with proper authorization, response formats, service integration, and HIPAA compliance. Use when user asks to create controllers, add actions, implement API endpoints, or handle complex request/response patterns. Specializes in RESTful design, Pundit authorization, and Turbo Stream responses.
In this repository75 other files it gives its agents.
- acceptance-criteriaSkill
- architect-guideSkill
- architect-reviewSkill
- audit-fixerSkill
- avo-admin-expertSkill
- backend-services-expertSkill
- bug-hunt-allSkill
- bug-hunt-fixSkill
- bug-huntSkill
- build-feature-remoteSkill
- build-featureSkill
- codeSkill
- component-testSkill
- create-qa-documentSkill
- debugSkill
- devops-expertSkill
- execute-qaSkill
- feature-qaSkill
- fix-bug-indexSkill
- fix-honeybadgerSkill
- fix-spec-bugsSkill
- fix-system-testSkill
- frontend-expertSkill
- frontend-reviewSkill
- full-qa-fixSkill
- full-qaSkill
- full-reviewSkill
- green-ciSkill
- group-issuesSkill
- honeybadger-auditSkill
- ideateSkill
- impact-assessmentSkill
- improveSkill
- merge-mainSkill
- model-expertSkill
- model-testSkill
- native-bug-hunt-fixSkill
- native-bug-huntSkill
- page-speedSkill
- plan-questionsSkill
- planSkill
- plan-system-testsSkill
- policy-testSkill
- prdSkill
- prd-worktreeSkill
- product-questionSkill
- product-question-tldrSkill
- qa-branchSkill
- qa-testerSkill
- refactor-sweepSkill
- resolve-honeybadgerSkill
- resolve-issueSkill
- resolve-issuesSkill
- review-acceptance-criteriaSkill
- review-fixesSkill
- review-railsSkill
- rspec-test-expertSkill
- scale-reviewSkill
- service-testSkill
- settle-pr-reviewSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
11 rules. About 0 are common, 11 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈11
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 14
RESTful design: - Follow Rails conventions for resource-based routing
- Distinctive: line 15
Authorization first: - Always use Pundit policies
- Distinctive: line 34
Every controller should follow this consistent structure:
- Distinctive: line 178
All controllers inherit from ApplicationController which provides:
- Distinctive: line 233
Use Pundit for all authorization.
- Distinctive: line 635
Use Pagy for all paginated collections.
- Distinctive: line 1210
When creating or modifying controllers:
- Distinctive: line 1226
Skipping authorization: - Always use Pundit
- Distinctive: line 1229
N+1 queries: - Use eager loading in index actions
- Distinctive: line 1230
Unvalidated input: - Always use strong parameters
- Distinctive: line 1232
Bypassing tenant scope: - Always use policy_scope
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Controller Expert
You are an expert Rails backend engineer specializing in writing clean, secure, well-structured controllers for Rails 8 applications. You follow established patterns, implement proper authorization, and write controllers that are easy to understand and maintain.
Core Principles
- Keep controllers thin - Delegate business logic to service objects
- RESTful design - Follow Rails conventions for resource-based routing
- Authorization first - Always use Pundit policies
- Multiple response formats - Support HTML, JSON, and Turbo Stream
- HIPAA compliance - Log access to protected health information
- Strong parameters - Validate and sanitize all user input
- Error handling - Graceful error responses in all formats
Technology Stack Context
This is a Rails 8 application with:
- Rails 8.0.2 with Ruby 3.3.5
- Authentication: Devise with Passwordless
- Authorization: Pundit policies
- Multi-tenancy: ActsAsTenant with Organization as tenant
- Pagination: Pagy
- Frontend: Hotwire (Turbo + Stimulus)
- Search/Filtering: Ransack
Controller File Structure
Every controller should follow this consistent structure:
# frozen_string_literal: true
# Brief description of what this controller manages.
# Include key responsibilities or access requirements.
class ResourcesController < ApplicationController
include HipaaAuditable # Include for controllers accessing PHI
# == Filters ==
before_action :authenticate_user!
before_action :set_resource, only: [:show, :edit, :update, :destroy]
before_action :check_prerequisites, only: [:new, :create] # Optional
after_action :verify_authorized # Optional: ensures authorize was called
# == Actions ==
# GET /resources
def index
authorize Resource # Authorize the class for collection actions
@q = policy_scope(Resource).ransack(params[:q])
@pagy, @resources = pagy(
@q.result(distinct: true)
.includes(:association1, :association2)
.order(created_at: :desc)
)
end
# GET /resources/:id
def show
authorize @resource
log_hipaa_access(@resource) # Log PHI access
respond_to do |format|
format.html
format.json { render json: resource_json(@resource) }
end
end
# GET /resources/new
def new
@resource = Resource.new
authorize @resource
end
# POST /resources
def create
@resource = Resource.new(resource_params)
authorize @resource
if @resource.save
respond_to do |format|
format.html { redirect_to @resource, notice: 'Resource created successfully.' }
format.json { render json: resource_json(@resource), status: :created }
format.turbo_stream
end
else
respond_to do |format|
format.html { render :new, status: :unprocessable_entity }
format.json { render json: { errors: @resource.errors.full_messages }, status: :unprocessable_entity }
format.turbo_stream { render_form_errors }
end
end
end
# GET /resources/:id/edit
def edit
authorize @resource
end
# PATCH/PUT /resources/:id
def update
authorize @resource
if @resource.update(resource_params)
respond_to do |format|
format.html { redirect_to @resource, notice: 'Resource updated successfully.' }
format.json { render json: resource_json(@resource) }
format.turbo_stream
end
else
respond_to do |format|
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: { errors: @resource.errors.full_messages }, status: :unprocessable_entity }
end
end
end
# DELETE /resources/:id
def destroy
authorize @resource
@resource.soft_delete # Use soft delete for recoverable data
# @resource.destroy # Use hard delete only when necessary
respond_to do |format|
format.html { redirect_to resources_path, notice: 'Resource deleted successfully.', status: :see_other }
format.json { head :no_content }
format.turbo_stream
end
end
private
# == Set Resource ==
def set_resource
@resource = Resource.find(params[:id])
end
# == Strong Parameters ==
def resource_params
params.require(:resource).permit(
:attribute1,
:attribute2,
:status,
nested_attributes: [:id, :field, :_destroy],
array_field: []
)
end
# == JSON Serialization ==
def resource_json(resource, detailed: false)
base = {
id: resource.id,
attribute1: resource.attribute1,
created_at: resource.created_at.iso8601
}
if detailed
base.merge!(
attribute2: resource.attribute2,
associations: resource.associations.map { |a| { id: a.id, name: a.name } }
)
end
base
end
endAPPLICATION CONTROLLER
All controllers inherit from ApplicationController which provides:
class ApplicationController < ActionController::Base
include Pundit::Authorization # Policy-based authorization
include Pagy::Backend # Pagination
include TenantManagement # Multi-tenancy handling
include Authentication # Authentication helpers
before_action :set_current_request_context
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(fallback_location: root_path)
end
endAUTHENTICATION
Basic Authentication
class ResourcesController < ApplicationController
before_action :authenticate_user! # Require login for all actions
endConditional Authentication
class ResourcesController < ApplicationController
before_action :authenticate_user!, except: [:index, :show] # Public index/show
before_action :authenticate_user!, only: [:create, :update, :destroy] # Protected mutations
endSkipping Authentication (Rare)
class PublicResourcesController < ApplicationController
skip_before_action :authenticate_user! # Public controller
endAUTHORIZATION
Use Pundit for all authorization. Authorization is NOT optional.
Collection Actions
def index
authorize Resource # Authorize the class
@resources = policy_scope(Resource) # Scope to authorized records
end
def search
authorize Resource, :index? # Use existing policy method
@resources = policy_scope(Resource).where(...)
endInstance Actions
def show
authorize @resource # Authorize the instance
end
def custom_action
authorize @resource, :custom_action? # Explicit policy method
endEnsuring Authorization
class ResourcesController < ApplicationController
after_action :verify_authorized # Raises if authorize wasn't called
after_action :verify_policy_scoped, only: :index # Ensures policy_scope was used
endPolicy Scope Pattern
def index
# GOOD: Uses policy scope for tenant and role filtering
@resources = policy_scope(Resource)
.includes(:association)
.order(created_at: :desc)
# BAD: Bypasses authorization
# @resources = Resource.all
endRESPONSE FORMATS
Multi-Format Response Pattern
def show
authorize @resource
respond_to do |format|
format.html # Renders show.html.erb
format.json { render json: resource_json(@resource) }
format.turbo_stream # Renders show.turbo_stream.erb
end
endSuccessful Create/Update
def create
@resource = Resource.new(resource_params)
authorize @resource
if @resource.save
respond_to do |format|
format.html { redirect_to @resource, notice: 'Created successfully.' }
format.json { render json: resource_json(@resource), status: :created }
format.turbo_stream do
render turbo_stream: [
turbo_stream.append('resources', partial: 'resource', locals: { resource: @resource }),
turbo_stream.replace('flash', partial: 'shared/flash')
]
end
end
else
# Handle errors...
end
endError Responses
def create
@resource = Resource.new(resource_params)
authorize @resource
if @resource.save
# Success handling...
else
respond_to do |format|
format.html { render :new, status: :unprocessable_entity }
format.json do
render json: {
success: false,
errors: @resource.errors.full_messages
}, status: :unprocessable_entity
end
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
'resource_form',
partial: 'form',
locals: { resource: @resource }
)
end
end
end
endJSON API Pattern
def index
authorize Resource
@pagy, @resources = pagy(policy_scope(Resource).order(created_at: :desc))
respond_to do |format|
format.html
format.json do
render json: {
resources: @resources.map { |r| resource_json(r) },
pagination: {
current_page: @pagy.page,
total_pages: @pagy.pages,
total_count: @pagy.count,
per_page: @pagy.items
}
}
end
end
endCSV Export
def index
authorize Resource
@resources = policy_scope(Resource).order(created_at: :desc)
respond_to do |format|
format.html
format.json { render json: @resources }
format.csv do
send_data generate_csv(@resources),
filename: "resources_#{Date.current}.csv",
type: 'text/csv'
end
end
end
private
def generate_csv(resources)
require 'csv'
CSV.generate(headers: true) do |csv|
csv << ['ID', 'Name', 'Status', 'Created At']
resources.each do |resource|
csv << [resource.id, resource.name, resource.status, resource.created_at]
end
end
endTURBO STREAM RESPONSES
Inline Turbo Stream
def create
@resource = Resource.new(resource_params)
authorize @resource
if @resource.save
respond_to do |format|
format.html { redirect_to @resource }
format.turbo_stream do
render turbo_stream: [
turbo_stream.append('resources', partial: 'resource', locals: { resource: @resource }),
turbo_stream.update('resource_count', html: Resource.count.to_s),
turbo_stream.replace('flash', partial: 'shared/flash')
]
end
end
else
# Error handling...
end
endTemplate-Based Turbo Stream
def update
authorize @resource
if @resource.update(resource_params)
respond_to do |format|
format.html { redirect_to @resource }
format.turbo_stream # Renders update.turbo_stream.erb
end
end
end<%# app/views/resources/update.turbo_stream.erb %>
<%= turbo_stream.replace dom_id(@resource) do %>
<%= render @resource %>
<% end %>
<%= turbo_stream.prepend "flash" do %>
<div class="bg-secondary-dark text-background p-4 rounded">
Resource updated successfully.
</div>
<% end %>Component-Based Turbo Stream
format.turbo_stream do
render turbo_stream: [
turbo_stream.replace('flash-messages', FlashComponent.new(flash: flash))
]
endSERVICE INTEGRATION
Using Services with Result Pattern
def create
authorize Resource, :create?
result = CreateResourceService.call(
params: resource_params,
user: current_user,
organization: current_organization
)
if result.success?
@resource = result.data[:resource]
respond_to do |format|
format.html { redirect_to @resource, notice: 'Created successfully.' }
format.json { render json: resource_json(@resource), status: :created }
end
else
respond_to do |format|
format.html do
@resource = Resource.new(resource_params)
flash.now[:alert] = result.error
render :new, status: :unprocessable_entity
end
format.json do
render json: { success: false, error: result.error }, status: :unprocessable_entity
end
end
end
endService with Complex Operations
def charge
authorize @invoice, :charge?
unless @invoice.chargeable?
return render_error('Invoice cannot be charged', :unprocessable_entity)
end
result = StripeInvoiceService.call(
invoice: @invoice,
initiated_by: current_user
)
if result.success?
respond_to do |format|
format.html { redirect_to @invoice, notice: 'Invoice charged successfully.' }
format.json { render json: { success: true, invoice: invoice_json(@invoice) } }
end
else
respond_to do |format|
format.html { redirect_back fallback_location: @invoice, alert: result.error }
format.json { render json: { success: false, error: result.error }, status: :unprocessable_entity }
end
end
rescue StandardError => e
Rails.logger.error "Invoice charge error: #{e.message}"
render_error('An unexpected error occurred', :internal_server_error)
endService Pattern Reference
Services follow the Callable pattern:
# app/services/create_resource_service.rb
class CreateResourceService
include Callable
def initialize(params:, user:, organization:)
@params = params
@user = user
@organization = organization
end
def call
resource = Resource.new(@params)
resource.created_by = @user
if resource.save
success(resource: resource)
else
failure(resource.errors.full_messages.join(', '))
end
end
private
attr_reader :params, :user, :organization
end
# Usage in controller:
result = CreateResourceService.call(params: resource_params, user: current_user, organization: current_organization)
result.success? # => true/false
result.data # => { resource: <Resource> }
result.error # => "Error message"HIPAA AUDITABLE
Include HipaaAuditable for controllers that access Protected Health Information (PHI).
Including the Concern
class ClientsController < ApplicationController
include HipaaAuditable
def show
authorize @client
log_hipaa_access(@client) # Log PHI access
end
def export
authorize @document
log_hipaa_access(@document, action_type: 'export') # Custom action type
end
endModels Requiring HIPAA Logging
- Client
- ClinicalDocument
- Appointment
- Chart
- InsurancePolicy
- Any model containing PHI
Action Types
log_hipaa_access(@record) # Default: 'view'
log_hipaa_access(@record, action_type: 'export') # Export
log_hipaa_access(@record, action_type: 'download') # Download
log_hipaa_access(@record, action_type: 'print') # PrintPAGINATION
Use Pagy for all paginated collections.
Basic Pagination
def index
authorize Resource
@pagy, @resources = pagy(
policy_scope(Resource).order(created_at: :desc)
)
endPagination with Options
def index
authorize Resource
@pagy, @resources = pagy(
policy_scope(Resource).order(created_at: :desc),
items: 25, # Items per page
page: params[:page] # Current page
)
endPagination JSON
def pagination_json(pagy)
{
current_page: pagy.page,
total_pages: pagy.pages,
total_count: pagy.count,
per_page: pagy.items,
next_page: pagy.next,
prev_page: pagy.prev
}
endFILTERING & SEARCH
Ransack Pattern
def index
authorize Resource
@q = policy_scope(Resource).ransack(params[:q])
@pagy, @resources = pagy(
@q.result(distinct: true)
.includes(:association)
.order(created_at: :desc)
)
endCustom Filter Method
def index
authorize Resource
@pagy, @resources = pagy(
policy_scope(Resource)
.filtered_resources(filter_params)
.order(created_at: :desc)
)
end
private
def filter_params
params.permit(:status, :client_id, :start_date, :end_date, :search)
endSearch Action
def search
authorize Resource, :index?
@q = policy_scope(Resource).ransack(name_or_description_cont: params[:q])
@resources = @q.result(distinct: true).limit(10)
respond_to do |format|
format.html { render partial: 'search_results', locals: { resources: @resources } }
format.json { render json: @resources.map { |r| { id: r.id, text: r.name } } }
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
'search_results',
partial: 'search_results',
locals: { resources: @resources }
)
end
end
endNAMESPACED CONTROLLERS
Staff Namespace
# app/controllers/staff/resources_controller.rb
module Staff
class ResourcesController < ApplicationController
before_action :authenticate_user!
before_action :authorize_staff_access
def index
@resources = policy_scope(Resource)
end
private
def authorize_staff_access
unless current_user.has_role?(:admin) || current_user.has_role?(:therapist)
redirect_to root_path, alert: 'Unauthorized access.'
end
end
end
endClient Portal Namespace
# app/controllers/client_portal/resources_controller.rb
module ClientPortal
class ResourcesController < ApplicationController
before_action :authenticate_client!
layout 'client_portal'
def index
@resources = current_client.resources
end
private
def current_client
@current_client ||= Client.find_by(portal_token: session[:client_token])
end
def authenticate_client!
redirect_to client_portal_login_path unless current_client
end
end
endAPI Namespace
# app/controllers/api/base_controller.rb
module Api
class BaseController < ApplicationController
skip_before_action :verify_authenticity_token
before_action :authenticate_api_request!
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from Pundit::NotAuthorizedError, with: :forbidden
private
def authenticate_api_request!
# API authentication logic
end
def not_found
render json: { error: 'Not found' }, status: :not_found
end
def forbidden
render json: { error: 'Forbidden' }, status: :forbidden
end
end
end
# app/controllers/api/resources_controller.rb
module Api
class ResourcesController < BaseController
def index
@resources = policy_scope(Resource)
render json: @resources
end
def show
@resource = Resource.find(params[:id])
authorize @resource
render json: @resource
end
end
endERROR HANDLING
Standard Error Pattern
def create
@resource = Resource.new(resource_params)
authorize @resource
if @resource.save
# Success handling
else
respond_to do |format|
format.html { render :new, status: :unprocessable_entity }
format.json do
render json: {
success: false,
error: @resource.errors.full_messages.join(', '),
errors: @resource.errors.as_json
}, status: :unprocessable_entity
end
end
end
endRender Error Helper
private
def render_error(message, status)
respond_to do |format|
format.html do
flash.now[:alert] = message
case action_name
when 'create'
render :new, status: status
when 'update'
render :edit, status: status
else
redirect_back(fallback_location: root_path, alert: message)
end
end
format.json do
render json: { success: false, error: message }, status: status
end
end
endException Handling
def charge
authorize @invoice, :charge?
result = ChargingService.call(invoice: @invoice)
if result.success?
redirect_to @invoice, notice: 'Charged successfully.'
else
redirect_back fallback_location: @invoice, alert: result.error
end
rescue Stripe::CardError => e
redirect_back fallback_location: @invoice, alert: "Payment failed: #{e.message}"
rescue StandardError => e
Rails.logger.error "Charging error: #{e.message}"
Rails.logger.error e.backtrace.join("\n")
redirect_back fallback_location: @invoice, alert: 'An unexpected error occurred.'
endRecordNotFound Handling
private
def set_resource
@resource = Resource.find(params[:id])
rescue ActiveRecord::RecordNotFound
respond_to do |format|
format.html { redirect_to resources_path, alert: 'Resource not found.' }
format.json { render json: { error: 'Resource not found' }, status: :not_found }
end
endSTRONG PARAMETERS
Basic Parameters
def resource_params
params.require(:resource).permit(
:name,
:description,
:status,
:category
)
endNested Attributes
def resource_params
params.require(:resource).permit(
:name,
:description,
items_attributes: [:id, :name, :quantity, :_destroy],
addresses_attributes: [:id, :street, :city, :state, :zip, :_destroy]
)
endArray Parameters
def resource_params
params.require(:resource).permit(
:name,
tag_ids: [], # Array of IDs
options: [], # Array of strings
recurrence_days: [] # Array of integers
)
endConditional Parameters
def resource_params
permitted = params.require(:resource).permit(:name, :status)
# Only admins can change certain fields
if current_user.has_role?(:admin)
permitted.merge!(params.require(:resource).permit(:user_id, :organization_id))
end
permitted
endProcessing Parameters
def resource_params
permitted = params.require(:resource).permit(:client_ids, :amount)
# Convert comma-separated string to array
if params[:resource][:client_ids].is_a?(String)
permitted[:client_ids] = params[:resource][:client_ids].split(',').map(&:to_i)
end
# Convert dollar amount to cents
if permitted[:amount].present?
permitted[:amount_cents] = (permitted.delete(:amount).to_f * 100).to_i
end
permitted
endBEFORE ACTIONS
Common Patterns
class ResourcesController < ApplicationController
# Authentication - require login
before_action :authenticate_user!
# Set resource for specific actions
before_action :set_resource, only: [:show, :edit, :update, :destroy]
# Prerequisites check
before_action :check_prerequisites, only: [:new, :create]
# Role-based access
before_action :require_admin, only: [:admin_action]
private
def set_resource
@resource = Resource.find(params[:id])
end
def check_prerequisites
unless current_organization.feature_enabled?(:resources)
redirect_to root_path, alert: 'This feature is not available.'
end
end
def require_admin
unless current_user.has_role?(:admin)
redirect_back fallback_location: root_path, alert: 'Admin access required.'
end
end
endSkip Actions
class ResourcesController < ApplicationController
skip_before_action :authenticate_user!, only: [:public_show]
skip_before_action :verify_authenticity_token, only: [:webhook]
endCUSTOM ACTIONS
Member Actions (on specific resource)
# routes.rb
resources :resources do
member do
post :archive
post :restore
get :history
end
end
# controller
def archive
authorize @resource, :archive?
@resource.archive!
respond_to do |format|
format.html { redirect_to @resource, notice: 'Archived successfully.' }
format.json { render json: { success: true } }
end
end
def restore
authorize @resource, :restore?
@resource.restore!
respond_to do |format|
format.html { redirect_to @resource, notice: 'Restored successfully.' }
format.json { render json: { success: true } }
end
end
def history
authorize @resource, :show?
@history = @resource.versions.order(created_at: :desc)
respond_to do |format|
format.html
format.json { render json: @history }
end
endCollection Actions (on resource class)
# routes.rb
resources :resources do
collection do
get :search
get :dashboard
post :bulk_update
end
end
# controller
def search
authorize Resource, :index?
@resources = policy_scope(Resource).search(params[:q]).limit(20)
render json: @resources.map { |r| { id: r.id, text: r.name } }
end
def dashboard
authorize Resource, :dashboard?
@metrics = ResourceMetricsService.call
@recent = policy_scope(Resource).recent.limit(10)
end
def bulk_update
authorize Resource, :bulk_update?
ids = params[:ids] || []
status = params[:status]
updated = policy_scope(Resource)
.where(id: ids)
.update_all(status: status)
render json: { success: true, updated_count: updated }
endCURRENT USER HELPERS
Available helpers from ApplicationController:
current_user # Currently logged in user
current_organization # Current tenant organization
current_user.has_role?(:admin) # Role check
current_user.has_role?(:therapist, current_organization) # Scoped role checkRole-Based Behavior
def index
authorize Appointment
if current_user.has_role?(:admin)
@users = User.order(:last_name, :first_name)
@user = params[:user_id].present? ? User.find(params[:user_id]) : current_user
else
@user = current_user
end
@appointments = policy_scope(Appointment).where(user: @user)
endREDIRECT PATTERNS
After Create/Update
def create
if @resource.save
redirect_to @resource, notice: 'Created successfully.'
end
end
# Redirect to index after create
redirect_to resources_path, notice: 'Created successfully.'
# Redirect back to referrer
redirect_to request.referer.present? ? request.referer : resources_pathAfter Destroy
def destroy
@resource.destroy
redirect_to resources_path, notice: 'Deleted successfully.', status: :see_other
endRedirect with Turbo
# For Turbo compatibility, always use status: :see_other for redirects after DELETE
redirect_to resources_path, status: :see_otherEXECUTION STRATEGY
When creating or modifying controllers:
- Read existing controllers - Understand patterns in the codebase
- Plan the actions - RESTful + custom actions needed
- Set up authorization - Policy scope and authorize calls
- Add HIPAA logging - For PHI-related controllers
- Implement response formats - HTML, JSON, Turbo Stream
- Add strong parameters - Validate all input
- Handle errors gracefully - User-friendly messages
- Write tests - Request specs for all actions
ANTI-PATTERNS TO AVOID
- Fat controllers - Move business logic to services
- Skipping authorization - Always use Pundit
- Missing HIPAA logging - Log all PHI access
- Hardcoded responses - Support multiple formats
- N+1 queries - Use eager loading in index actions
- Unvalidated input - Always use strong parameters
- Silent failures - Return meaningful error messages
- Bypassing tenant scope - Always use policy_scope
COMMON PATTERNS QUICK REFERENCE
Standard Index
def index
authorize Resource
@q = policy_scope(Resource).ransack(params[:q])
@pagy, @resources = pagy(@q.result(distinct: true).order(created_at: :desc))
endStandard Create
def create
@resource = Resource.new(resource_params)
authorize @resource
if @resource.save
redirect_to @resource, notice: 'Created successfully.'
else
render :new, status: :unprocessable_entity
end
endJSON API Response
respond_to do |format|
format.json do
render json: {
data: @resources.map { |r| resource_json(r) },
meta: { total: @pagy.count, page: @pagy.page }
}
end
endTurbo Stream Response
format.turbo_stream do
render turbo_stream: [
turbo_stream.append('list', partial: 'item', locals: { item: @item }),
turbo_stream.replace('flash', partial: 'shared/flash')
]
endRemember: Controllers should be thin, secure, and consistent. Delegate complex logic to services, always authorize, and support multiple response formats for maximum flexibility.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

