rails-controllers
Rails Controllers
Quick Reference
| Pattern | Example |
|---|---|
| Generate | rails g controller Posts index show |
| Route | resources :posts |
| Action | def show; @post = Post.find(params[:id]); end |
| Render | render :edit |
| Redirect | redirect_to posts_path |
| Filter | before_action :authenticate_user! |
| Strong Params | params.require(:post).permit(:title, :body) |
Controller Structure
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
# GET /posts
def index
@posts = Post.all.order(created_at: :desc)
end
# GET /posts/:id
def show
# @post set by before_action
end
# GET /posts/new
def new
@post = Post.new
end
# POST /posts
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post, notice: 'Post created successfully.'
else
render :new, status: :unprocessable_entity
end
end
# GET /posts/:id/edit
def edit
# @post set by before_action
end
# PATCH/PUT /posts/:id
def update
if @post.update(post_params)
redirect_to @post, notice: 'Post updated successfully.'
else
render :edit, status: :unprocessable_entity
end
end
# DELETE /posts/:id
def destroy
@post.destroy
redirect_to posts_path, notice: 'Post deleted successfully.'
end
private
def set_post
@post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :body, :published)
end
end
Routing
RESTful Routes
# config/routes.rb
Rails.application.routes.draw do
# Creates 7 standard routes (index, show, new, create, edit, update, destroy)
resources :posts
# Limit actions
resources :posts, only: [:index, :show]
resources :posts, except: [:destroy]
# Nested resources
resources :authors do
resources :posts
end
# URLs: /authors/:author_id/posts
# Shallow nesting (recommended for deep nesting)
resources :authors do
resources :posts, shallow: true
end
# URLs: /authors/:author_id/posts (collection)
# /posts/:id (member)
# Custom member and collection routes
resources :posts do
member do
post :publish
post :unpublish
end
collection do
get :archived
end
end
# URLs: POST /posts/:id/publish
# GET /posts/archived
# Singular resource
resource :profile, only: [:show, :edit, :update]
# URLs: /profile (no :id needed)
end
Custom Routes
# Named routes
get 'about', to: 'pages#about', as: :about
# Usage: about_path
# Root route
root 'posts#index'
# Redirect
get '/old-path', to: redirect('/new-path')
# Constraints
get 'posts/:id', to: 'posts#show', constraints: { id: /\d+/ }
# Namespace
namespace :admin do
resources :posts
end
# URLs: /admin/posts
# Controller: Admin::PostsController
# Scope
scope module: 'admin' do
resources :posts
end
# URLs: /posts
# Controller: Admin::PostsController
# Concern for reusable routes
concern :commentable do
resources :comments
end
resources :posts, concerns: :commentable
resources :photos, concerns: :commentable
Filters (Callbacks)
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :set_locale
around_action :log_request
after_action :track_analytics
private
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
def log_request
start_time = Time.current
yield
duration = Time.current - start_time
Rails.logger.info "Request took #{duration}s"
end
end
class PostsController < ApplicationController
skip_before_action :authenticate_user!, only: [:index, :show]
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :authorize_post, only: [:edit, :update, :destroy]
private
def authorize_post
unless @post.author == current_user
redirect_to root_path, alert: 'Not authorized'
end
end
end
Strong Parameters
class PostsController < ApplicationController
private
# Basic
def post_params
params.require(:post).permit(:title, :body, :published)
end
# Arrays
def post_params
params.require(:post).permit(:title, :body, tag_ids: [])
end
# Nested attributes
def post_params
params.require(:post).permit(
:title,
:body,
comments_attributes: [:id, :content, :_destroy]
)
end
# Conditional
def post_params
permitted = [:title, :body]
permitted << :published if current_user.admin?
params.require(:post).permit(permitted)
end
end
Rendering and Redirecting
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
# Implicit render: renders views/posts/show.html.erb
# Explicit template
# render :show
# Different template
# render :custom_template
# Partial
# render partial: 'post', locals: { post: @post }
# JSON
# render json: @post
# Plain text
# render plain: "Hello"
# Status codes
# render :show, status: :ok
# render :new, status: :unprocessable_entity
# render json: { error: 'Not found' }, status: :not_found
end
def create
@post = Post.new(post_params)
if @post.save
# Redirect to show page
redirect_to @post
# With flash message
# redirect_to @post, notice: 'Created!'
# With custom path
# redirect_to posts_path
# Back to previous page
# redirect_back(fallback_location: root_path)
else
render :new, status: :unprocessable_entity
end
end
end
Flash Messages
class PostsController < ApplicationController
def create
@post = Post.new(post_params)
if @post.save
# Set flash for next request
flash[:notice] = 'Post created!'
# Or shorter:
redirect_to @post, notice: 'Post created!'
# Different flash types
# flash[:success] = 'Success!'
# flash[:error] = 'Error!'
# flash[:alert] = 'Alert!'
# flash[:warning] = 'Warning!'
else
# flash.now for current request
flash.now[:alert] = 'Could not create post'
render :new
end
end
def update
# Keep flash for next request
flash.keep
redirect_to @post
end
end
Response Formats
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
respond_to do |format|
format.html # renders show.html.erb
format.json { render json: @post }
format.xml { render xml: @post }
format.pdf { render pdf: generate_pdf(@post) }
end
end
def create
@post = Post.new(post_params)
respond_to do |format|
if @post.save
format.html { redirect_to @post, notice: 'Created!' }
format.json { render json: @post, status: :created }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
end
Controller Concerns
# app/controllers/concerns/authenticatable.rb
module Authenticatable
extend ActiveSupport::Concern
included do
before_action :authenticate_user!
helper_method :current_user, :logged_in?
end
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
def logged_in?
current_user.present?
end
def authenticate_user!
unless logged_in?
redirect_to login_path, alert: 'Please log in'
end
end
end
# Usage
class PostsController < ApplicationController
include Authenticatable
end
Error Handling
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
rescue_from ActionController::ParameterMissing, with: :parameter_missing
private
def record_not_found
render file: "#{Rails.root}/public/404.html", status: :not_found
end
def parameter_missing
render json: { error: 'Missing parameter' }, status: :bad_request
end
end
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to posts_path, alert: 'Post not found'
end
end
Session and Cookies
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
# Set session
session[:user_id] = user.id
# Set cookie
cookies[:user_name] = user.name
# Signed cookie (tamper-proof)
cookies.signed[:user_id] = user.id
# Encrypted cookie
cookies.encrypted[:user_data] = { id: user.id, role: user.role }
# Permanent cookie (20 years)
cookies.permanent[:remember_token] = user.remember_token
redirect_to root_path
else
flash.now[:alert] = 'Invalid credentials'
render :new
end
end
def destroy
session.delete(:user_id)
cookies.delete(:user_name)
redirect_to root_path
end
end
Best Practices
- Keep controllers thin - Move business logic to models or service objects
- Use before_action for common setup code
- Always use strong parameters for security
- Return proper HTTP status codes
- Use concerns for shared controller behavior
- Follow REST conventions when possible
- Handle errors gracefully with rescue_from
- Use flash messages for user feedback
- Set instance variables only for view rendering
- Avoid complex queries in controllers - use scopes or query objects
Common Patterns
Service Objects for Complex Actions
class PostsController < ApplicationController
def create
result = Posts::CreateService.call(
params: post_params,
user: current_user
)
if result.success?
redirect_to result.post, notice: 'Created!'
else
@post = result.post
flash.now[:alert] = result.error
render :new
end
end
end
Query Objects for Complex Queries
class PostsController < ApplicationController
def index
@posts = PostsQuery.new(params).call
end
end
References
More from shoebtamboli/rails_claude_skills
rails-auth-with-devise
Complete authentication setup for Ruby on Rails applications using Devise. Use when: (1) Setting up user authentication in a Rails app, (2) Adding sign in/sign up/sign out functionality, (3) Implementing email confirmation, password recovery, or account locking, (4) Configuring OmniAuth social login, (5) Adding multiple user models (User/Admin), (6) Customizing Devise views or controllers, (7) Testing authentication with RSpec/Minitest, (8) API authentication setup
10rspec-testing
This skill should be used when writing, reviewing, or improving RSpec tests for Ruby on Rails applications. Use this skill for all testing tasks including model specs, controller specs, system specs, component specs, service specs, and integration tests. The skill provides comprehensive RSpec best practices from Better Specs and thoughtbot guides.
5rails-mailers
Use when sending emails - ActionMailer with async delivery via SolidQueue, templates, previews, and testing
4minitest-testing
Write, review, and improve Minitest tests for Ruby on Rails applications. Covers model tests, controller tests, system tests, fixtures, and best practices from Rails Testing Guide.
4rails-hotwire
Hotwire (Turbo Drive, Turbo Frames, Turbo Streams, Stimulus)
4