Ruby on Rails API開発用CLAUDE.mdテンプレート
なぜRails専用のCLAUDE.mdが必要か
Section titled “なぜRails専用のCLAUDE.mdが必要か”RailsはCoC(Convention over Configuration)を重視するフレームワークです。しかし「Railsらしい書き方」を知らないClaudeに任せると、Serviceオブジェクトを使わずにControllerに処理を詰め込んだり、N+1クエリを生んだりと、Railsのアンチパターンに陥りやすくなります。
また、RSpecの書き方にもプロジェクトごとのスタイルがあります。describeとcontextの使い分け、letとlet!の違いなど、事前に伝えておくことで一貫したテストコードが生成されます。
※CLAUDE.md: プロジェクトのルートに置くClaude Code専用の設定ファイル。プロジェクト固有のルールやコンテキストを記述する。※API: Application Programming Interface の略。外部サービスとプログラム間でデータをやりとりするための接続口。
テンプレート本体
Section titled “テンプレート本体”# プロジェクト概要
このプロジェクトは Ruby on Rails 7.1 (APIモード) + PostgreSQL で構築された [サービス名] のバックエンドAPIです。
## 技術スタック
- **フレームワーク**: Ruby on Rails 7.1 (APIモード)- **言語**: Ruby 3.2- **データベース**: PostgreSQL 15- **認証**: Devise + JWT (devise-jwt)- **バックグラウンドジョブ**: Sidekiq + Redis- **テスト**: RSpec 3.x- **リンター**: RuboCop (rubocop-rails, rubocop-rspec)- **シリアライザー**: jsonapi-serializer- **デプロイ**: Heroku / Render
## ディレクトリ構造app/ ├── controllers/ │ ├── api/ │ │ └── v1/ # バージョニングされたAPIコントローラ │ └── application_controller.rb ├── models/ # ActiveRecordモデル ├── services/ # ビジネスロジック(Service Objects) ├── serializers/ # APIレスポンスの整形 ├── jobs/ # Sidekiqジョブ └── lib/ # 汎用モジュール spec/ ├── models/ ├── controllers/api/v1/ ├── services/ └── factories/ # FactoryBot
## コーディング規約
### コントローラの責務
コントローラは「薄く」保つ。ビジネスロジックはServiceオブジェクトに移譲する。
```ruby# ✅ 推奨: コントローラは薄くclass Api::V1::OrdersController < ApplicationController def create result = Orders::CreateService.call( user: current_user, params: order_params )
if result.success? render json: OrderSerializer.new(result.order), status: :created else render json: { errors: result.errors }, status: :unprocessable_entity end end
private
def order_params params.require(:order).permit(:product_id, :quantity, :shipping_address) endend
# ❌ 非推奨: コントローラにビジネスロジックclass Api::V1::OrdersController < ApplicationController def create order = Order.new(order_params) order.user = current_user order.total_price = order.product.price * order.quantity if order.quantity > order.product.stock render json: { error: '在庫不足' }, status: :unprocessable_entity return end order.product.update!(stock: order.product.stock - order.quantity) order.save! # ... 長くなる一方 endendService Objects
Section titled “Service Objects”module Orders class CreateService Result = Struct.new(:success?, :order, :errors, keyword_init: true)
def self.call(**args) new(**args).call end
def initialize(user:, params:) @user = user @params = params end
def call ActiveRecord::Base.transaction do order = Order.new(@params.merge(user: @user))
unless order.product.stock >= order.quantity return Result.new(success?: false, errors: ['在庫が不足しています']) end
order.product.decrement!(:stock, order.quantity) order.save!
Result.new(success?: true, order: order) end rescue ActiveRecord::RecordInvalid => e Result.new(success?: false, errors: e.record.errors.full_messages) end endend- バリデーションは積極的に使う
- スコープで条件を名前付きにする
- コールバックは最小限(複雑なロジックはServiceへ)
class Product < ApplicationRecord # バリデーション validates :name, presence: true, length: { maximum: 100 } validates :price, numericality: { greater_than: 0 } validates :stock, numericality: { greater_than_or_equal_to: 0 }
# スコープ scope :available, -> { where('stock > 0') } scope :by_category, ->(category) { where(category: category) } scope :recently_updated, -> { order(updated_at: :desc) }
# アソシエーション belongs_to :category has_many :orders, dependent: :restrict_with_error has_many :reviews, dependent: :destroyendN+1クエリの防止
Section titled “N+1クエリの防止”# ✅ 推奨: eagerロードを使うdef index @orders = Order.includes(:user, :products).where(user: current_user) render json: OrderSerializer.new(@orders)end
# ❌ 非推奨: N+1クエリが発生def index @orders = Order.where(user: current_user) # serializer内でorder.user.nameにアクセスするたびにSQLが発行されるendデータベース
Section titled “データベース”- インデックスは必ずマイグレーションで追加する(外部キー・検索カラム)
add_indexとadd_foreign_keyを忘れない
# ✅ 適切なマイグレーション例class CreateOrders < ActiveRecord::Migration[7.1] def change create_table :orders do |t| t.references :user, null: false, foreign_key: true t.references :product, null: false, foreign_key: true t.integer :quantity, null: false t.integer :status, null: false, default: 0 t.timestamps end
add_index :orders, :status add_index :orders, :created_at endendAPIレスポンス形式
Section titled “APIレスポンス形式”JSON:API形式を採用する。
class OrderSerializer include JSONAPI::Serializer
attributes :id, :quantity, :status, :total_price, :created_at
belongs_to :user has_many :productsend※マイグレーション: データベースのスキーマ(テーブル構造)を安全に変更するための手順書。※インデックス: データベースの検索を高速化するための索引データ。適切に設定しないとクエリが遅くなる。※デプロイ: 開発したコードを本番サーバーに公開・適用すること。※JWT: JSON Web Token の略。ユーザー認証情報をJSON形式でエンコードしたトークン。ヘッダーに付けてAPIを呼び出す際に使う。
RSpecテストの書き方
Section titled “RSpecテストの書き方”ファイル構造
Section titled “ファイル構造”RSpec.describe Orders::CreateService do describe '.call' do subject(:result) { described_class.call(user: user, params: params) }
let(:user) { create(:user) } let(:product) { create(:product, stock: 10, price: 1000) } let(:params) { { product_id: product.id, quantity: 2 } }
context '正常系' do it '注文が作成される' do expect(result).to be_success expect(result.order).to be_persisted end
it '在庫が減る' do expect { result }.to change { product.reload.stock }.by(-2) end end
context '在庫不足の場合' do let(:params) { { product_id: product.id, quantity: 20 } }
it '失敗する' do expect(result).not_to be_success end
it 'エラーメッセージが含まれる' do expect(result.errors).to include('在庫が不足しています') end end endendFactoryBot
Section titled “FactoryBot”FactoryBot.define do factory :user do sequence(:email) { |n| "user#{n}@example.com" } password { 'password123' } name { Faker::Name.name } endendよく使うコマンド
Section titled “よく使うコマンド”# 開発サーバー起動bin/dev
# テスト実行bundle exec rspec
# 特定のファイルのみbundle exec rspec spec/services/orders/create_service_spec.rb
# リントbundle exec rubocop
# 自動修正bundle exec rubocop -a
# マイグレーションbin/rails db:migrate
# ルート確認bin/rails routes | grep orders
# Railsコンソールbin/rails consolebinding.pryはコミット前に必ず削除するputsデバッグも同様- Strong Parametersは必ず使う(
permit漏れに注意) findは見つからないとき例外を投げる。find_byはnilを返す。用途に応じて使い分ける
## 各セクションの説明
**Service Objects パターン**: RailsでMVCの「M」や「C」が肥大化するのを防ぐパターンです。Claudeにこのパターンを明示しておくと、コントローラを薄く保ったコードを生成してくれます。
**N+1クエリの防止**: Claudeは便利ですが、N+1クエリに気づかないことがあります。`includes`を使うルールを明記しておくことで、パフォーマンス問題を事前に防げます。
**RSpec規約**: `let` vs `let!`、`subject`の使い方、`context`のネストの深さなど、チーム内で統一したいスタイルを記述します。
## カスタマイズのポイント
1. **認証方法**: Devise以外(例: Doorkeeper, jwt-simple)を使う場合は差し替えてください。
2. **シリアライザー**: `jsonapi-serializer` 以外に `fast_jsonapi` や `active_model_serializers` を使う場合も同様です。
3. **Service Object の戻り値形式**: `Result = Struct.new(...)` の代わりに `dry-monads` の `Success/Failure` を使う場合は更新してください。
:::cautionRails 7以降は `Zeitwerk` によるオートロードが標準です。ファイル名とクラス名の対応(`Orders::CreateService` → `app/services/orders/create_service.rb`)を必ず守ってください。Claudeはこのルールを知っていますが、念のためCLAUDE.mdに明記しておくと安心です。:::※コミット: Gitでファイルの変更を履歴として記録する操作。