action-controller

bastos's avatarfrom bastos

This skill should be used when the user asks about "controllers", "actions", "params", "strong parameters", "filters", "before_action", "after_action", "callbacks", "respond_to", "render", "redirect_to", "flash messages", "sessions", "cookies", "request handling", "routing constraints", or needs guidance on building Rails controllers.

0stars🔀0forks📁View on GitHub🕐Updated Jan 10, 2026

When & Why to Use This Skill

This Claude skill serves as a comprehensive technical reference and assistant for Ruby on Rails Action Controller development. It streamlines the creation of backend logic by providing expert guidance on request handling, RESTful routing, and response rendering. By leveraging this skill, developers can efficiently implement secure strong parameters, manage complex controller filters, and handle sessions or cookies, ensuring follow-through on Rails best practices and modern conventions like Rails 8 features.

Use Cases

  • Scaffolding and customizing RESTful controllers with standard actions like index, show, create, and update.
  • Implementing secure data ingestion using Strong Parameters and the Rails 8 'params.expect' syntax to prevent mass-assignment vulnerabilities.
  • Configuring controller callbacks (before_action, after_action) for authentication, authorization, and resource pre-loading.
  • Designing multi-format responses including HTML, JSON for APIs, and Turbo Streams for reactive web interfaces.
  • Managing user state and feedback using sessions, encrypted cookies, and flash messages for a seamless UX.
  • Setting up robust error handling and global exceptions using 'rescue_from' to return consistent status codes and error pages.
nameaction-controller
descriptionThis skill should be used when the user asks about "controllers", "actions", "params", "strong parameters", "filters", "before_action", "after_action", "callbacks", "respond_to", "render", "redirect_to", "flash messages", "sessions", "cookies", "request handling", "routing constraints", or needs guidance on building Rails controllers.
version1.0.0

Action Controller

Comprehensive guide to Rails controllers, request handling, filters, and responses.

Controller Basics

Naming Conventions

Convention Example
Class name ArticlesController (plural + Controller)
File name articles_controller.rb
Views directory app/views/articles/
Route resource resources :articles

Standard RESTful Actions

class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!, except: [:index, :show]

  # GET /articles
  def index
    @articles = Article.published.recent.page(params[:page])
  end

  # GET /articles/:id
  def show
  end

  # GET /articles/new
  def new
    @article = current_user.articles.build
  end

  # POST /articles
  def create
    @article = current_user.articles.build(article_params)

    if @article.save
      redirect_to @article, notice: "Article created successfully."
    else
      render :new, status: :unprocessable_entity
    end
  end

  # GET /articles/:id/edit
  def edit
  end

  # PATCH/PUT /articles/:id
  def update
    if @article.update(article_params)
      redirect_to @article, notice: "Article updated successfully."
    else
      render :edit, status: :unprocessable_entity
    end
  end

  # DELETE /articles/:id
  def destroy
    @article.destroy
    redirect_to articles_url, notice: "Article deleted."
  end

  private

  def set_article
    @article = Article.find(params[:id])
  end

  def article_params
    params.require(:article).permit(:title, :body, :status, tag_ids: [])
  end
end

Strong Parameters

Basic Usage

def article_params
  params.require(:article).permit(:title, :body, :published)
end

Nested Attributes

def article_params
  params.require(:article).permit(
    :title,
    :body,
    tag_ids: [],
    comments_attributes: [:id, :body, :_destroy],
    metadata: {}  # Permit hash with any keys
  )
end

Conditional Permitting

def article_params
  permitted = [:title, :body]
  permitted << :featured if current_user.admin?
  params.require(:article).permit(permitted)
end

Using expect (Rails 8+)

def article_params
  params.expect(article: [:title, :body, :status])
end

Filters (Callbacks)

Types

class ApplicationController < ActionController::Base
  before_action :authenticate_user!
  after_action :log_activity
  around_action :wrap_in_transaction
end

Filter Options

class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!, except: [:index, :show]
  before_action :require_admin, if: :admin_action?
  before_action :check_feature_flag, unless: -> { Rails.env.development? }
  skip_before_action :authenticate_user!, only: [:index]
end

Filter Methods

class ApplicationController < ActionController::Base
  before_action :set_locale

  private

  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end

  def authenticate_user!
    redirect_to login_path, alert: "Please sign in" unless current_user
  end

  def require_admin
    head :forbidden unless current_user&.admin?
  end
end

Rendering

Render Options

# Render view for current action
render

# Render specific template
render :edit
render "articles/edit"
render template: "articles/edit"

# Render with status
render :new, status: :unprocessable_entity

# Render partial
render partial: "form"
render partial: "article", locals: { article: @article }
render partial: "article", collection: @articles

# Render inline
render inline: "<%= @article.title %>"
render plain: "OK"
render html: "<strong>Bold</strong>".html_safe
render json: @article
render xml: @article

# Render nothing
head :ok
head :no_content
head :not_found

Render Formats

def show
  respond_to do |format|
    format.html
    format.json { render json: @article }
    format.pdf { render pdf: generate_pdf(@article) }
  end
end

Turbo Stream Responses

