対象: Rails 8.x(8.1系)/ Ruby 3.4 以降 最終更新: 2026年10月


目次

  1. 基本思想
  2. プロジェクト構成
  3. モデル(Active Record)
  4. コントローラー
  5. ルーティング
  6. ビュー / フロントエンド
  7. ビジネスロジックの置き場所
  8. データベースとマイグレーション
  9. パフォーマンス
  10. バックグラウンドジョブ
  11. セキュリティ
  12. テスト
  13. 設定・秘密情報の管理
  14. コード品質・ツール
  15. デプロイ・運用
  16. アップグレード戦略
  17. チェックリスト

1. 基本思想

  • Convention over Configuration(設定より規約) 命名規則・ディレクトリ構成・REST などの Rails の規約に従う。規約から外れるほど、学習コストと保守コストが上がる。
  • DRY(Don’t Repeat Yourself) ただし早すぎる抽象化は避ける。「3回出てきたら共通化」程度が目安。
  • 「Rails Way」をまず使い切る 独自アーキテクチャや大量の gem を入れる前に、Rails 標準機能(concerns、コールバック、Active Job、Solid 系など)で解決できないか検討する。
  • 小さく、読みやすく メソッドは短く、クラスは単一責務に。命名で意図が伝わるコードを書く。

2. プロジェクト構成

app/
├── controllers/
│   └── concerns/
├── models/
│   └── concerns/
├── views/
├── jobs/
├── mailers/
├── helpers/
├── javascript/        # Stimulus コントローラーなど
├── services/          # (任意) 複数モデルにまたがる処理
├── forms/             # (任意) フォームオブジェクト
├── queries/           # (任意) 複雑なクエリオブジェクト
└── policies/          # (任意) 認可ロジック
  • app/ 配下に作ったディレクトリは自動でオートロード対象になる(Zeitwerk)。ファイル名とクラス名を必ず一致させる。
    • app/services/orders/checkout.rb → Orders::Checkout
  • lib/ には Rails に依存しない汎用コードを置く。Rails 7.1 以降は config.autoload_lib(ignore: %w[assets tasks]) で管理できる。
  • ディレクトリを増やしすぎない。必要になってから作る。

3. モデル(Active Record)

3.1 クラス内の記述順序を統一する

class User < ApplicationRecord
  # 1. include / extend
  include Searchable
 
  # 2. 定数
  ROLES = %w[admin member guest].freeze
 
  # 3. enum / 属性定義
  enum :status, { active: 0, suspended: 1 }, default: :active
 
  # 4. 関連
  belongs_to :organization
  has_many :posts, dependent: :destroy
 
  # 5. バリデーション
  validates :email, presence: true, uniqueness: { case_sensitive: false }
 
  # 6. コールバック
  before_validation :normalize_email
 
  # 7. スコープ
  scope :recent, -> { order(created_at: :desc) }
 
  # 8. クラスメソッド
  def self.find_by_login(login) = find_by(email: login.downcase)
 
  # 9. インスタンスメソッド
  def display_name = name.presence || email
 
  private
 
  # 10. private メソッド
  def normalize_email
    self.email = email&.strip&.downcase
  end
end

3.2 バリデーション

  • アプリ側のバリデーションと DB 制約を両方設ける。validates :email, uniqueness: true だけでは競合状態で重複が入りうるため、ユニークインデックスも張る。
  • presence には DB 側の null: false を対応させる。
  • 正規化は normalizes(Rails 7.1+)を使うとシンプル。
normalizes :email, with: ->(email) { email.strip.downcase }

3.3 コールバックは控えめに

  • コールバックは「そのモデル自身のデータ整合性」に関わる処理に限定する(正規化、デフォルト値の設定など)。
  • メール送信・外部API呼び出し・他モデルの更新などの副作用はコールバックに書かない。明示的にサービスやコントローラーから呼ぶ。
  • どうしても必要な場合は after_commit を使い、トランザクション確定後に実行する。

