Skip to content

Ruby on Rails Controllers Deep — Advanced Controller Patterns

DodaTech Updated 2026-06-28 4 min read

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

What is the difference between before_action and after_action?

before_action runs before the action. after_action runs after.

Can you skip a before_action?

Yes. skip_before_action :filter_name in child controllers.

What is around_action?

A filter that wraps the action execution, useful for measuring time or transactions.

How do you handle multiple formats?

Use respond_to with format.html, format.json blocks.

What HTTP status code should I use for create?

Return 201 Created for successful resource creation.

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