mcpField
R
rails-best-practices-core

rails-best-practices-core

github.com/marckohlbrugge開発73055 回インストール20 週間前に更新
リポジトリ

rails-best-practices-core について

日常的な Rails 開発の基本指針をまとめたスキルです。モデリング、ルーティング、認可、依存関係の選定、コードレビューを扱い、Rails の規約、明快なコード、データ安全性を重視します。

主な機能

  1. 01Rails の規約と組み込み機能を優先
  2. 02ドメインの振る舞いをモデルに配置
  3. 03レコードで状態と振る舞いを表現
  4. 04データベース制約で不変条件を保証
  5. 05REST ルーティングと認可のパターンを解説
  6. 06コードレビューの確認項目を提供

活用シーン

  1. 01Rails の変更に適した実装パターンを選定
  2. 02ドメインモデルとリソースルートを設計
  3. 03データ制約とテナント境界を確認
  4. 04依存関係の追加が必要か評価

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/marckohlbrugge/37signals-skills --skill rails-best-practices-core

インストール後、Claude Code で/rails-best-practices-coreを入力して読み込まれたか確認します。

詳細

Rails のベストプラクティス(コア)

Rails で作業する際のデフォルトの基準として使用してください。37signals のコードベース(Campfire、Fizzy)と DHH のレビューの傾向から抽出した内容です。

基本の方針

  • 気の利いた抽象化より、明快で明示的なコードを優先します。抽象化は価値を示す必要があります。必要とするバリエーションを 3 つ以上挙げられないなら、インライン化してください。
  • コントローラーは薄く保ち、ドメインの振る舞いはモデルに置きます。
  • gem を追加する前に、Rails の規約や組み込み機能を優先します。
  • 場当たり的なフラグではなく、ドメインの概念を使って状態と振る舞いをモデル化します。
  • テナントやユーザーのデータは、所有権の境界を通じてスコープします。
  • 強い不変条件にはデータベース制約を優先します。AR でバリデーションするのは、ユーザー向けのエラーメッセージが必要な場合に限ります。
  • インターフェースは小さく保ちます。どこからも使われていないパブリックメソッドは追加しません。
  • 高コストな読み取り時の組み立てより、書き込み時の計算を優先します(カウンターキャッシュ、委譲型、事前計算済みの集計、コールバックが不要な場合の dependent: :delete_all)。
  • モダンな Rails では、ストロングパラメーターに params.expect(...) を使用します。
  • クラッシュを許容します。bang メソッド(create!)を使い、例外は境界で処理します。bang なしの対応メソッドが存在する場合にのみ ! を使います。
  • 症状ではなく根本原因を修正します(競合に対するリトライロジックより enqueue_after_transaction_commit を使うなど)。
  • 振る舞いの変更と同じ PR でテストを追加します。

モデリングのパターン

  • ブール値ではなくレコードで状態を表します。 closed: boolean の代わりに、creator とタイムスタンプを持つ Closure レコードを作成します。誰がいつ行ったかを追加の作業なしで取得でき、スコープ設定も簡単です。
has_one :closure, dependent: :destroy
scope :closed, -> { joins(:closure) }
scope :open, -> { where.missing(:closure) }
  • 大きなモデルは、機能にちなんだ名前(Closeable、Watchable、Assignable)の concern に分割します。 各 concern は関連付け、スコープ、メソッドを備えた自己完結型にし、規模は約 50~150 行にします。ドメインの機能ごとに分割する場合は、モデルの名前空間内にネストしたモジュール(app/models/card/closeable.rb の Card::Closeable)を優先します。app/models/concerns/ は、本当に複数モデルで共有される振る舞いに限って使います。プライベートメソッドしか含まない concern は決して抽出しません。
  • PORO は app/services/ ではなく app/models/ に置きます。 表示用オブジェクト(Event::Description)、複雑な操作(SystemCommenter)、ビューコンテキストのまとまり(User::Filtering)などが該当します。これらはコントローラーではなく、モデルに隣接するものです。
  • デフォルト値にはラムダを使います。 belongs_to :creator, class_name: "User", default: -> { Current.user }、belongs_to :account, default: -> { board.account } のようにします。
  • リクエストコンテキストには Current 属性を使います(Current.user、Current.account)。セッターは連鎖させます(session を設定すると identity が解決され、さらに、そのアカウントの user が解決されます)。
  • コールバックはセットアップや後片付けに使い、ビジネスロジックには使いません。 コールバックの数は抑えます。
  • 積極的に活用する Rails のショートカット: normalizes(バリデーション前のデータクリーンアップ)、store_accessor(JSON カラム)、delegated_type(異種コレクション)、generates_token_for(有効期限付きの署名済みトークン)、enum :status, %w[drafted published].index_by(&:itself) による文字列 enum、after_save_commit、キャッシュ無効化のための touch: true チェーン、delegate。
  • 一括のドメイン操作には関連付けの拡張を使います。 has_many のプロキシに grant_to/revise を定義します。一括作成には insert_all を使い、コールバックのない結合テーブルには dependent: :delete_all を使います。
  • 人にわかりやすい URL: 生の ID/UUID を公開するのではなく、テナントごとの number を使うように to_param をオーバーライドします。

