Use when building or refactoring Rails controllers — structuring actions, defining routes, whitelisting parameters, or responding in multiple formats. Also applies when sharing logic across controllers with concerns, handling errors consistently, or adding Turbo Stream responses alongside HTML. Covers Rails 7+ patterns including params.expect.
Generates Rails controller code following RESTful patterns with strong parameters, filters, responses, and error handling for Rails 7+.
npx claudepluginhub chaserx/cpcThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/routing-patterns.mdGuidance for implementing Rails controllers, routing, request handling, and response patterns for Rails 7+.
| Need | Solution |
|---|---|
| Load resource | before_action :set_resource |
| Require login | before_action :authenticate_user! |
| Whitelist params | params.require(:model).permit(...) |
| Params (7.2+) | params.expect(model: [...]) |
| Multiple formats | respond_to block |
| Handle 404 | rescue_from ActiveRecord::RecordNotFound |
| Custom route action | member or collection route |
| Shared controller logic | Controller concern |
A well-structured controller follows RESTful conventions with callbacks for shared setup, authorization checks, and a private method for strong parameters. Keep actions focused on the request/response cycle.
class ArticlesController < ApplicationController
before_action :authenticate_user!
before_action :set_article, only: [:show, :edit, :update, :destroy]
before_action :authorize_article, only: [:edit, :update, :destroy]
# GET /articles
def index
@articles = Article.published.recent.page(params[:page])
end
# GET /articles/:id
def show; end
# GET /articles/new
def new
@article = current_user.articles.build
end
# POST /articles
def create
@article = current_user.articles.build(article_params)
if @article.save
redirect_to @article, notice: 'Article created successfully.'
else
render :new, status: :unprocessable_entity
end
end
# GET /articles/:id/edit
def edit; end
# PATCH/PUT /articles/:id
def update
if @article.update(article_params)
redirect_to @article, notice: 'Article updated successfully.'
else
render :edit, status: :unprocessable_entity
end
end
# DELETE /articles/:id
def destroy
@article.destroy
redirect_to articles_url, notice: 'Article deleted.'
end
private
def set_article
@article = Article.find(params[:id])
end
def authorize_article
redirect_to articles_url, alert: 'Not authorized.' unless @article.user == current_user
end
def article_params
params.require(:article).permit(:title, :body, :published)
end
end
Strong parameters prevent mass-assignment vulnerabilities by whitelisting permitted attributes. Always define a private *_params method rather than permitting inline.
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
Permit nested attributes for accepts_nested_attributes_for associations. Include :id and :_destroy to support editing and removing nested records.
def order_params
params.require(:order).permit(
:customer_name,
:shipping_address,
line_items_attributes: [:id, :product_id, :quantity, :_destroy]
)
end
The params.expect method provides stricter parameter filtering that raises on unexpected structures, preventing parameter injection attacks.
def user_params
params.expect(user: [:name, :email, :password])
end
def order_params
params.expect(order: [:customer_name, line_items: [[:product_id, :quantity]]])
end
Conditionally expand permitted attributes based on user roles or context.
def article_params
permitted = [:title, :body]
permitted << :featured if current_user.admin?
params.require(:article).permit(permitted)
end
Filters run code before, after, or around controller actions. Use before_action for authentication, resource loading, and authorization. Scope filters with only, except, or if/unless to keep them targeted.
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :set_locale
before_action :set_time_zone
private
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
def set_time_zone
Time.zone = current_user&.time_zone || 'UTC'
end
end
Restrict filters to specific actions or conditions to avoid unnecessary processing.
class ArticlesController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_article, only: [:show, :edit, :update, :destroy]
before_action :require_admin, if: :admin_action?
private
def admin_action?
action_name.in?(%w[feature unfeature])
end
end
Override inherited filters in subclasses. Use sparingly — skipping authentication or CSRF protection requires careful consideration. See the rails-security skill for security implications.
class Api::BaseController < ApplicationController
skip_before_action :verify_authenticity_token # For API controllers
end
class PublicPagesController < ApplicationController
skip_before_action :authenticate_user!
end
Use respond_to to serve different formats from a single action. Rails selects the block matching the request's Accept header or URL format extension.
def show
@article = Article.find(params[:id])
respond_to do |format|
format.html
format.json { render json: @article }
format.xml { render xml: @article }
format.pdf { send_article_pdf(@article) }
end
end
Return Turbo Stream responses for in-place page updates without full reloads. Always provide an HTML fallback for non-Turbo requests. For comprehensive Turbo Stream patterns including broadcasts and frame targeting, see the hotwire-patterns skill.
def create
@comment = @article.comments.build(comment_params)
respond_to do |format|
if @comment.save
format.turbo_stream
format.html { redirect_to @article, notice: 'Comment added.' }
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
For dedicated API endpoints, render JSON directly. For full API controller patterns including serialization, versioning, and authentication, see the rails-api-pro agent.
def index
@users = User.all
render json: @users
end
def create
@user = User.new(user_params)
if @user.save
render json: @user, status: :created, location: @user
else
render json: @user.errors, status: :unprocessable_entity
end
end
Stream large responses (CSV exports, reports) to avoid buffering the entire response in memory. Particularly useful for exports that iterate over large record sets.
def export
headers['Content-Type'] = 'text/csv'
headers['Content-Disposition'] = 'attachment; filename="users.csv"'
response.status = 200
self.response_body = Enumerator.new do |yielder|
yielder << "name,email\n"
User.find_each do |user|
yielder << "#{user.name},#{user.email}\n"
end
end
end
Define RESTful routes with resources and scope them with namespaces, nesting, and constraints. For detailed routing patterns including nested resources, namespaces, constraints, and route concerns, see references/routing-patterns.md.
Rails.application.routes.draw do
resources :articles do
resources :comments, only: [:create, :destroy]
member do
post :publish
end
collection do
get :search
end
end
namespace :admin do
resources :users
end
end
Use rescue_from in ApplicationController to handle exceptions consistently across the application. Map exception classes to handler methods that render appropriate responses for both HTML and JSON formats. See the rails-security skill for authorization error handling patterns.
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActionController::ParameterMissing, with: :bad_request
rescue_from Pundit::NotAuthorizedError, with: :forbidden
private
def not_found
respond_to do |format|
format.html { render 'errors/not_found', status: :not_found }
format.json { render json: { error: 'Not found' }, status: :not_found }
end
end
def bad_request(exception)
render json: { error: exception.message }, status: :bad_request
end
def forbidden
redirect_to root_path, alert: 'Access denied.'
end
end
Flash messages persist across a single redirect. Use flash.now when rendering (not redirecting) to avoid the message leaking into the next request.
def create
@user = User.new(user_params)
if @user.save
redirect_to @user, notice: 'User created successfully.'
else
flash.now[:alert] = 'Please fix the errors below.'
render :new, status: :unprocessable_entity
end
end
# Types: :notice, :alert, or custom like :success, :error
redirect_to users_path, flash: { success: 'Welcome!' }
Extract shared behavior into concerns when multiple controllers need the same functionality. Define a clear interface with abstract methods or configuration options.
# app/controllers/concerns/searchable.rb
module Searchable
extend ActiveSupport::Concern
def search
@results = model_class.search(params[:q]).page(params[:page])
render 'shared/search_results'
end
private
def model_class
raise NotImplementedError
end
end
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
include Searchable
private
def model_class
Article
end
end
Controllers should only handle the request/response cycle — parsing params, calling domain logic, and choosing a response. Move complex logic out of controllers into dedicated objects:
For detailed patterns and implementation examples, see the service-patterns skill.
# Delegate to a service object instead of inlining business logic
def create
result = CreateOrderService.new(current_user, order_params).call
if result.success?
redirect_to result.order, notice: 'Order placed!'
else
@order = result.order
flash.now[:alert] = result.error
render :new, status: :unprocessable_entity
end
end
service-patterns — Service objects, form objects, query objects, and interactorshotwire-patterns — Turbo Frames, Turbo Streams, and Stimulus controllersrails-security — Authentication, authorization, CSRF, and security best practicesrails-testing — Controller and request spec patternsreferences/routing-patterns.md — Detailed routing patterns including nested resources, namespaces, constraints, and route concernsActivates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.