internal/skills/content/rails/SKILL.md
Rails 7+ framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Rails projects, or when the user mentions Ruby on Rails. Provides ActiveRecord, Hotwire/Turbo, Action Cable, and convention-over-configuration guidelines.
npx skillsauth add ar4mirez/samuel railsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Applies to: Rails 7+, Ruby 3.2+, Hotwire/Turbo, ActiveRecord, Action Cable
Good fit: Full-stack web apps, MVPs, CMS, e-commerce, SaaS, Hotwire/Turbo SPA-like UX, API backends.
Consider alternatives: Minimal microservices (Sinatra, Hanami), heavy real-time streaming, strict type safety needs (Rust, Go).
myapp/
├── app/
│ ├── controllers/
│ │ ├── application_controller.rb
│ │ ├── concerns/ # Controller concerns (auth, pagination)
│ │ └── api/
│ │ └── v1/ # Versioned API controllers
│ ├── models/
│ │ ├── application_record.rb
│ │ └── concerns/ # Model concerns (searchable, sluggable)
│ ├── views/
│ │ ├── layouts/ # Application layouts
│ │ │ └── application.html.erb
│ │ ├── shared/ # Shared partials (_navbar, _footer)
│ │ └── posts/ # Resource-specific views
│ ├── helpers/ # View helpers
│ ├── jobs/ # ActiveJob classes
│ ├── mailers/ # Action Mailer classes
│ ├── channels/ # Action Cable channels
│ └── services/ # Service objects (custom directory)
├── config/
│ ├── routes.rb # Route definitions
│ ├── database.yml # Database configuration
│ ├── environments/ # Per-environment settings
│ └── initializers/ # Boot-time configuration
├── db/
│ ├── migrate/ # Migration files
│ ├── schema.rb # Current schema snapshot
│ └── seeds.rb # Seed data
├── lib/
│ └── tasks/ # Custom Rake tasks
├── test/ # Minitest (default) or spec/ for RSpec
│ ├── models/
│ ├── controllers/
│ ├── integration/
│ └── system/
├── Gemfile
└── Gemfile.lock
app/services/; use concerns for shared model/controller behaviorApi::V1; keep shared partials in views/shared/before_action for authentication and resource loading*_params methodsstatus: :unprocessable_entity for failed form submissionsstatus: :see_other (303) for redirect_to after DELETErespond_to blocks when serving multiple formatsdependent: option on has_many/has_one associationsscope for reusable queries (never build queries in controllers)enum with explicit integer or string mappingsbefore_validation for data normalization (downcase, strip)counter_cache: true for belongs_to when parent displays countsnull: false for required columnsreferences with foreign_key: true for associationsdefault: for boolean and status columnsup and down methods for irreversible migrationsparams.permit! (permit all)raw or html_safe with user dataRails.application.credentials or environment variablesforce_ssl in productioncontent_security_policy configuration in productionincludes (or preload/eager_load) to prevent N+1 queriescounter_cache for association counts displayed in viewscache @record do) for expensive view renderingfind_each instead of each when iterating over large datasets# app/models/post.rb
class Post < ApplicationRecord
# 1. Associations
belongs_to :user
belongs_to :category, optional: true
has_many :comments, dependent: :destroy
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
has_one_attached :featured_image
# 2. Validations
validates :title, presence: true, length: { maximum: 255 }
validates :body, presence: true
# 3. Enums
enum :status, { draft: 0, published: 1, archived: 2 }
# 4. Scopes
scope :published, -> { where(status: :published) }
scope :recent, -> { order(created_at: :desc) }
scope :by_category, ->(cat) { where(category: cat) }
scope :search, ->(q) {
where("title ILIKE :q OR body ILIKE :q", q: "%#{sanitize_sql_like(q)}%")
}
# 5. Callbacks (keep minimal)
before_validation :normalize_title
# 6. Instance methods
def publish!
update!(status: :published, published_at: Time.current)
end
private
def normalize_title
self.title = title&.strip
end
end
Model ordering convention: Associations, validations, enums, scopes, callbacks, class methods, instance methods, private methods.
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :authorize_post!, only: [:edit, :update, :destroy]
def index
@posts = Post.published
.includes(:user, :category)
.recent
.page(params[:page])
.per(20)
end
def show; end
def new
@post = current_user.posts.build
end
def create
@post = current_user.posts.build(post_params)
if @post.save
redirect_to @post, notice: "Post created."
else
render :new, status: :unprocessable_entity
end
end
def edit; end
def update
if @post.update(post_params)
redirect_to @post, notice: "Post updated."
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@post.destroy
redirect_to posts_url, notice: "Post deleted.", status: :see_other
end
private
def set_post
@post = Post.find(params[:id])
end
def authorize_post!
redirect_to posts_url, alert: "Not authorized." unless @post.user == current_user
end
def post_params
params.require(:post).permit(:title, :body, :category_id, :status, :featured_image, tag_ids: [])
end
end
_form.html.erb partial shared between new and editrender @posts (auto-maps to _post.html.erb)render partial: "post", locals: { post: @post }content_for :title for page-specific titles# config/routes.rb
Rails.application.routes.draw do
root "home#index"
# Authentication
get "login", to: "sessions#new"
post "login", to: "sessions#create"
delete "logout", to: "sessions#destroy"
# RESTful resources
resources :posts do
resources :comments, only: [:create, :destroy]
member do
post :publish
post :unpublish
end
end
resources :categories, only: [:index, :show] do
resources :posts, only: [:index]
end
# Admin namespace
namespace :admin do
root "dashboard#index"
resources :users
resources :posts
end
# API namespace
namespace :api do
namespace :v1 do
resources :posts, only: [:index, :show, :create, :update, :destroy]
end
end
# Health check
get "health", to: "health#show"
end
Route conventions:
resources for standard CRUD (generates 7 RESTful routes)only: or except: to limit generated routesmember for actions on a specific record, collection for actions on the setshallow: true for deeper nesting# db/migrate/20240115000000_create_posts.rb
class CreatePosts < ActiveRecord::Migration[7.1]
def change
create_table :posts do |t|
t.references :user, null: false, foreign_key: true
t.references :category, foreign_key: true
t.string :title, null: false
t.text :body, null: false
t.integer :status, default: 0, null: false
t.datetime :published_at
t.integer :comments_count, default: 0, null: false
t.timestamps
end
add_index :posts, [:user_id, :status]
add_index :posts, :published_at
end
end
t.references for foreign keys (adds index automatically)t.timestamps for created_at and updated_atcomments_count with counter_cache: true on the association# app/services/application_service.rb
class ApplicationService
def self.call(...)
new(...).call
end
end
# app/services/posts/publish_service.rb
module Posts
class PublishService < ApplicationService
def initialize(post:, user:)
@post = post
@user = user
end
def call
return ServiceResult.failure(["Not authorized"]) unless @user == @post.user
ActiveRecord::Base.transaction do
@post.update!(status: :published, published_at: Time.current)
notify_subscribers
end
ServiceResult.success(@post)
rescue ActiveRecord::RecordInvalid => e
ServiceResult.failure(e.record.errors.full_messages)
end
private
def notify_subscribers
PostMailer.published(@post).deliver_later
end
end
end
self.call(...) class method pattern for clean invocationActiveRecord::Base.transactionPosts::PublishService, Users::CreateService# Application
rails new myapp --database=postgresql --css=tailwind
rails new myapp --api # API-only mode
rails server # Start dev server
rails console # Interactive console
rails routes # List all routes
# Generators
rails generate model User name:string email:string
rails generate controller Posts index show new create
rails generate scaffold Article title:string body:text
rails generate migration AddStatusToPosts status:integer
# Database
rails db:create # Create database
rails db:migrate # Run pending migrations
rails db:rollback # Undo last migration
rails db:seed # Run seeds.rb
rails db:reset # Drop, create, migrate, seed
# Testing
rails test # Run all tests
rails test:models # Model tests only
rails test:system # System tests only
# Assets and dependencies
bundle install # Install gems
rails assets:precompile # Compile assets for production
# test/models/post_test.rb
require "test_helper"
class PostTest < ActiveSupport::TestCase
def setup
@post = posts(:first_post)
end
test "valid post" do
assert @post.valid?
end
test "invalid without title" do
@post.title = nil
assert_not @post.valid?
assert_includes @post.errors[:title], "can't be blank"
end
test "publish! sets status and timestamp" do
@post.publish!
assert @post.published?
assert_not_nil @post.published_at
end
end
factory_bot for test datatest "user cannot edit others' posts"Core: rails ~> 7.1, pg, puma, redis, turbo-rails, stimulus-rails, importmap-rails
Auth: bcrypt (has_secure_password)
Background: sidekiq
Pagination: kaminari or pagy
Dev/Test: debug, capybara, selenium-webdriver, web-console, rack-mini-profiler
Optional: rspec-rails, factory_bot_rails, faker, shoulda-matchers, webmock
includes/preload on every association accessed in viewsfind_each for batch operations on large datasetsparams.permit! (mass-assignment vulnerability)Model.all without pagination or limitsraw/html_safe with user-provided dataFor detailed code examples and advanced patterns, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.