命名

  • 名前には時間をかけましょう。命名は設計です。CardClose より Closure、UserReference より Mention を選びます。
  • 肯定形の名前を使います。not_deleted ではなく active、not_hidden ではなく visible とします。
  • 役割を表す意味のある関連付け名を使います。belongs_to :user ではなく belongs_to :creator, class_name: "User" とします。
  • 技術的な表現よりドメイン駆動の表現を優先します。quota.over_limit? ではなく quota.depleted? とします。
  • ビジネスに即したスコープを使います。SQL 風の :without_pop ではなく、:active、:unassigned、:golden とします。
  • ドメイン用語を統一します。1 つの概念に対して source/resource/container を混在させません。

REST とルーティング

  • すべてを CRUD として扱います。動詞を名詞に変えます。Close → resource :closure(POST で閉じ、DELETE で再度開きます)、publish → resource :publication。カスタムのメンバーアクションは作りません。
  • 親ごとに状態が 1 つだけ存在する場合は単数形の resource を使います。ネストされたコントローラー(Cards::ClosuresController)をグループ化するには scope module: を使い、深い階層には浅いネストを使います。
  • リソースのスコープを設定するコントローラー concern(CardScoped は Current.user.accessible_cards.find_by!(...) を使って @card を設定します)を、共有の Turbo レンダリングヘルパーも含めてネストされたコントローラー間で共有します。
  • 親へのアンカー付きポリモーフィック URL を生成するには resolve "Comment" を使います。
  • respond_to を使い、HTML/Turbo/JSON に同じコントローラーで対応します。API 専用の名前空間は作りません。

認可

  • Pundit/CanCanCan は使いません。モデルにシンプルな述語メソッドを定義します(card.editable_by?(user)、user.can_administer_board?(board))。
  • コントローラーでチェックし(head :forbidden unless ...)、権限の意味はモデルで定義します。
  • 認証・認可の方針は、宣言的なコントローラーマクロで示します。allow_unauthenticated_access、ensure_can_administer。

依存関係

gem を追加する前に、次の点を確認してください。標準の Rails で実現できますか? リポジトリ内に 50~150 行書くほうが、依存関係を追加するよりシンプルではありませんか? 多くの場合、Devise、Pundit、ViewComponent、RSpec、FactoryBot、Redis(Solid Queue/Cache/Cable はデータベースを使います)、サービスオブジェクト、フォームオブジェクト、デコレーター、GraphQL、SPA フレームワーク、Tailwind は追加せずに済みます。

レビューの優先順位

  1. 正確性とデータの安全性。
  2. マルチテナント/セキュリティの境界。
  3. 保守性と可読性。
  4. パフォーマンス上のボトルネック。
  5. スタイルと仕上がり。

必ず指摘する項目

  • テナントを考慮するフローでの、スコープなしのレコード検索(Comment.find(params[:id]))。
  • 強い根拠のない新しい依存関係。
  • SQL で行うべきインメモリのフィルタリング/ソート(および、.pluck(:name) で済む場合の .map(&:name))。
  • 単純なモデルメソッドで済む処理を置き換えるサービスオブジェクト。
  • リソースとしてモデル化したほうが明確な場合の、RESTful ではないカスタムアクション。
  • 誰がいつ状態を変更したかを記録するレコードの代わりに、真偽値の状態カラムを使うこと。
  • HTTP キャッシュ(fresh_when/etag)を使うフォーム付きページ。CSRF トークンが古くなり、422 エラーが発生します。
  • 述語形式の API(StringInquirer/文字列 enum)が使える場合の、文字列によるステータスチェック(status == "x")。
  • 裏付けとなる一意インデックスがない validates :x, uniqueness: true。
  • 明示的な引数ではなく、暗黙のインスタンス変数に依存するヘルパー。
  • html_safe 文字列へのエスケープされていない補間。先にエスケープします。"<b>#{h(input)}</b>".html_safe
  • 2~3 個のケースに対するメタプログラミング。メソッドをそのまま記述してください。
  • private メソッドしかない concern。インライン化してください。

レビュー結果

  • 重大度の高い指摘から始めます。
  • 指摘ごとに、問題点、影響、具体的な修正方法を file:line の参照とともに記載します。
  • 率直かつ実用的に書きます。「これは過剰設計です」だけでも、十分な指摘になります。
  • 最後は Ship it または優先順位を付けた短い修正リストで締めくくります。

コメント