Ruby on Rails Controllers Deep — Advanced Controller Patterns
In this tutorial, you will learn about Ruby on Rails Controllers Deep. We cover key concepts, practical examples, and best practices to help you master this topic.
Rails controllers handle HTTP requests with before/after/around filters, strong parameter whitelisting, multiple rendering formats, flash messages, and reusable concerns.
What You'll Learn
By the end of this tutorial, you'll implement controller filters, whitelist parameters with strong params, render JSON and HTML, use flash messages, and organize code with concerns.
Why Controllers Matter
Controllers orchestrate the request-response cycle. Well-structured controllers extract business logic to models and services while keeping request handling in the controller.
Real-World Use
An API controller uses before_action for authentication, strong parameters for mass assignment protection, and renders JSON responses with proper HTTP status codes.
Controller Path
flowchart LR
A[Rails MVC] --> B[Controllers Deep]
B --> C[Filters]
B --> D[Strong Params]
B --> E[Rendering]
B --> F[Concerns]
B --> G{You Are Here}
style G fill:#f90,color:#fff
Controller Filters
Before, after, and around filters.
class ProductsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_product, only: [:show, :edit, :update, :destroy]
before_action :authorize_product, only: [:edit, :update, :destroy]
after_action :log_access, only: [:show]
around_action :measure_execution_time
def index
@products = Product.all
end
private
def set_product
@product = Product.find(params[:id])
end
def authorize_product
redirect_to products_path, alert: "Not authorized" unless @product.owned_by?(current_user)
end
def measure_execution_time
start = Time.current
yield
Rails.logger.info "Request took #{Time.current - start}s"
end
end
Strong Parameters
Whitelist parameters to prevent mass assignment.
class ProductsController < ApplicationController
def create
@product = Product.new(product_params)
if @product.save
redirect_to @product, notice: "Product created."
else
render :new, status: :unprocessable_entity
end
end
private
def product_params
params.require(:product).permit(
:name, :description, :price, :category_id,
:published,
variants: [:name, :sku, :price],
images: [],
)
end
end
Rendering Options
Render different formats and responses.
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
respond_to do |format|
format.html # renders show.html.erb
format.json { render json: @product, status: :ok }
format.xml { render xml: @product }
format.csv { send_data @product.to_csv, filename: "product.csv" }
format.pdf { render pdf: @product.name, template: "products/show" }
end
end
def update
@product = Product.find(params[:id])
if @product.update(product_params)
respond_to do |format|
format.html { redirect_to @product, notice: "Updated." }
format.json { render json: @product, status: :ok }
end
else
respond_to do |format|
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end
end
Controller Concerns
Share controller logic across multiple controllers.
# app/controllers/concerns/rate_limitable.rb
module RateLimitable
extend ActiveSupport::Concern
included do
before_action :check_rate_limit
end
private
def check_rate_limit
ip = request.remote_ip
key = "rate_limit:#{ip}:#{controller_name}:#{action_name}"
count = Redis.current.get(key).to_i
if count > 100
render json: { error: "Rate limit exceeded" }, status: :too_many_requests
else
Redis.current.multi do
Redis.current.incr(key)
Redis.current.expire(key, 3600)
end
end
end
end
# Include in controllers
class Api::ProductsController < ApplicationController
include RateLimitable
end
Common Mistakes
1. Fat Controllers
Business logic in controllers makes testing hard. Extract to models or service objects.
2. Not Using Strong Parameters
Using params[:product] without permit allows mass assignment of any column.
3. Forgetting to Handle Unauthorized Access
Filters that redirect without proper error messages confuse users.
4. Using Multiple Instance Variables
Excessive @vars in actions signals missing service objects.
5. Not Setting HTTP Status Codes
render json: @product defaults to 200. Use status: :created for create, :unprocessable_entity for validation errors.
Practice Questions
1. What is before_action?
A filter that runs before controller actions, useful for authentication or loading resources.
2. What does params.require(:product).permit(:name) do?
Requires the :product key in params and permits only :name through strong parameters.
3. How do you render different formats?
Use respond_to with format-specific blocks.
4. What is a controller concern?
A module that shares filter, method, and configuration logic across controllers.
5. Challenge: Create an API controller with authentication and Rate Limiting.
class Api::V1::ProductsController < ApplicationController
include RateLimitable
before_action :authenticate_request
def index
products = Product.all
render json: products, each_serializer: ProductSerializer
end
private
def authenticate_request
token = request.headers["Authorization"]&.split(" ")&.last
@user = User.find_by(api_token: token)
render json: { error: "Unauthorized" }, status: :unauthorized unless @user
end
end
FAQ
Mini Project: RESTful API Controller
Build a production-ready API controller.
class Api::V1::ProductsController < ApplicationController
before_action :authenticate
before_action :set_product, only: [:show, :update, :destroy]
def index
products = Product.published.page(params[:page])
render json: products, meta: pagination_meta(products)
end
def show
render json: @product, serializer: ProductDetailSerializer
end
def create
product = Product.new(product_params)
if product.save
render json: product, status: :created
else
render json: { errors: product.errors }, status: :unprocessable_entity
end
end
private
def set_product
@product = Product.find(params[:id])
end
def product_params
params.require(:product).permit(:name, :price, :description)
end
def pagination_meta(collection)
{ current_page: collection.current_page, total_pages: collection.total_pages, total_count: collection.total_count }
end
end
What's Next
Rails Params Strong Parameters Rails Views Deep Rails Layouts Partials
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro