This skill should be used when the user asks about "Rails conventions", "Rails best practices", "Rails file structure", "MVC patterns", "naming conventions", "Rails Way", "convention over configuration", "where to put code", "Rails architecture", or needs guidance on organizing Rails application code following established conventions.
Provides guidance on Rails conventions, file structure, MVC patterns, and best practices.
npx claudepluginhub bastos/ruby-plugin-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/advanced-patterns.mdreferences/code-organization.mdComprehensive guide to Ruby on Rails conventions, the Rails Way philosophy, and best practices for organizing Rails 7+ applications.
Rails follows "Convention over Configuration" - sensible defaults reduce decision fatigue and boilerplate. Learn the conventions to write less code and collaborate effectively.
app/
├── controllers/ # Handle HTTP requests
│ ├── concerns/ # Shared controller modules
│ └── application_controller.rb
├── models/ # Business logic and data
│ ├── concerns/ # Shared model modules
│ └── application_record.rb
├── views/ # Templates
│ ├── layouts/ # Page wrappers
│ └── shared/ # Partials used across views
├── helpers/ # View helper methods
├── mailers/ # Email logic
├── jobs/ # Background jobs (ActiveJob)
├── channels/ # ActionCable WebSocket channels
└── javascript/ # Hotwire/Stimulus (import maps)
└── controllers/ # Stimulus controllers
config/
├── routes.rb # URL routing
├── database.yml # Database configuration
├── credentials.yml.enc # Encrypted secrets
└── initializers/ # Startup configuration
db/
├── migrate/ # Database migrations
├── schema.rb # Current schema (auto-generated)
└── seeds.rb # Seed data
lib/
├── tasks/ # Custom Rake tasks
└── [custom modules] # Non-Rails-specific code
test/ # Tests (Minitest)
| Convention | Example | Notes |
|---|---|---|
| Class name | User, BlogPost | Singular, CamelCase |
| File name | user.rb, blog_post.rb | Singular, snake_case |
| Table name | users, blog_posts | Plural, snake_case |
| Foreign key | user_id, blog_post_id | Singular + _id |
| Convention | Example | Notes |
|---|---|---|
| Class name | UsersController | Plural + Controller |
| File name | users_controller.rb | Plural, snake_case |
| Actions | index, show, new, create, edit, update, destroy | RESTful |
| Convention | Example |
|---|---|
| Directory | app/views/users/ |
| Template | index.html.erb, show.html.erb |
| Partial | _user.html.erb, _form.html.erb |
| Layout | application.html.erb |
Standard resource routes map to controller actions:
# config/routes.rb
resources :articles do
resources :comments, only: [:create, :destroy]
end
| HTTP Verb | Path | Controller#Action | Purpose |
|---|---|---|---|
| GET | /articles | articles#index | List all |
| GET | /articles/new | articles#new | New form |
| POST | /articles | articles#create | Create |
| GET | /articles/:id | articles#show | Show one |
| GET | /articles/:id/edit | articles#edit | Edit form |
| PATCH/PUT | /articles/:id | articles#update | Update |
| DELETE | /articles/:id | articles#destroy | Delete |
Add member and collection routes when needed:
resources :articles do
member do
post :publish # POST /articles/:id/publish
end
collection do
get :drafts # GET /articles/drafts
end
end
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
def index
@articles = Article.all
end
def show; end
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article, notice: "Article created."
else
render :new, status: :unprocessable_entity
end
end
def edit; end
def update
if @article.update(article_params)
redirect_to @article, notice: "Article updated."
else
render :edit, status: :unprocessable_entity
end
end
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, :published)
end
end
Always whitelist permitted parameters:
def article_params
params.require(:article).permit(:title, :body, :published, tag_ids: [])
end
class Article < ApplicationRecord
# Constants first
STATUSES = %w[draft published archived].freeze
# Associations
belongs_to :author, class_name: "User"
has_many :comments, dependent: :destroy
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
# Validations
validates :title, presence: true, length: { maximum: 255 }
validates :body, presence: true
validates :status, inclusion: { in: STATUSES }
# Callbacks (use sparingly)
before_validation :normalize_title
after_create :notify_subscribers
# Scopes
scope :published, -> { where(status: "published") }
scope :recent, -> { order(created_at: :desc) }
scope :by_author, ->(author) { where(author: author) }
# Class methods
def self.search(query)
where("title ILIKE ?", "%#{query}%")
end
# Instance methods
def publish!
update!(status: "published", published_at: Time.current)
end
private
def normalize_title
self.title = title&.strip&.titleize
end
def notify_subscribers
ArticleNotificationJob.perform_later(self)
end
end
Extract shared behavior into concerns:
# app/models/concerns/publishable.rb
module Publishable
extend ActiveSupport::Concern
included do
scope :published, -> { where.not(published_at: nil) }
scope :draft, -> { where(published_at: nil) }
end
def published?
published_at.present?
end
def publish!
update!(published_at: Time.current)
end
end
# app/models/article.rb
class Article < ApplicationRecord
include Publishable
end
For complex business logic, use service objects in app/services/:
# app/services/article_publisher.rb
class ArticlePublisher
def initialize(article, user:)
@article = article
@user = user
end
def call
return failure("Not authorized") unless @user.can_publish?(@article)
return failure("Already published") if @article.published?
ActiveRecord::Base.transaction do
@article.publish!
notify_subscribers
track_analytics
end
success(@article)
rescue StandardError => e
failure(e.message)
end
private
def notify_subscribers
# notification logic
end
def track_analytics
# analytics logic
end
def success(result)
OpenStruct.new(success?: true, result: result)
end
def failure(error)
OpenStruct.new(success?: false, error: error)
end
end
| Code Type | Location | Example |
|---|---|---|
| Business logic | Models or Services | Validation, calculations |
| HTTP handling | Controllers | Params, redirects, rendering |
| View formatting | Helpers or View Components | Date formatting, HTML helpers |
| Background work | Jobs | Email sending, API calls |
| Database queries | Models (scopes) | Complex queries |
| External APIs | lib/ or Services | API wrappers |
| Shared behavior | Concerns | Reusable modules |
references/advanced-patterns.md - Service objects, query objects, form objectsreferences/code-organization.md - When models get too bigActivates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.