def create
  @comment = @article.comments.build(comment_params)

  if @comment.save
    respond_to do |format|
      format.turbo_stream
      format.html { redirect_to @article }
    end
  else
    render :new, status: :unprocessable_entity
  end
end

Redirects

# To URL
redirect_to articles_url
redirect_to article_path(@article)

# To record (resourceful)
redirect_to @article  # Same as article_path(@article)

# With flash message
redirect_to @article, notice: "Saved!"
redirect_to @article, alert: "Warning!"
redirect_to @article, flash: { info: "Custom message" }

# Status codes
redirect_to @article, status: :see_other  # 303
redirect_to @article, status: :moved_permanently  # 301

# Back to referrer
redirect_back(fallback_location: root_path)
redirect_back_or_to root_path  # Rails 7+

Flash Messages

class ArticlesController < ApplicationController
  def create
    @article = Article.new(article_params)

    if @article.save
      flash[:notice] = "Article created!"
      redirect_to @article
    else
      flash.now[:alert] = "Please fix the errors."
      render :new
    end
  end

  def update
    if @article.update(article_params)
      redirect_to @article, notice: "Updated!"  # Shorthand
    else
      flash.now[:alert] = "Failed to update."
      render :edit, status: :unprocessable_entity
    end
  end
end

Flash Types

# Standard
flash[:notice]  # Success messages
flash[:alert]   # Warning/error messages

# Custom types (add to application_controller.rb)
add_flash_types :info, :warning, :error

Sessions and Cookies

Sessions

# Store
session[:user_id] = user.id
session[:cart] = { items: [], total: 0 }

# Read
current_user_id = session[:user_id]

# Delete
session.delete(:user_id)

# Clear all
reset_session

Cookies

# Simple cookie
cookies[:remember_token] = user.remember_token

# With options
cookies[:user_preferences] = {
  value: preferences.to_json,
  expires: 1.year.from_now,
  httponly: true,
  secure: Rails.env.production?
}

# Signed cookie (tamper-proof)
cookies.signed[:user_id] = current_user.id
user_id = cookies.signed[:user_id]

# Encrypted cookie
cookies.encrypted[:secret_data] = sensitive_info
data = cookies.encrypted[:secret_data]

# Permanent cookie (20 years)
cookies.permanent[:preferences] = value

# Delete
cookies.delete(:remember_token)

Request and Response

Accessing Request Data

# Parameters
params[:id]
params[:article][:title]
params.permit(:title, :body)

# Request info
request.method        # "GET", "POST", etc.
request.path          # "/articles/1"
request.fullpath      # "/articles/1?page=2"
request.url           # Full URL
request.host          # "example.com"
request.ip            # Client IP
request.remote_ip     # Client IP (proxy-aware)
request.user_agent    # Browser info
request.referer       # Previous URL

# Headers
request.headers["Authorization"]
request.headers["X-Custom-Header"]

# Format
request.format        # :html, :json, etc.
request.xhr?          # AJAX request?
request.get?          # HTTP method checks
request.post?

Setting Response

# Headers
response.headers["X-Custom-Header"] = "value"
response.headers["Cache-Control"] = "no-cache"

# Status
response.status = 201

# Content type
response.content_type = "application/json"

Error Handling

Rescue From

class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, with: :not_found
  rescue_from ActionController::ParameterMissing, with: :bad_request
  rescue_from Pundit::NotAuthorizedError, with: :forbidden

  private

  def not_found
    respond_to do |format|
      format.html { render "errors/404", status: :not_found }
      format.json { render json: { error: "Not found" }, status: :not_found }
    end
  end

  def bad_request(exception)
    render json: { error: exception.message }, status: :bad_request
  end

  def forbidden
    redirect_to root_path, alert: "Access denied."
  end
end

API Controllers

class Api::V1::ArticlesController < ActionController::API
  before_action :authenticate_api_user!

  def index
    @articles = Article.published.page(params[:page])
    render json: @articles, each_serializer: ArticleSerializer
  end

  def show
    @article = Article.find(params[:id])
    render json: @article, serializer: ArticleSerializer
  end

  def create
    @article = current_user.articles.build(article_params)

    if @article.save
      render json: @article, status: :created
    else
      render json: { errors: @article.errors }, status: :unprocessable_entity
    end
  end

  private

  def authenticate_api_user!
    token = request.headers["Authorization"]&.split(" ")&.last
    @current_user = User.find_by(api_token: token)
    head :unauthorized unless @current_user
  end

  def article_params
    params.require(:article).permit(:title, :body)
  end
end

Streaming and Downloads

# Send file
send_file Rails.root.join("files", "report.pdf"),
          type: "application/pdf",
          disposition: "attachment"

# Send data
send_data generate_csv(@records),
          type: "text/csv",
          filename: "export.csv"

# Streaming response
def export
  response.headers["Content-Type"] = "text/csv"
  response.headers["Content-Disposition"] = 'attachment; filename="data.csv"'

  response.stream.write "id,name\n"
  Article.find_each do |article|
    response.stream.write "#{article.id},#{article.name}\n"
  end
ensure
  response.stream.close
end

Additional Resources

Reference Files

  • references/controller-patterns.md - Advanced patterns, concerns, service integration
action-controller – AI Agent Skills | Claude Skills