rails

Applies to: Rails 7+, Ruby 3.2+, Hotwire/Turbo, ActiveRecord, Action Cable

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "rails" with this command: npx skills add ar4mirez/samuel/ar4mirez-samuel-rails

Ruby on Rails Guide

Applies to: Rails 7+, Ruby 3.2+, Hotwire/Turbo, ActiveRecord, Action Cable

Core Principles

  • Convention over Configuration: Follow Rails conventions -- naming, directory structure, RESTful routes

  • Fat Models, Thin Controllers: Business logic in models and service objects, controllers handle request/response only

  • DRY: Use concerns, partials, helpers, and shared services to avoid repetition

  • RESTful by Default: Design resources around standard CRUD actions before adding custom routes

  • Security by Default: CSRF, XSS protection, strong parameters, and parameterized queries are built in

When to Use Rails

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).

Project Structure

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

  • Place business logic in app/services/ ; use concerns for shared model/controller behavior

  • Namespace API controllers under Api::V1 ; keep shared partials in views/shared/

Guardrails

Controllers

  • Keep controllers under 100 lines total

  • Limit each action to 10 lines (delegate to services for complex logic)

  • Always use before_action for authentication and resource loading

  • Always use strong parameters via private *_params methods

  • Return status: :unprocessable_entity for failed form submissions

  • Use status: :see_other (303) for redirect_to after DELETE

  • Use respond_to blocks when serving multiple formats

Models

  • Always add validations for required fields and constraints

  • Always add dependent: option on has_many /has_one associations

  • Use scope for reusable queries (never build queries in controllers)

  • Use enum with explicit integer or string mappings

  • Use before_validation for data normalization (downcase, strip)

  • Avoid heavy logic in callbacks -- prefer service objects for side effects

  • Always define counter_cache: true for belongs_to when parent displays counts

Migrations

  • Always add null: false for required columns

  • Always add database indexes for foreign keys and frequently queried columns

  • Always add unique indexes where uniqueness is required

  • Use references with foreign_key: true for associations

  • Set sensible defaults with default: for boolean and status columns

  • Include both up and down methods for irreversible migrations

  • Never modify a migration after it has been applied to production

Security

  • Strong parameters: never use params.permit! (permit all)

  • CSRF protection: enabled by default, skip only for API controllers with token auth

  • SQL injection: use ActiveRecord query methods, never string interpolation in queries

  • XSS: ERB auto-escapes by default; never use raw or html_safe with user data

  • Mass assignment: only permit explicitly needed attributes

  • Secrets: use Rails.application.credentials or environment variables

  • Set force_ssl in production

  • Use content_security_policy configuration in production

Performance

  • Always use includes (or preload /eager_load ) to prevent N+1 queries

  • Add database indexes for all foreign keys and commonly filtered columns

  • Use counter_cache for association counts displayed in views

  • Use pagination for all list endpoints (Kaminari or Pagy)

  • Use fragment caching (cache @record do ) for expensive view rendering

  • Use background jobs (ActiveJob + Sidekiq) for slow operations

  • Use find_each instead of each when iterating over large datasets

MVC Conventions

Models

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.

Controllers

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

Views and Partials

  • Use _form.html.erb partial shared between new and edit

  • Use collection rendering: render @posts (auto-maps to _post.html.erb )

  • Use locals: render partial: "post", locals: { post: @post }

  • Use content_for :title for page-specific titles

  • Use helpers for complex view logic (not inline ERB conditionals)

Routing

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:

  • Use resources for standard CRUD (generates 7 RESTful routes)

  • Use only: or except: to limit generated routes

  • Use member for actions on a specific record, collection for actions on the set

  • Nest resources only one level deep; use shallow: true for deeper nesting

  • Namespace admin and API routes separately

Migrations

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

  • Use t.references for foreign keys (adds index automatically)

  • Use t.timestamps for created_at and updated_at

  • Add composite indexes for common query patterns

  • Use comments_count with counter_cache: true on the association

Service Objects

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

  • Use self.call(...) class method pattern for clean invocation

  • Wrap multi-step operations in ActiveRecord::Base.transaction

  • Return a result object (success/failure) instead of raising

  • Namespace services by domain: Posts::PublishService , Users::CreateService

Rails Commands

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

Testing

Minitest (Default)

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

Testing Standards

  • Use fixtures (default) or factory_bot for test data

  • Test validations, associations, scopes, and instance methods on models

  • Test authentication, authorization, and response codes on controllers

  • Use system tests (Capybara) for critical user flows

  • Coverage target: >80% for models and services, >60% overall

  • Test names describe behavior: test "user cannot edit others' posts"

  • See references/patterns.md for controller and system test examples

Dependencies

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

Best Practices

Do

  • Follow RESTful conventions for routes and controllers

  • Use includes /preload on every association accessed in views

  • Extract business logic to service objects

  • Use scopes for all reusable query patterns

  • Use find_each for batch operations on large datasets

  • Use background jobs for email, notifications, and heavy processing

  • Use fragment caching for expensive view partials

  • Keep secrets in credentials or environment variables

Don't

  • Put business logic in controllers or views

  • Use params.permit! (mass-assignment vulnerability)

  • Use string interpolation in SQL queries

  • Skip database indexes on foreign keys

  • Use Model.all without pagination or limits

  • Modify migrations after they have been applied to production

  • Use raw /html_safe with user-provided data

  • Rely heavily on callbacks for business logic (use services)

Advanced Topics

For detailed code examples and advanced patterns, see:

  • references/patterns.md -- ActiveRecord advanced patterns, Hotwire/Turbo, Action Cable, ActiveJob, API mode, deployment, and testing strategies

External References

  • Rails Guides

  • Rails API Documentation

  • Hotwire Documentation

  • Rails Tutorial

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

actix-web

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

blazor

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi

No summary provided by upstream source.

Repository SourceNeeds Review