jr-rails-classic について
jr-rails-classicは、37signalsの慣習に基づいてRails開発者を支援するコーディングスタイルスキルです。リッチなドメインモデル、RESTリソース設計、fixturesを使ったMinitestを重視します。
主な機能
- 01ジェネレーターを優先する開発手順
- 02リッチなドメインモデルと薄いコントローラー
- 03状態変更をサブリソースに割り当て
- 04Minitestとfixturesによるテスト
- 05データベース制約とジョブの設計指針
活用シーン
- 0137signalsの慣習に沿ったRails機能の実装
- 02RESTコントローラーとデータモデルの設計
- 03Railsアプリのテストとバックグラウンドジョブの設計
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ julianrubisch/ skills --skill jr-rails-classicインストール後、Claude Code で/jr-rails-classicを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
reference/agentic-worktrees.mdanti-patterns.mdcoding-classic.mdcoding-phlex.mddevops.mdpatterns.mdreek.ymlreference/coding/api.mdreference/refactorings/000-template.md001-extract-scope-from-controller.md002-replace-conditional-with-polymorphism.md003-replace-conditional-with-null-object.md004-introduce-form-object.md005-replace-subclasses-with-strategies.md006-replace-mixin-with-composition.md007-extract-validator.md008-introduce-parameter-object.md009-replace-callback-with-method.md010-refactor-service-object-into-poro.mdextraction-signals.mdreference/shared/architecture.mdauthorization.mdcallbacks.mdcomponents.mdconcerns.mdconfiguration.mdcurrent_attributes.mdgraphql.mdhotwire.mdinstrumentation.md詳細
Rails クラシックコーディングスタイル
37signals の規約に従って Rails アプリケーションのコードを書きます。リッチなドメインモデル、CRUD コントローラー、すべてデータベースベース、フィクスチャを使った Minitest。
コアワークフロー
- まずジェネレーターを使う —
rails g model、rails g controller、rails g migration。ジェネレーターは正しいファイル構造、テストスタブ、 ルートエントリを一度に生成します。 - 実装順序 — モデル → コントローラー → ビュー → テスト。
- Ship, Validate, Refine — プロトタイプから本番へ、実際の利用から学ぶ。
- Let it crash — bang メソッドを使い、RecordInvalid は Rails に 422 で処理させる。
ガードレール
- サービスオブジェクトは使わない —
app/models/以下に名前空間を切ったドメインモデルを使う - カスタムコントローラーアクションは作らない — 代わりに新しいリソースを作る(REST マッピング)
- RSpec なし、factory_bot なし — Minitest とフィクスチャのみ
- Redis なし — Solid Queue、Solid Cache、Solid Cable
- ビューで
strftimeは使わない — initializer でカスタム DATE_FORMATS を定義 - コールバックは派生データと非同期ディスパッチのみ — ビジネスロジックには決して使わない
- 強い保証にはモデルのバリデーションよりデータベース制約
- ジョブにはオブジェクトではなく ID を渡す
規約クイックリファレンス
命名
- 状態変化には動詞:
card.close、board.publish - レコードの存在に基づく述語:
card.closed?、card.golden? - Concern は形容詞:
Closeable、Publishable、Watchable - コントローラー は名詞:
Cards::ClosuresController - スコープ はビジネス用語:
scope :active、scope :chronologically
REST マッピング
カスタムアクションはなし。サブリソースを作成します:
| アクション | ルート |
|---|---|
| カードを閉じる | POST /cards/:id/closure |
| カードをアーカイブする | POST /cards/:id/archival |
| ボードをウォッチする | POST /boards/:id/watching |
モデル — 状態をレコードとして
ブール値の代わりに、状態レコードを作成します(タイムスタンプと作成者が自動で付きます):
class Card::Closure < ApplicationRecord
belongs_to :card
belongs_to :creator, class_name: "User"
end
module Closeable
extend ActiveSupport::Concern
included do
has_one :closure, dependent: :destroy
end
def closed? = closure.present?
def close(creator: Current.user) = create_closure!(creator: creator)
def reopen = closure&.destroy
end
# Querying
Card.joins(:closure) # closed
Card.where.missing(:closure) # open
Concern — 横断的な振る舞い
自己完結型(アソシエーション + スコープ + メソッド)。50〜150 行。組織ではなく 機能にちなんで命名します。
class Card < ApplicationRecord
include Assignable, Closeable, Golden, Watchable, Searchable
end
モデル名前空間下の PORO
Concern に収まらないビジネスロジック:
# app/models/event/description.rb
class Event::Description
def initialize(event) = @event = event
def to_s = # ...
end
バリデーション
モデルには最小限(データ整合性)、フォームオブジェクトには文脈依存(UI フロー):
# Model — minimal
class User < ApplicationRecord
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
normalizes :email, with: ->(e) { e.strip.downcase }
end
# Migration — hard constraints
add_index :users, :email, unique: true
add_foreign_key :cards, :boards
アソシエーション設計
| 使わない | 優先する | 使うとき |
|---|---|---|
has_and_belongs_to_many | has_many :through | ジョインモデルに属性やコールバックが必要な場合 |
| 1:N | M:N (has_many :through) | 関係が拡大する可能性がある場合 |
polymorphic: true | delegated_type | 異なるスキーマを持つ型安全なバリアント |
has_many/has_one には常に :dependent を設定します。Rails が推論できない場合は
:inverse_of を使います。
Rails 7.1+ のパターン
# Delegated types
class Message < ApplicationRecord
delegated_type :messageable, types: %w[Comment Reply Announcement]
end
# store_accessor for JSON/JSONB
class User < ApplicationRecord
store_accessor :settings, :theme, :notifications_enabled
end
データベース
# UUIDs (UUIDv7 — time-sortable)
create_table :cards, id: :uuid do |t|
t.references :board, type: :uuid, foreign_key: true
end
# Counter caches
class Comment < ApplicationRecord
belongs_to :card, counter_cache: true
end
# Default values with Current
class Card < ApplicationRecord
belongs_to :creator, class_name: "User", default: -> { Current.user }
end
コントローラー
薄く。共有の振る舞いには Concern を使います:
module CardScoped
extend ActiveSupport::Concern
included { before_action :set_card }
private
def set_card
@card = Card.find(params[:card_id])
@board = @card.board
end
def render_card_replacement
render turbo_stream: turbo_stream.replace(@card)
end
end
class Cards::ClosuresController < ApplicationController
include CardScoped
def create = @card.close && render_card_replacement
def destroy = @card.reopen && render_card_replacement
end
Current Attributes
class Current < ActiveSupport::CurrentAttributes
attribute :session, :user, :account, :request_id
delegate :user, to: :session, allow_nil: true
end
バックグラウンドジョブ
薄いラッパー — ロジックはモデルに置きます:
module Watchable
def notify_watchers_later = NotifyWatchersJob.perform_later(self)
def notify_watchers
watchers.each { |w| WatcherMailer.notification(w, self).deliver_later }
end
end
class NotifyWatchersJob < ApplicationJob
def perform(card) = card.notify_watchers
end
ビューヘルパー
日付フォーマットは initializer 経由(ビューで strftime は決して使わない):
# config/initializers/date_formats.rb
Date::DATE_FORMATS[:default] = "%d.%m.%Y"
Time::DATE_FORMATS[:default] = "%d.%m.%Y %H:%M"
Time::DATE_FORMATS[:time_only] = "%H:%M"
ナビゲーションのアクティブ状態には active_link_to gem を使います。
テスト
- Minitest と フィクスチャ、コントローラーには統合テスト
- 実装ではなく観測可能な振る舞いをテストする
- 実際にテストできるものはモックしない
- 外部 API には VCR
推奨スタック
| 関心事 | Gem |
|---|---|
| フロントエンド | turbo-rails、stimulus-rails、importmap-rails |
| アセット | propshaft |
| ジョブ | Solid Queue |
| キャッシュ/Cable | Solid Cache、Solid Cable |
| 認可 | Pundit |
| デプロイ | Kamal + Thruster |
Gem の選定
- 素の Rails でできるか?
- アプリのコアな関心事か? はいならコードを自分で持つ
- インフラを追加するか? データベースベースの代替がある
- 複雑さに見合う価値があるか?
- 信頼できる人が作ったものか?
詳細リファレンスファイル
タスクでより深いガイダンスが必要な場合は、必要に応じてこれらを参照してください。
- デザインパターン(フォームオブジェクト、クエリオブジェクト、ストラテジーなど): reference/patterns.md
- アンチパターンとコードスメル: reference/anti-patterns.md、reference/smells.md
- リファクタリングレシピ: reference/refactorings/(002~010)
- テストガイド: reference/shared/testing.md
- Hotwire(Turbo + Stimulus): reference/shared/hotwire.md
- バックグラウンドジョブ + Continuations: reference/shared/jobs.md
- ステートマシン(AASM): reference/shared/state_machines.md
- コールバックのスコアリング + 抽出: reference/shared/callbacks.md
- 認可(Pundit): reference/shared/authorization.md
- 通知(Noticed): reference/shared/notifications.md
- インストルメンテーション / EventReporter: reference/shared/instrumentation.md
- コンポーネント(Phlex): reference/shared/components.md
- シリアライザー(API JSON): reference/shared/serializers.md
- アーキテクチャレイヤー: reference/shared/architecture.md
- Gem のおすすめ: reference/toolbelt.md
フロントエンドのパターン(Stimulus コントローラー、Turbo Frames/Streams)については、このスキルと併せて該当する hwc-* スキルを呼び出してください。