rails-controllers
npx machina-cli add skill Shoebtamboli/rails_claude_skills/rails-controllers --openclawFiles (1)
SKILL.md
10.8 KB
Rails Controllers
Quick Reference
| Pattern | Example |
|---|---|
| Generate | rails g controller Posts index show |
| Route | resources :posts |
| Action | def show; @post = Post.find(params[:id]); end |
| Render | render :edit |
| Redirect | redirect_to posts_path |
| Filter | before_action :authenticate_user! |
| Strong Params | params.require(:post).permit(:title, :body) |
Controller Structure
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
# GET /posts
def index
@posts = Post.all.order(created_at: :desc)
end
# GET /posts/:id
def show
# @post set by before_action
end
# GET /posts/new
def new
@post = Post.new
end
# POST /posts
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post, notice: 'Post created successfully.'
else
render :new, status: :unprocessable_entity
end
end
# GET /posts/:id/edit
def edit
# @post set by before_action
end
# PATCH/PUT /posts/:id
def update
if @post.update(post_params)
redirect_to @post, notice: 'Post updated successfully.'
else
render :edit, status: :unprocessable_entity
end
end
# DELETE /posts/:id
def destroy
@post.destroy
redirect_to posts_path, notice: 'Post deleted successfully.'
end
private
def set_post
@post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :body, :published)
end
end
Routing
RESTful Routes
# config/routes.rb
Rails.application.routes.draw do
# Creates 7 standard routes (index, show, new, create, edit, update, destroy)
resources :posts
# Limit actions
resources :posts, only: [:index, :show]
resources :posts, except: [:destroy]
# Nested resources
resources :authors do
resources :posts
end
# URLs: /authors/:author_id/posts
# Shallow nesting (recommended for deep nesting)
resources :authors do
resources :posts, shallow: true
end
# URLs: /authors/:author_id/posts (collection)
# /posts/:id (member)
# Custom member and collection routes
resources :posts do
member do
post :publish
post :unpublish
end
collection do
get :archived
end
end
# URLs: POST /posts/:id/publish
# GET /posts/archived
# Singular resource
resource :profile, only: [:show, :edit, :update]
# URLs: /profile (no :id needed)
end
Custom Routes
# Named routes
get 'about', to: 'pages#about', as: :about
# Usage: about_path
# Root route
root 'posts#index'
# Redirect
get '/old-path', to: redirect('/new-path')
# Constraints
get 'posts/:id', to: 'posts#show', constraints: { id: /\d+/ }
# Namespace
namespace :admin do
resources :posts
end
# URLs: /admin/posts
# Controller: Admin::PostsController
# Scope
scope module: 'admin' do
resources :posts
end
# URLs: /posts
# Controller: Admin::PostsController
# Concern for reusable routes
concern :commentable do
resources :comments
end
resources :posts, concerns: :commentable
resources :photos, concerns: :commentable
Filters (Callbacks)
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :set_locale
around_action :log_request
after_action :track_analytics
private
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
def log_request
start_time = Time.current
yield
duration = Time.current - start_time
Rails.logger.info "Request took #{duration}s"
end
end
class PostsController < ApplicationController
skip_before_action :authenticate_user!, only: [:index, :show]
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :authorize_post, only: [:edit, :update, :destroy]
private
def authorize_post
unless @post.author == current_user
redirect_to root_path, alert: 'Not authorized'
end
end
end
Strong Parameters
class PostsController < ApplicationController
private
# Basic
def post_params
params.require(:post).permit(:title, :body, :published)
end
# Arrays
def post_params
params.require(:post).permit(:title, :body, tag_ids: [])
end
# Nested attributes
def post_params
params.require(:post).permit(
:title,
:body,
comments_attributes: [:id, :content, :_destroy]
)
end
# Conditional
def post_params
permitted = [:title, :body]
permitted << :published if current_user.admin?
params.require(:post).permit(permitted)
end
end
Rendering and Redirecting
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
# Implicit render: renders views/posts/show.html.erb
# Explicit template
# render :show
# Different template
# render :custom_template
# Partial
# render partial: 'post', locals: { post: @post }
# JSON
# render json: @post
# Plain text
# render plain: "Hello"
# Status codes
# render :show, status: :ok
# render :new, status: :unprocessable_entity
# render json: { error: 'Not found' }, status: :not_found
end
def create
@post = Post.new(post_params)
if @post.save
# Redirect to show page
redirect_to @post
# With flash message
# redirect_to @post, notice: 'Created!'
# With custom path
# redirect_to posts_path
# Back to previous page
# redirect_back(fallback_location: root_path)
else
render :new, status: :unprocessable_entity
end
end
end
Flash Messages
class PostsController < ApplicationController
def create
@post = Post.new(post_params)
if @post.save
# Set flash for next request
flash[:notice] = 'Post created!'
# Or shorter:
redirect_to @post, notice: 'Post created!'
# Different flash types
# flash[:success] = 'Success!'
# flash[:error] = 'Error!'
# flash[:alert] = 'Alert!'
# flash[:warning] = 'Warning!'
else
# flash.now for current request
flash.now[:alert] = 'Could not create post'
render :new
end
end
def update
# Keep flash for next request
flash.keep
redirect_to @post
end
end
Response Formats
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
respond_to do |format|
format.html # renders show.html.erb
format.json { render json: @post }
format.xml { render xml: @post }
format.pdf { render pdf: generate_pdf(@post) }
end
end
def create
@post = Post.new(post_params)
respond_to do |format|
if @post.save
format.html { redirect_to @post, notice: 'Created!' }
format.json { render json: @post, status: :created }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
end
Controller Concerns
# app/controllers/concerns/authenticatable.rb
module Authenticatable
extend ActiveSupport::Concern
included do
before_action :authenticate_user!
helper_method :current_user, :logged_in?
end
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
def logged_in?
current_user.present?
end
def authenticate_user!
unless logged_in?
redirect_to login_path, alert: 'Please log in'
end
end
end
# Usage
class PostsController < ApplicationController
include Authenticatable
end
Error Handling
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
rescue_from ActionController::ParameterMissing, with: :parameter_missing
private
def record_not_found
render file: "#{Rails.root}/public/404.html", status: :not_found
end
def parameter_missing
render json: { error: 'Missing parameter' }, status: :bad_request
end
end
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to posts_path, alert: 'Post not found'
end
end
Session and Cookies
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
# Set session
session[:user_id] = user.id
# Set cookie
cookies[:user_name] = user.name
# Signed cookie (tamper-proof)
cookies.signed[:user_id] = user.id
# Encrypted cookie
cookies.encrypted[:user_data] = { id: user.id, role: user.role }
# Permanent cookie (20 years)
cookies.permanent[:remember_token] = user.remember_token
redirect_to root_path
else
flash.now[:alert] = 'Invalid credentials'
render :new
end
end
def destroy
session.delete(:user_id)
cookies.delete(:user_name)
redirect_to root_path
end
end
Best Practices
- Keep controllers thin - Move business logic to models or service objects
- Use before_action for common setup code
- Always use strong parameters for security
- Return proper HTTP status codes
- Use concerns for shared controller behavior
- Follow REST conventions when possible
- Handle errors gracefully with rescue_from
- Use flash messages for user feedback
- Set instance variables only for view rendering
- Avoid complex queries in controllers - use scopes or query objects
Common Patterns
Service Objects for Complex Actions
class PostsController < ApplicationController
def create
result = Posts::CreateService.call(
params: post_params,
user: current_user
)
if result.success?
redirect_to result.post, notice: 'Created!'
else
@post = result.post
flash.now[:alert] = result.error
render :new
end
end
end
Query Objects for Complex Queries
class PostsController < ApplicationController
def index
@posts = PostsQuery.new(params).call
end
end
References
Source
git clone https://github.com/Shoebtamboli/rails_claude_skills/blob/main/lib/generators/claude/skills_library/rails-controllers/SKILL.mdView on GitHub Overview
Rails-Controllers covers building RESTful endpoints with standard controller actions, routing, and response handling in Rails 7+. It demonstrates how to wire actions like index, show, create, update, and destroy with filters and strong parameters to secure inputs.
How This Skill Works
Controllers map HTTP requests to actions via RESTful routes. They commonly use before_action filters for setup and authentication, rely on strong params to permit inputs, and respond with render or redirect, including appropriate HTTP status codes for success or errors.
When to Use It
- Building a standard resource (e.g., Post) with index/show/new/create/edit/update/destroy actions
- Applying authentication or setup logic with before_action filters
- Leveraging RESTful routes via resources and nested resources
- Enforcing input security with strong parameters (params.require(...).permit(...))
- Choosing between rendering views and redirecting after actions
Quick Start
- Step 1: Generate the controller with standard actions: rails g controller Posts index show new create edit update destroy
- Step 2: Implement actions and before_actions, e.g., set_post and post_params
- Step 3: Wire routes using resources :posts and implement strong_params in a private post_params method
Best Practices
- Follow RESTful routing with resources to keep URLs predictable and conventional
- Use before_action to set commonly used records and enforce authentication
- Use strong_params (params.require(:model).permit(...)) to prevent mass assignment
- Keep controller actions lean by delegating business logic to models or services
- Return proper HTTP status codes (e.g., unprocessable_entity for validation errors; redirects on success)
Example Use Cases
- Generate a controller with regular actions: rails g controller Posts index show new create edit update destroy
- Define index and show actions that load records (e.g., @posts = Post.all; @post = Post.find(params[:id]))
- Use before_action :authenticate_user! and before_action :set_post to manage access and data
- Render or redirect after create/update/destroy (e.g., render :new, status: :unprocessable_entity or redirect_to @post)
- Configure RESTful routes with resources :posts, including nested or custom routes as needed
Frequently Asked Questions
Add this skill to your agents