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
-
Rails Guides - Controllers
-
Rails Guides - Routing
-
Rails API - ActionController