Skip to content

Ruby Action Pack — Controllers Routing Filters and HTTP Handling Explained

DodaTech Updated 2026-06-28 8 min read

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