install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/TerminalSkills/skills/rails" ~/.claude/skills/comeonoliver-skillshub-rails && rm -rf "$T"
manifest:
skills/TerminalSkills/skills/rails/SKILL.mdsource content
Ruby on Rails
Rails is an opinionated full-stack framework that favors convention over configuration. It includes everything needed to build database-backed web apps: ORM, routing, views, mailers, jobs, and WebSocket support.
Installation
# Create new Rails app with PostgreSQL gem install rails rails new myapp --database=postgresql --css=tailwind cd myapp rails db:create
Project Structure
# Standard Rails project layout app/ ├── controllers/ # Request handlers ├── models/ # ActiveRecord models ├── views/ # ERB/HTML templates ├── channels/ # Action Cable channels ├── jobs/ # Background jobs ├── mailers/ # Email classes └── serializers/ # API serializers config/ ├── routes.rb # URL routing ├── database.yml # DB config └── environments/ # Per-env settings db/ ├── migrate/ # Schema migrations ├── schema.rb # Current schema └── seeds.rb # Seed data
Models
# app/models/article.rb — ActiveRecord model class Article < ApplicationRecord belongs_to :author, class_name: "User" has_many :comments, dependent: :destroy validates :title, presence: true, length: { maximum: 200 } validates :slug, presence: true, uniqueness: true validates :body, presence: true scope :published, -> { where(published: true) } scope :recent, -> { order(created_at: :desc) } before_validation :generate_slug, on: :create private def generate_slug self.slug = title&.parameterize end end
Migrations
# db/migrate/20240101000000_create_articles.rb — database migration class CreateArticles < ActiveRecord::Migration[7.1] def change create_table :articles do |t| t.string :title, null: false, limit: 200 t.string :slug, null: false, index: { unique: true } t.text :body, null: false t.references :author, null: false, foreign_key: { to_table: :users } t.boolean :published, default: false t.timestamps end end end
Controllers
# app/controllers/articles_controller.rb — RESTful controller class ArticlesController < ApplicationController before_action :authenticate_user!, except: [:index, :show] before_action :set_article, only: [:show, :update, :destroy] def index @articles = Article.published.recent .includes(:author) .page(params[:page]) .per(20) render json: @articles, include: [:author] end def show render json: @article 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 def destroy @article.destroy head :no_content end private def set_article @article = Article.find(params[:id]) end def article_params params.require(:article).permit(:title, :body) end end
Routes
# config/routes.rb — URL routing Rails.application.routes.draw do root "pages#home" resources :articles, only: [:index, :show, :create, :update, :destroy] namespace :api do namespace :v1 do resources :articles, only: [:index, :show] end end mount ActionCable.server => "/cable" end
Views
<!-- app/views/articles/index.html.erb — list view template --> <h1>Articles</h1> <% @articles.each do |article| %> <article> <h2><%= link_to article.title, article_path(article) %></h2> <p>By <%= article.author.name %> — <%= time_ago_in_words(article.created_at) %> ago</p> <p><%= truncate(article.body, length: 200) %></p> </article> <% end %> <%= paginate @articles %>
Action Cable (WebSockets)
# app/channels/chat_channel.rb — WebSocket channel class ChatChannel < ApplicationCable::Channel def subscribed stream_from "chat_#{params[:room_id]}" end def receive(data) ActionCable.server.broadcast("chat_#{params[:room_id]}", { user: current_user.name, message: data["message"] }) end end
Background Jobs
# app/jobs/send_notification_job.rb — Active Job class SendNotificationJob < ApplicationJob queue_as :default retry_on StandardError, wait: :polynomially_longer, attempts: 5 def perform(user, message) NotificationService.send(user, message) end end # Enqueue: SendNotificationJob.perform_later(user, "Hello!")
Testing
# test/models/article_test.rb — model test require "test_helper" class ArticleTest < ActiveSupport::TestCase test "validates title presence" do article = Article.new(body: "content", author: users(:one)) assert_not article.valid? assert_includes article.errors[:title], "can't be blank" end test "published scope" do assert_includes Article.published, articles(:published_one) assert_not_includes Article.published, articles(:draft_one) end end
Key Commands
# Common Rails commands rails generate model Article title:string body:text author:references rails generate controller Articles index show create rails db:migrate rails db:seed rails console # Interactive REPL rails routes # Show all routes rails test # Run tests
Key Patterns
- Use
(strong_parameters
) to whitelist input — never trust user dataparams.permit - Use
/includes
to prevent N+1 querieseager_load - Use scopes for reusable query logic on models
- Use
for authentication and resource loadingbefore_action - Use Active Job + Sidekiq/GoodJob for background processing
- Use
for secrets — never commit themrails credentials:edit