Ruby Action Pack — Controllers Routing Filters and HTTP Handling Explained
In this tutorial, you will learn about Ruby Action Pack. We cover key concepts, practical examples, and best practices to help you master this topic.
Ruby Action Pack is Rails' controller and routing layer that handles HTTP request/response cycles, processes parameters, manages sessions and cookies, and renders responses through controllers, routing, and middleware.
What You'll Learn
- Building controllers and actions
- Defining routes with resources
- Handling params, sessions, and cookies
- Using filters and callbacks
- Understanding RESTful design
Why It Matters
Action Pack is the request/response engine of Rails. Doda Browser uses Action Pack patterns for its admin API. Durga Antivirus Pro uses Action Pack for its cloud management dashboard. Every HTTP request to a Rails app flows through Action Pack.
Real-World Use
GitHub processes millions of requests per second through Rails controllers. Any Rails web application depends on Action Pack for routing, parameter handling, session management, and response rendering.
flowchart LR
A["Request"] --> B["Router"]
B --> C["Controller"]
C --> D["Action"]
D --> E["Response"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Controllers
Controllers are Ruby classes that inherit from ApplicationController:
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
def index
@articles = Article.published.order(created_at: :desc)
end
def show
end
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article, notice: "Article created!"
else
render :new, status: :unprocessable_entity
end
end
def edit
end
def update
if @article.update(article_params)
redirect_to @article, notice: "Article updated!"
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@article.destroy
redirect_to articles_path, notice: "Article deleted!"
end
private
def set_article
@article = Article.find(params[:id])
end
def article_params
params.require(:article).permit(:title, :body, :published)
end
end
Routing
Basic Routes
# config/routes.rb
Rails.application.routes.draw do
# RESTful resource
resources :articles
# Specific routes
get "/articles/search", to: "articles#search"
get "/about", to: "pages#about"
post "/contact", to: "contact#create"
# Nested resources
resources :articles do
resources :comments
end
# Member and collection routes
resources :articles do
member do
patch :publish
post :archive
end
collection do
get :drafts
get :archived
end
end
# Root
root "articles#index"
end
Route Helpers
# Generated helpers for resources :articles
articles_path # /articles
article_path(@article) # /articles/1
new_article_path # /articles/new
edit_article_path(@article) # /articles/1/edit
# With nested resource
article_comments_path(@article) # /articles/1/comments
article_comment_path(@article, @comment) # /articles/1/comments/1
# Member route
publish_article_path(@article) # /articles/1/publish
Parameters
class ArticlesController < ApplicationController
def create
# params contains all request parameters
puts params.inspect
# {"article" => {"title" => "My Post", "body" => "Hello"}, "controller" => "articles", "action" => "create"}
# Strong parameters — whitelist permitted values
permitted = params.require(:article).permit(:title, :body, :published)
@article = Article.new(permitted)
end
def show
# URL parameter (from /articles/:id)
@article = Article.find(params[:id])
end
def search
# Query parameter (from /articles/search?q=rails)
query = params[:q]
@articles = Article.where("title LIKE ?", "%#{query}%")
end
end
Sessions and Cookies
class SessionsController < ApplicationController
def new
# Read session
@return_to = session[:return_to] || root_path
end
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
# Store in session
session[:user_id] = user.id
redirect_to session.delete(:return_to) || root_path
else
flash.now[:alert] = "Invalid email or password"
render :new, status: :unprocessable_entity
end
end
def destroy
# Clear session
session[:user_id] = nil
redirect_to root_path, notice: "Logged out!"
end
end
Cookies
class PreferencesController < ApplicationController
def update_theme
# Set cookie
cookies[:theme] = {
value: params[:theme],
expires: 1.year.from_now,
secure: Rails.env.production?,
httponly: true
}
redirect_back fallback_location: root_path
end
def show
# Read cookie
@theme = cookies[:theme] || "light"
end
def destroy
# Delete cookie
cookies.delete(:theme)
end
end
Filters
Filters run before, after, or around controller actions:
class ApplicationController < ActionController::Base
# Before filters — run before action
before_action :require_login
before_action :set_locale
before_action :track_page_view, only: [:show, :index]
# After filters — run after action
after_action :log_request
# Around filters — wrap action execution
around_action :measure_execution_time
private
def require_login
unless session[:user_id]
session[:return_to] = request.fullpath
redirect_to login_path, alert: "Please log in first"
end
end
def set_locale
I18n.locale = params[:locale] || cookies[:locale] || I18n.default_locale
end
def track_page_view
PageView.create(path: request.path, user_id: session[:user_id])
end
def log_request
Rails.logger.info "#{request.method} #{request.fullpath} -> #{response.status}"
end
def measure_execution_time
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
Rails.logger.debug "Action took #{elapsed.round(3)}s"
end
end
Skipping Filters
class SessionsController < ApplicationController
# Skip require_login for login actions
skip_before_action :require_login, only: [:new, :create]
# Only skip certain actions
skip_before_action :track_page_view, except: [:index]
end
Rendering Responses
class ResponsesController < ApplicationController
skip_before_action :require_login
# Render default view
def index
@articles = Article.all
# Renders app/views/responses/index.html.erb
end
# Explicit render
def show
@article = Article.find(params[:id])
render :show # Explicit, same as default
end
# Different template
def print
@article = Article.find(params[:id])
render "articles/print", layout: "print"
end
# JSON response
def json_example
@article = Article.find(params[:id])
render json: @article, include: :comments, status: :ok
end
# Text response
def health
render plain: "OK", status: :ok
end
# No content
def delete_all
Article.delete_all
head :no_content
end
# Different status codes
def not_found
render plain: "Not found", status: 404
end
# Redirect
def old_page
redirect_to new_location_path, status: :moved_permanently
end
# Send file
def download
send_file Rails.root.join("public", "document.pdf"),
type: "application/pdf",
disposition: "attachment"
end
end
Strong Parameters
class UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render :new, status: :unprocessable_entity
end
end
private
def user_params
# require ensures :user key exists
# permit whitelists allowed attributes
params.require(:user).permit(
:name,
:email,
:password,
:password_confirmation,
:avatar,
role_ids: [], # Array
preferences: {} # Hash (permit all keys)
)
end
end
Flash Messages
class ArticlesController < ApplicationController
def create
@article = Article.new(article_params)
if @article.save
# Flash for next request (redirect)
redirect_to @article, notice: "Article created!"
else
# Flash.now for current request (render)
flash.now[:alert] = "Please fix errors below"
render :new, status: :unprocessable_entity
end
end
def destroy
@article.destroy
# Multiple messages
flash[:notice] = "Article deleted"
flash[:warning] = "This action cannot be undone"
redirect_to articles_path
end
end
Common Mistakes
1. Using Strong Parameters Improperly
# Bad — allows mass assignment of all attributes
def user_params
params.permit!
end
# Bad — missing require
def user_params
params.permit(:name, :email)
end
2. Forgetting to Skip Filters on Auth Controllers
# Login loop — can't log in because require_login blocks login page!
skip_before_action :require_login, only: [:new, :create]
3. Putting Business Logic in Controllers
# Bad — controller too fat
def create
@order = Order.new(order_params)
@order.calculate_tax
@order.apply_discounts
@order.send_confirmation
@order.update_inventory
@order.track_analytics
@order.save
end
# Good — move to model or service
def create
@order = Order.create_with_processing(order_params)
end
4. Not Using HTTP Status Codes Correctly
# Bad — always 200 OK even on errors
render json: { error: "Not found" }
# Good — appropriate status
render json: { error: "Not found" }, status: :not_found
5. Not Handling Unpermitted Parameters
# In config/application.rb or initializer
config.action_controller.action_on_unpermitted_parameters = :raise
# Or :log in development
Practice Questions
1. What is the request/response cycle in Rails?
Router matches URL to controller/action. Controller processes request (params, session), interacts with model, and renders response (view, JSON, redirect).
2. What are strong parameters?
A security feature requiring explicit permission for mass assignment. params.require(:user).permit(:name, :email) whitelists name and email, blocking all other attributes.
3. What's the difference between render and redirect_to?
render renders a template for the current request. redirect_to sends a 302 response telling the browser to make a new request to a different URL.
4. How do before_action filters work?
Methods that run before controller actions. Can halt the request cycle (by redirecting or rendering) or set up instance variables for the action.
Challenge: Create a RESTful API controller for a Task model that supports CRUD, due-date filtering, and returns JSON responses with proper HTTP status codes.
Solution
class Api::TasksController < ApplicationController
before_action :set_task, only: [:show, :update, :destroy]
def index
@tasks = Task.order(created_at: :desc)
@tasks = @tasks.where("due_date <= ?", params[:due_before]) if params[:due_before]
@tasks = @tasks.where(completed: params[:completed]) if params.key?(:completed)
render json: @tasks, status: :ok
end
def show
render json: @task, status: :ok
end
def create
@task = Task.new(task_params)
if @task.save
render json: @task, status: :created, location: api_task_url(@task)
else
render json: { errors: @task.errors.full_messages }, status: :unprocessable_entity
end
end
def update
if @task.update(task_params)
render json: @task, status: :ok
else
render json: { errors: @task.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
@task.destroy
head :no_content
end
private
def set_task
@task = Task.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: "Task not found" }, status: :not_found
end
def task_params
params.require(:task).permit(:title, :description, :completed, :due_date, :priority)
end
end
Routing:
Rails.application.routes.draw do
namespace :api do
resources :tasks
end
end
FAQ
{{< faq question="What is the difference between ApplicationController and ActionController::Base?" >}}
ActionController::Base is Rails' base controller class. ApplicationController inherits from it and serves as the parent for all application controllers. You add shared logic in ApplicationController.
{{< /faq >}}
{{< faq question="Should controllers be skinny or fat?" >}} Skinny. Controllers should only handle HTTP concerns (params, session, response). Business logic belongs in models or service objects. "Skinny controller, fat model" is the Rails way. {{< /faq >}}
{{< faq question="What is CSRF protection in Rails?" >}}
Cross-Site Request Forgery protection. Rails includes an authenticity token in forms and verifies it on POST/PUT/DELETE requests. protect_from_forgery with: :exception is default.
{{< /faq >}}
{{< faq question="How do I handle API authentication?" >}}
Use authenticate_or_request_with_http_token or libraries like Devise + JWT. For Rails API apps, use before_action :authenticate with token verification.
{{< /faq >}}
{{< faq question="What is the difference between resources and resource?" >}}
resources creates RESTful routes for a collection (index, show, new, create, edit, update, destroy). resource creates routes for a Singleton (show, new, create, edit, update, destroy) without index.
{{< /faq >}}
Try It Yourself
# Create a Rails API
rails new todo_api --api
cd todo_api
# Generate task resource
bin/rails generate scaffold Task title:string completed:boolean
bin/rails db:migrate
# Add some data
bin/rails console
Task.create!(title: "Learn Rails", completed: false)
Task.create!(title: "Build an API", completed: false)
exit
# Start server
bin/rails server
# Test with curl
curl http://localhost:3000/tasks
curl -X POST http://localhost:3000/tasks \
-H "Content-Type: application/json" \
-d '{"task": {"title": "New task"}}'
What's Next
Now that you understand controllers and routing, learn about Action View for rendering HTML templates.
| Topic | Description | Link |
|---|---|---|
| Ruby Action View | ERB templates, helpers, partials | {{< ref "28-action-view" >}} |
| Ruby Migrations | Schema changes and data types | {{< ref "29-migrations" >}} |
| Python Django Views | Compare Django's view layer | Python |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro