Skip to content

Ruby on Rails API開発用CLAUDE.mdテンプレート

RailsはCoC(Convention over Configuration)を重視するフレームワークです。しかし「Railsらしい書き方」を知らないClaudeに任せると、Serviceオブジェクトを使わずにControllerに処理を詰め込んだり、N+1クエリを生んだりと、Railsのアンチパターンに陥りやすくなります。

また、RSpecの書き方にもプロジェクトごとのスタイルがあります。describecontextの使い分け、letlet!の違いなど、事前に伝えておくことで一貫したテストコードが生成されます。

CLAUDE.md: プロジェクトのルートに置くClaude Code専用の設定ファイル。プロジェクト固有のルールやコンテキストを記述する。API: Application Programming Interface の略。外部サービスとプログラム間でデータをやりとりするための接続口。
# プロジェクト概要
このプロジェクトは 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)
end
end
# ❌ 非推奨: コントローラにビジネスロジック
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!
# ... 長くなる一方
end
end
app/services/orders/create_service.rb
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
end
end
  • バリデーションは積極的に使う
  • スコープで条件を名前付きにする
  • コールバックは最小限(複雑なロジックは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: :destroy
end
# ✅ 推奨: 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
  • インデックスは必ずマイグレーションで追加する(外部キー・検索カラム)
  • add_indexadd_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
end
end

JSON:API形式を採用する。

app/serializers/order_serializer.rb
class OrderSerializer
include JSONAPI::Serializer
attributes :id, :quantity, :status, :total_price, :created_at
belongs_to :user
has_many :products
end
マイグレーション: データベースのスキーマ(テーブル構造)を安全に変更するための手順書。インデックス: データベースの検索を高速化するための索引データ。適切に設定しないとクエリが遅くなる。デプロイ: 開発したコードを本番サーバーに公開・適用すること。JWT: JSON Web Token の略。ユーザー認証情報をJSON形式でエンコードしたトークン。ヘッダーに付けてAPIを呼び出す際に使う。
spec/services/orders/create_service_spec.rb
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
end
end
spec/factories/users.rb
FactoryBot.define do
factory :user do
sequence(:email) { |n| "user#{n}@example.com" }
password { 'password123' }
name { Faker::Name.name }
end
end
Terminal window
# 開発サーバー起動
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 console
  • binding.pry はコミット前に必ず削除する
  • puts デバッグも同様
  • Strong Parametersは必ず使う(permit漏れに注意)
  • find は見つからないとき例外を投げる。find_bynil を返す。用途に応じて使い分ける
## 各セクションの説明
**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` を使う場合は更新してください。
:::caution
Rails 7以降は `Zeitwerk` によるオートロードが標準です。ファイル名とクラス名の対応(`Orders::CreateService` → `app/services/orders/create_service.rb`)を必ず守ってください。Claudeはこのルールを知っていますが、念のためCLAUDE.mdに明記しておくと安心です。
:::
コミット: Gitでファイルの変更を履歴として記録する操作。