mcpField
R
ruby-on-rails-best-practices

ruby-on-rails-best-practices

github.com/sergiodxa開発92280 回インストール38 週間前に更新
リポジトリ

ruby-on-rails-best-practices について

ruby-on-rails-best-practicesは、Basecampの本番Railsアプリからまとめた設計パターンとコーディング規約で、Railsプロジェクトの開発やレビューに携わるエンジニア向けです。モデル構成からリアルタイム機能、テストまで6分野・16項目を扱い、Railsの慣例に沿って追加レイヤーを抑える方針です。

主な機能

  1. 016分野にわたる16のルール
  2. 02モデルとconcernの配置規約
  3. 03ジョブとコントローラー設計の指針
  4. 04Currentとコールバックのパターン
  5. 05Turboのリアルタイム機能とテストの指針

活用シーン

  1. 01Railsのモデルとconcernの配置を決める
  2. 02コントローラーとバックグラウンドジョブの責務を見直す
  3. 03Turboのリアルタイム機能でブロードキャスト方式を選ぶ
  4. 04Railsプロジェクトのテストとコード構成を統一する

インストール

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

$npx skills add https://github.com/sergiodxa/agent-skills --skill ruby-on-rails-best-practices

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

rules/
association-extensions.md
callbacks-patterns.md
code-style.md
concern-naming.md
current-attributes.md
current-in-other-contexts.md
fixtures-testing.md
model-scoped-concerns.md
nested-service-objects.md
paired-async-methods.md
resource-controllers.md
scoping-concerns.md
template-method-concerns.md
thin-controllers.md
thin-jobs.md
turbo-broadcasts.md

詳細

Ruby on Rails のベストプラクティス

Basecamp の本番環境の Rails アプリケーション(Fizzy と Campfire)から抽出したアーキテクチャパターンとコーディング規約です。Basecamp 独自の工夫を加えた「Rails Way」に沿って、コードの整理、保守性、規約の遵守に焦点を当てた 6 つのカテゴリ、16 のルールをまとめています。

適用する場面

次のような場面で、これらのガイドラインを参照してください。

  • モデル、コンサーン、コントローラーを整理するとき
  • バックグラウンドジョブを作成するとき
  • Turbo Streams でリアルタイム機能を実装するとき
  • コードの配置場所を決めるとき
  • Rails アプリケーションのテストを書くとき
  • Rails コードのアーキテクチャの一貫性をレビューするとき

ルールの概要

モデルの整理(HIGH)
model-scoped-concerns - @rules/model-scoped-concerns.md

モデル固有のコンサーンは app/models/concerns/ ではなく、app/models/model_name/ に配置します。

# Directory structure
app/models/
├── card.rb
├── card/
│   ├── closeable.rb     # Card::Closeable
│   ├── searchable.rb    # Card::Searchable
│   └── assignable.rb    # Card::Assignable

# app/models/card.rb
class Card < ApplicationRecord
  include Closeable, Searchable, Assignable
  # Ruby resolves from Card:: namespace first
end
concern-naming - @rules/concern-naming.md

振る舞いを表すコンサーンには -able の接尾辞を使い、機能を表すコンサーンには名詞を使います。

# Behaviors: -able suffix
module Card::Closeable     # Can be closed
module Card::Searchable    # Can be searched
module User::Mentionable   # Can be mentioned

# Features: nouns
module User::Avatar        # Has avatar
module User::Role          # Has role
module Card::Mentions      # Has @mentions
template-method-concerns - @rules/template-method-concerns.md

カスタマイズ可能な振る舞いには、共有コンサーンでテンプレートメソッドを使います。

# app/models/concerns/searchable.rb (shared)
module Searchable
  def search_title
    raise NotImplementedError
  end
end

# app/models/card/searchable.rb (model-specific)
module Card::Searchable
  include ::Searchable

  def search_title
    title  # Implement the hook
  end
end
バックグラウンドジョブ(HIGH)
paired-async-methods - @rules/paired-async-methods.md

同期メソッドには、ジョブをエンキューする _later バリアントを組み合わせます。

# app/models/card/readable.rb
def remove_inaccessible_notifications
  # Sync implementation
end

private
  def remove_inaccessible_notifications_later
    Card::RemoveInaccessibleNotificationsJob.perform_later(self)
  end

# app/jobs/card/remove_inaccessible_notifications_job.rb
class Card::RemoveInaccessibleNotificationsJob < ApplicationJob
  def perform(card)
    card.remove_inaccessible_notifications
  end
end
thin-jobs - @rules/thin-jobs.md

ジョブからモデルメソッドを呼び出します。ロジックはすべてモデルに記述します。

# Bad: Logic in job
class ProcessOrderJob < ApplicationJob
  def perform(order)
    order.items.each { |i| i.product.decrement!(:stock) }
    order.update!(status: :processing)
  end
end

# Good: Job delegates to model
class ProcessOrderJob < ApplicationJob
  def perform(order)
    order.process  # Single method call
  end
end
コントローラー(HIGH)
resource-controllers - @rules/resource-controllers.md

状態変更にはカスタムアクションではなく、リソースコントローラーを作成します。

# Bad: Custom actions
resources :cards do
  post :close
  post :reopen