3.4 enum

  • 必ずハッシュで値を明示する(配列だと順序変更で値がずれる)。
  • Rails 7 以降のキーワード引数形式 enum :status, {...} を使う。
  • validate: true(Rails 7.1+)で不正値を例外ではなくバリデーションエラーにできる。

3.5 スコープとクエリ

  • 再利用するクエリ条件はスコープにまとめる。
  • 引数が多い、複雑な条件分岐がある場合はクラスメソッドかクエリオブジェクトにする。
  • default_scope は原則使わない(予期しない挙動の原因になる)。

3.6 Fat Model を避ける

  • モデルが肥大化したら、Concern・値オブジェクト・サービスオブジェクトなどに分割する。
  • Concern は「複数モデルで共有される振る舞い」か「明確に名前をつけられる責務の切り出し」に使う。単なるファイル分割目的では使わない。

3.7 関連付け

  • dependent: オプションを必ず検討する(:destroy, :delete_all, :nullify, :restrict_with_error)。
  • belongs_to は Rails 5 以降デフォルトで必須。任意の場合のみ optional: true を付ける。
  • 使われなくなった関連は deprecated: true(Rails 8.1+)で段階的に廃止できる。

4. コントローラー

4.1 Skinny Controller

コントローラーの責務は「リクエストを受け取り、適切な処理を呼び出し、レスポンスを返す」ことだけ。ビジネスロジックはモデルやサービスへ。

class PostsController < ApplicationController
  before_action :set_post, only: %i[show edit update destroy]
 
  def index
    @posts = Post.published.includes(:author).order(created_at: :desc)
  end
 
  def create
    @post = Current.user.posts.build(post_params)
 
    if @post.save
      redirect_to @post, notice: "投稿を作成しました"
    else
      render :new, status: :unprocessable_entity
    end
  end
 
  private
 
  def set_post
    @post = Post.find(params[:id])
  end
 
  def post_params
    params.expect(post: [:title, :body, :published])
  end
end

4.2 ポイント

  • Strong Parameters を必ず使う。Rails 8 以降は params.expect が推奨(不正な形式のパラメータで 400 を返し、500 エラーを防げる)。
  • アクションは基本の 7 つ(index show new create edit update destroy)に限定する。それ以外が必要なら新しいコントローラーを作ることを検討する(後述)。
  • バリデーション失敗時は status: :unprocessable_entity(422)を返す。Turbo で正しく動作させるために必須。
  • インスタンス変数はアクションごとに 1〜2 個程度に抑える。
  • 認可チェックは before_action やポリシーで一元化する。
  • リクエスト単位のグローバル状態(現在のユーザーなど)は Current(ActiveSupport::CurrentAttributes)を使う。

5. ルーティング

5.1 RESTful に設計する

# Good: カスタムアクションの代わりにリソースを切り出す
resources :posts do
  resource :publication, only: %i[create destroy]  # POST/DELETE /posts/:post_id/publication
  resources :comments, only: %i[create destroy], shallow: true
end
 
# Bad: 独自アクションを増やす
resources :posts do
  member do
    post :publish
    post :unpublish
  end
end
  • 「動詞」ではなく「名詞(リソース)」で考える。publish → Publication リソースの create。
  • ネストは 1 階層までが目安。深くなる場合は shallow: true を使う。
  • only: / except: で不要なルートを生成しない。
  • bin/rails routes で定期的にルートを確認する。

6. ビュー / フロントエンド

6.1 ビューにロジックを書かない

  • 複雑な条件分岐や計算はヘルパー、モデル、プレゼンター(デコレーター)、ViewComponent などへ移す。
  • ビューからのクエリ発行(N+1 の温床)を避ける。必要なデータはコントローラーで用意する。

6.2 パーシャルを活用する

<%# コレクションレンダリング(ループより高速) %>
<%= render @posts %>
 
