rails-workflow

Ruby on Rails framework workflow guidelines. Activate when working with Rails projects, Gemfile with rails, rake tasks, or Rails-specific patterns.

$ 安裝

git clone https://github.com/ilude/claude-code-config /tmp/claude-code-config && cp -r /tmp/claude-code-config/skills/rails-workflow ~/.claude/skills/claude-code-config

// tip: Run this command in your terminal to install the skill


name: rails-workflow description: Ruby on Rails framework workflow guidelines. Activate when working with Rails projects, Gemfile with rails, rake tasks, or Rails-specific patterns. location: user

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

Rails Workflow

Tool Grid

TaskToolCommand
LintStandardRB + standard-railsbundle exec standardrb
SecurityBrakemanbundle exec brakeman
TestRSpec Railsbundle exec rspec
ConsoleRailsbundle exec rails console
ServerRailsbundle exec rails server
RoutesRailsbundle exec rails routes

Rails 8.x Features

Built-in Authentication

bundle exec rails generate authentication

Creates User with password_digest, Session controller, and authentication concern. You SHOULD use built-in auth for new projects.

Solid Queue (Background Jobs)

Database-backed, no Redis required. Rails 8 default.

config.active_job.queue_adapter = :solid_queue

Solid Cache & Solid Cable

Database-backed caching and Action Cable adapter:

config.cache_store = :solid_cache_store  # production.rb
adapter: solid_cable  # cable.yml

Controller Patterns

Controllers MUST delegate business logic to service objects:

# GOOD - Thin controller
def create
  result = Orders::CreateService.call(order_params, current_user)
  result.success? ? redirect_to(result.order) : render(:new, status: :unprocessable_entity)
end

Strong Parameters

You MUST use strong parameters. NEVER use params.permit! in production:

def user_params
  params.require(:user).permit(:name, :email, address_attributes: [:street, :city])
end

Service Objects

Services MUST follow a consistent pattern:

module Orders
  class CreateService
    Result = Struct.new(:success?, :order, :errors, keyword_init: true)

    def self.call(...) = new(...).call

    def initialize(params, user)
      @params, @user = params, user
    end

    def call
      order = Order.new(@params.merge(user: @user, status: :pending))
      order.save ? Result.new(success?: true, order:) : Result.new(success?: false, order:, errors: order.errors)
    end
  end
end

Naming: CreateService, UpdateService, ProcessService, SyncService

Model Organization

Models SHOULD follow this order:

class User < ApplicationRecord
  # 1. Constants
  ROLES = %w[admin member guest].freeze

  # 2. Associations
  belongs_to :organization
  has_many :posts, dependent: :destroy

  # 3. Validations
  validates :email, presence: true, uniqueness: true
  validates :role, inclusion: { in: ROLES }

  # 4. Callbacks (use sparingly)
  after_create :send_welcome_email

  # 5. Scopes
  scope :active, -> { where(active: true) }
  scope :admins, -> { where(role: "admin") }

  # 6. Class methods
  # 7. Instance methods
end

For complex queries, extract to query objects in app/queries/.

Security

Brakeman

You MUST run Brakeman before deployment. All warnings MUST be resolved:

bundle exec brakeman --no-pager

Common Patterns

# SQL injection prevention - use parameterized queries
User.where("email = ?", params[:email])  # GOOD
User.where("email = '#{params[:email]}'")  # BAD

# XSS - Rails auto-escapes. Use sanitize for HTML content
<%= sanitize @user.bio %>

Background Jobs

class ProcessOrderJob < ApplicationJob
  queue_as :default
  retry_on StandardError, wait: :polynomially_longer, attempts: 5
  discard_on ActiveRecord::RecordNotFound

  def perform(order_id)
    Orders::ProcessService.call(Order.find(order_id))
  end
end

Action Cable

class NotificationsChannel < ApplicationCable::Channel
  def subscribed
    stream_for current_user
  end
end

# Broadcasting
NotificationsChannel.broadcast_to(user, { type: "new_message", content: message.body })

View Components

class ButtonComponent < ViewComponent::Base
  def initialize(text:, variant: :primary)
    @text, @variant = text, variant
  end

  def call
    tag.button(@text, class: "btn btn-#{@variant}")
  end
end

Hotwire / Stimulus

Turbo Frames

<%= turbo_frame_tag "user_stats", src: user_stats_path, loading: :lazy %>

Turbo Streams

respond_to do |format|
  format.turbo_stream
  format.html { redirect_to @post }
end
<%= turbo_stream.prepend "comments", @comment %>

Stimulus Controllers

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["input", "output"]

  validate() {
    this.outputTarget.textContent = this.inputTarget.value.length > 0 ? "Valid" : "Required"
  }
}
<div data-controller="form">
  <input data-form-target="input" data-action="input->form#validate">
  <span data-form-target="output"></span>
</div>

Testing

Model Specs

RSpec.describe User, type: :model do
  it { is_expected.to validate_presence_of(:email) }
  it { is_expected.to have_many(:posts).dependent(:destroy) }
end

Request Specs

RSpec.describe "Posts", type: :request do
  it "creates a post" do
    sign_in(user)
    expect { post posts_path, params: { post: valid_attributes } }.to change(Post, :count).by(1)
  end
end

Database

You MUST use reversible migrations:

class AddStatusToOrders < ActiveRecord::Migration[8.0]
  def change
    add_column :orders, :status, :string, default: "pending", null: false
    add_index :orders, :status
  end
end

File Structure

app/
  channels/
  components/
  controllers/
  jobs/
  models/
  queries/
  services/
  views/
config/
  solid_queue.yml
  cable.yml
spec/
  factories/
  models/
  requests/
  services/