end

# Good: Resource controllers
resources :cards do
  resource :closure, only: [:create, :destroy]
end

# app/controllers/cards/closures_controller.rb
class Cards::ClosuresController < ApplicationController
  def create
    @card.close
  end

  def destroy
    @card.reopen
  end
end
scoping-concerns - @rules/scoping-concerns.md

ネストされたリソースのセットアップには、CardScoped のようなコンサーンを使います。

# app/controllers/concerns/card_scoped.rb
module CardScoped
  extend ActiveSupport::Concern

  included do
    before_action :set_card
  end

  private
    def set_card
      @card = Current.user.accessible_cards.find_by!(number: params[:card_id])
    end
end

# Usage
class Cards::CommentsController < ApplicationController
  include CardScoped
end
thin-controllers - @rules/thin-controllers.md

コントローラーから、機能豊富なモデル API を直接呼び出します。サービスオブジェクトは使いません。

# Good: Thin controller, rich model
class Cards::ClosuresController < ApplicationController
  include CardScoped

  def create
    @card.close  # All logic in model
  end
end
リクエストコンテキスト(MEDIUM)
current-attributes - @rules/current-attributes.md

リクエストスコープのデータには、連鎖するセッターを備えた Current を使います。

class Current < ActiveSupport::CurrentAttributes
  attribute :session, :user, :account

  def session=(value)
    super(value)
    self.user = session&.user
  end
end
current-in-other-contexts - @rules/current-in-other-contexts.md

Current が自動的に設定されるのは Web リクエスト内だけです。ジョブ、メーラー、チャネルでは明示的なセットアップが必要です。

# Jobs: extend ActiveJob to serialize/restore Current.account
# Mailers from jobs: wrap in Current.with_account { mailer.deliver }
# Channels: set Current in Connection#connect
アソシエーションとコールバック(MEDIUM)
association-extensions - @rules/association-extensions.md

必要なコンテキストに応じて、アソシエーション拡張とモデルクラスメソッドを使い分けます。

# Use extension when you need parent context (proxy_association.owner)
has_many :accesses do
  def grant_to(users)
    board = proxy_association.owner
    Access.insert_all(users.map { |u| { user_id: u.id, board_id: board.id, account_id: board.account_id } })
  end
end

# Use class method when operation is independent
class Access
  def self.grant(board:, users:)
    insert_all(users.map { |u| { user_id: u.id, board_id: board.id } })
  end
end
callbacks-patterns - @rules/callbacks-patterns.md

ジョブには after_commit を使い、単純な処理にはインラインのラムダを使います。

# Jobs: after_commit
after_create_commit :notify_recipients_later

# Simple ops: inline lambda
after_save -> { board.touch }, if: :published?

# Conditional: remember and check pattern
before_update :remember_changes
after_update_commit :process_changes, if: :should_process?
Turbo とリアルタイム機能(MEDIUM)
turbo-broadcasts - @rules/turbo-broadcasts.md

コールバックではなく、コントローラーから明示的にブロードキャストします。

# app/models/message/broadcasts.rb
module Message::Broadcasts
  def broadcast_create
    broadcast_append_to room, :messages, target: [room, :messages]
  end
end

# Controller calls explicitly
def create
  @message = @room.messages.create!(message_params)
  @message.broadcast_create
end
テスト(MEDIUM)
fixtures-testing - @rules/fixtures-testing.md

ファクトリーではなくフィクスチャを使います。テスト内でもコンサーンの構造を再現します。

# test/fixtures/cards.yml
logo:
  title: The logo isn't big enough
  board: writebook
  creator: david

# test/models/card/closeable_test.rb
class Card::CloseableTest < ActiveSupport::TestCase
  test "close creates closure" do
    card = cards(:logo)
    assert_difference -> { Closure.count } do
      card.close
    end
  end
end
コードの整理(LOW-MEDIUM)
nested-service-objects - @rules/nested-service-objects.md

サービスオブジェクトは app/services ではなく、モデルの名前空間の下に配置します。

# Good: app/models/card/activity_spike/detector.rb
class Card::ActivitySpike::Detector
  def initialize(card)
    @card = card
  end

  def detect
    # ...
  end
end
code-style - @rules/code-style.md

条件分岐は展開した形式を優先し、メソッドは呼び出し順に並べます。

# Expanded conditionals
def find_record
  if record = find_by_id(id)
    record
  else
    NullRecord.new
  end
end

# Method ordering: caller before callees
def process
  step_one
  step_two
end

private
  def step_one; end
  def step_two; end

方針

これらのパターンは、追加要素を最小限に抑えて Rails の規約を活用する「Vanilla Rails」の考え方を体現しています。

  1. 機能豊富なモデル、薄いコントローラー - ドメインロジックはモデルとコンサーンに記述します
  2. サービスオブジェクト層を設けない - コントローラーからモデルを直接呼び出します
  3. コードを近くに配置する - コンサーン、ジョブ、サービスは、それらが担うモデルの近くに配置します
  4. 暗黙的な処理より明示的な処理 - コールバックではなく、ブロードキャストを明示的に呼び出します
  5. 設定より規約 - 予測可能性を高めるため、命名パターンに従います

コメント