<%# ローカル変数を明示的に渡す %>
<%= render "shared/card", title: @post.title %>
  • パーシャル内ではインスタンス変数を参照せず、ローカル変数で受け取る。
  • Rails 7.1+ では strict locals(<%# locals: (title:, body: nil) -%>)でパーシャルの引数を明示できる。

6.3 Hotwire(Turbo + Stimulus)

  • Rails 標準のフロントエンドは Hotwire。SPA フレームワークを導入する前に、Turbo Drive / Frames / Streams で要件を満たせないか検討する。
  • JavaScript は Stimulus コントローラーとして小さく分割する。
  • アセットは Propshaft(Rails 8 のデフォルト)+ importmap が基本。ビルドが必要な場合は jsbundling-rails / cssbundling-rails。

6.4 国際化(I18n)

  • 文字列はハードコードせず、config/locales/*.yml に定義して t(".title") で呼び出す(遅延参照)。
  • モデル名・属性名も activerecord.models / activerecord.attributes に定義する。

7. ビジネスロジックの置き場所

置き場所向いている処理
モデル単一モデルに閉じたドメインロジック、バリデーション
Concern複数モデルで共有される振る舞い
サービスオブジェクト複数モデルにまたがる処理、外部API連携
フォームオブジェクト複数モデルを1つのフォームで扱う場合、DBに保存しないフォーム
クエリオブジェクト複雑な検索条件・集計
値オブジェクト金額、住所など、属性の組に振る舞いを持たせる場合
ジョブ時間のかかる処理、非同期処理

サービスオブジェクトの例

# app/services/orders/checkout.rb
module Orders
  class Checkout
    def self.call(...) = new(...).call
 
    def initialize(order:, payment_method:)
      @order = order
      @payment_method = payment_method
    end
 
    def call
      ActiveRecord::Base.transaction do
        @order.lock!
        @order.update!(status: :paid, paid_at: Time.current)
        @order.line_items.each(&:reserve_stock!)
      end
      OrderMailer.with(order: @order).confirmation.deliver_later
      @order
    end
  end
end
  • 1 クラス 1 責務。公開メソッドは call 1 つに統一するとチームで扱いやすい。
  • 名前は「動詞+目的語」など処理内容がわかるものにする。
  • 「とりあえず全部サービスへ」は避ける。モデルで自然に書けるならモデルに書く。

8. データベースとマイグレーション

  • マイグレーションは一度本番に適用したら編集しない。修正は新しいマイグレーションで行う。
  • change メソッドでロールバック可能に書く。不可逆な操作は up / down を明示する。
  • 外部キーには foreign_key: true で外部キー制約を、検索に使うカラムにはインデックスを張る。
  • null: false と default: を適切に設定する。
  • マイグレーション内でアプリのモデルクラスを使わない(将来モデルが変わると壊れる)。データ移行はデータ移行用のタスクやジョブに分離する。
  • 大きなテーブルへのカラム追加・インデックス作成はロックに注意する。PostgreSQL では algorithm: :concurrently + disable_ddl_transaction! を使う。strong_migrations gem で危険な操作を検出できる。
  • db/schema.rb(または structure.sql)は必ずバージョン管理に含める。
class AddPublishedAtToPosts < ActiveRecord::Migration[8.1]
  disable_ddl_transaction!
 
  def change
    add_column :posts, :published_at, :datetime
    add_index :posts, :published_at, algorithm: :concurrently
  end
end

9. パフォーマンス

9.1 N+1 クエリを防ぐ

# Bad
Post.all.each { |post| puts post.author.name }
 
# Good
Post.includes(:author).each { |post| puts post.author.name }
  • includes / preload / eager_load を使い分ける。
  • 開発環境では strict_loading や bullet gem / prosopite gem で検出する。
# config/environments/development.rb
config.active_record.strict_loading_by_default = true  # 必要に応じて

9.2 クエリの効率化

  • 必要なカラムだけ取得: select / pluck
  • 存在確認は exists?(present? や any? でレコードを全件ロードしない)
  • 件数は count(SQL)と size(ロード済みなら配列長)を使い分ける
  • 大量データは find_each / in_batches でバッチ処理
  • 一括操作は insert_all / upsert_all / update_all(ただしバリデーション・コールバックは走らない点に注意)
  • 集計値を頻繁に参照するなら counter_cache

9.3 キャッシュ

  • フラグメントキャッシュ・ロシアンドールキャッシュ(cache @post do ... end、touch: true)
  • 低レベルキャッシュ: Rails.cache.fetch(key, expires_in: 1.hour) { ... }
  • Rails 8 では Solid Cache(DB バックエンド)がデフォルトで、Redis なしでも運用できる。

9.4 計測してから最適化する

  • rack-mini-profiler、APM(New Relic、Datadog、Skylight など)、ログのクエリ時間を確認してボトルネックを特定する。
  • 本番では YJIT を有効にする(Rails 7.2 以降はデフォルトで有効化される設定)。

10. バックグラウンドジョブ

  • 時間のかかる処理(メール送信、外部API、画像処理、集計)は Active Job で非同期化する。
  • Rails 8 のデフォルトバックエンドは Solid Queue。Redis なしで運用でき、多くのアプリではこれで十分。
  • ジョブは冪等に設計する(リトライされても安全に)。
  • 引数には ActiveRecord オブジェクトそのもの(GlobalID でシリアライズされる)か ID を渡し、大きなデータは渡さない。
  • retry_on / discard_on でエラー時の挙動を明示する。
  • 長時間ジョブは Active Job Continuations(Rails 8.1+)でステップ単位で再開可能にできる。
  • メールは deliver_now ではなく deliver_later を使う。
class SyncInventoryJob < ApplicationJob
  queue_as :default
  retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 5
  discard_on ActiveRecord::RecordNotFound
 
  def perform(product)
    InventoryClient.sync(product)
  end
end

11. セキュリティ

  • Strong Parameters でマスアサインメントを防ぐ。

  • SQL インジェクション対策: 文字列補間でクエリを組まない。

    # Bad
    User.where("name = '#{params[:name]}'")
    # Good
    User.where(name: params[:name])
    User.where("name LIKE ?", "%#{User.sanitize_sql_like(params[:q])}%")
  • XSS 対策: ERB はデフォルトでエスケープされる。html_safe / raw は極力使わない。ユーザー入力の HTML は sanitize を通す。

  • CSRF 対策: protect_from_forgery を無効化しない(API 専用の場合はトークン認証で代替)。

  • 認証: Rails 8 の bin/rails generate authentication で基本的な認証を生成できる。パスワードは has_secure_password(bcrypt)で保存。

  • 認可: リソースへのアクセスは必ず所有者・権限でスコープする。

    # Bad: 他人の投稿も取れる
    @post = Post.find(params[:id])
    # Good
    @post = Current.user.posts.find(params[:id])
  • レート制限: Rails 7.2+ の rate_limit でログインなどへの総当たりを防ぐ。

    rate_limit to: 10, within: 3.minutes, only: :create
  • config.force_ssl = true(本番)、Content Security Policy の設定。

  • 機密カラムは Active Record Encryption(encrypts :ssn)で暗号化する。

  • ログに機密情報を出さない: config.filter_parameters に追加する。

  • Brakeman(静的解析)と bundler-audit(gem の脆弱性チェック)を CI で実行する。Rails 8 の新規アプリには Brakeman が標準で入っている。

  • Rails 本体と gem を定期的にアップデートし、セキュリティリリースは速やかに適用する。


12. テスト

  • Minitest(Rails 標準)か RSpec をチームで統一する。
  • テストの種類と比重
    • モデル / ユニットテスト: ロジックの大部分をここでカバー
    • リクエスト(Integration)テスト: コントローラーの振る舞い、認可、レスポンス
    • システムテスト: 主要なユーザーフローに絞る(遅いため)
    • ジョブ・メーラーのテスト
  • テストデータは fixtures(Rails 標準)か FactoryBot。必要最小限のデータで書く。
  • 外部 API は WebMock / VCR などでスタブする。
  • 時間依存のテストは travel_to を使う。
  • 並列実行(parallelize(workers: :number_of_processors))でテストを高速化する。
  • カバレッジは SimpleCov で計測しつつ、数値より「重要なパスがテストされているか」を重視する。
  • CI で必ずテストを実行し、失敗したらマージしない。Rails 8.1 ではローカル CI(bin/ci、config/ci.rb)も用意されている。
class PostTest < ActiveSupport::TestCase
  test "タイトルがないと無効" do
    post = Post.new(title: "")
    assert_not post.valid?
    assert_includes post.errors[:title], "を入力してください"
  end
end

13. 設定・秘密情報の管理

  • API キーやパスワードは Rails credentials(bin/rails credentials:edit)か環境変数で管理し、コードやリポジトリに直接書かない。

    • 環境ごとに --environment production で分ける。
    • config/master.key はコミットしない。
  • アプリ固有の設定値は config/x や config_for でまとめる。

    # config/application.rb
    config.x.payment = config_for(:payment)  # config/payment.yml
  • 環境ごとの差異は config/environments/*.rb に閉じ込め、コード中で Rails.env.production? の分岐を多用しない。


14. コード品質・ツール

ツール用途
RuboCop(rubocop-rails-omakase)コードスタイル統一。Rails 7.2+ の新規アプリに標準搭載
Brakemanセキュリティ静的解析
bundler-audit依存 gem の脆弱性チェック
bullet / prosopiteN+1 クエリ検出
strong_migrations危険なマイグレーションの検出
annotaterbモデルにスキーマ情報をコメントとして付与
debug標準デバッガ(binding.break / debugger)
  • コードレビューでは「Rails の規約から外れていないか」「テストがあるか」「N+1 がないか」「認可漏れがないか」を確認する。
  • Gemfile の gem は必要最小限に。メンテナンスされていない gem は避ける。

15. デプロイ・運用

  • Rails 8 は Kamal 2 + Thruster で Docker ベースのデプロイが標準。Heroku・Render・Fly.io などの PaaS も選択肢。
  • Rails 8 の Solid Queue / Solid Cache / Solid Cable により、Redis なしのシンプルな構成から始められる。
  • アプリはステートレスに保ち、ファイルは Active Storage 経由で S3 などの外部ストレージに保存する。
  • ヘルスチェックエンドポイント /up(Rails 7.1+ 標準)をロードバランサーに設定する。
  • ログは構造化し(Rails 8.1 の構造化イベント報告 Rails.event も活用できる)、外部のログ基盤に集約する。
  • エラートラッキング(Sentry、Honeybadger、AppSignal など)を導入する。Rails 標準の Rails.error API を使うとサービス非依存に書ける。
  • DB マイグレーションはデプロイ時に自動実行し、後方互換性のある変更(カラム追加 → コード変更 → 古いカラム削除)を段階的に行う。
  • DB のバックアップと復元手順を定期的に確認する。

16. アップグレード戦略

  • Rails のサポートポリシーは短い。サポート中のバージョンを使い続けることが最大のセキュリティ対策。
  • マイナーバージョンごとに段階的に上げる(例: 7.1 → 7.2 → 8.0 → 8.1)。一気に飛ばさない。
  • 手順:
    1. テストが通る状態を確認
    2. 非推奨(deprecation)警告をすべて解消
    3. Gemfile の Rails バージョンを上げて bundle update rails
    4. bin/rails app:update で設定ファイルの差分を確認
    5. new_framework_defaults_X_Y.rb の設定を一つずつ有効化
    6. config.load_defaults を新バージョンに更新
  • Ruby のバージョンも合わせて計画的に上げる。

17. チェックリスト

実装時

  • Rails の規約(命名、REST、ディレクトリ構成)に従っているか
  • コントローラーは薄く、ロジックは適切な場所にあるか
  • Strong Parameters(params.expect)を使っているか
  • 認可チェック(所有者スコープ)が漏れていないか
  • N+1 クエリが発生していないか
  • DB 制約(NOT NULL、ユニーク、外部キー)とインデックスがあるか
  • 重い処理はジョブで非同期化しているか
  • 文字列は I18n 化されているか
  • テストを書いたか

リリース前

  • CI(テスト、RuboCop、Brakeman、bundler-audit)がすべて通っているか
  • マイグレーションは本番データ量で安全か(ロック、所要時間)
  • 秘密情報がコードやログに含まれていないか
  • エラー監視・ログで問題を検知できるか

参考リンク

0件のページ