💧
before_actionの使いすぎ注意!Railsのコントローラ設計を見直す
はじめに:なぜ「before_action地獄」が起こるのか?
ruby
class UsersController < ApplicationController
before_action :set_user
before_action :check_admin, only: [:destroy]
before_action :authenticate_user!
before_action :set_posts, only: [:show, :edit]
def show; end
def edit; end
def destroy; end
end
一見整理されているように見えるけど…
- 各メソッドが何に依存しているか見えにくいです
- set_user が全アクションで呼ばれるが、不要なアクションもあります
- check_admin の中で @user に依存していたりします
→ 依存関係がスパゲッティ化しやすいです
before_action の役割を正しく理解してみる
before_action は、「リクエスト前に共通処理を挟む」ための仕組みです。
よく使われる代表例:
- ログインチェック (authenticate_user!)
- リソースの取得 (set_user)
- 権限確認 (check_admin)
これらは便利だが、「全アクション共通」な処理 に限定して使うべきです。
「何でもbefore_action」はアンチパターン
典型的な悪例:
ruby
before_action :set_project
before_action :set_tasks
before_action :set_comments
→ show だけでしか使わないものを共通処理にしてしまうと、
他のアクションで無駄にSQLが走ってしまいます。
しかも、どの変数がどこで使われているかが分かりづらいです。
責務を分離してみよう(リファクタリングのステップ)
ステップ1:アクションごとに明示する
ruby
def show
@project = Project.find(params[:id])
@tasks = @project.tasks
end
→ 「showで使う変数はshowの中で完結させる」
→ 可読性が上がる&依存関係が見えます
ステップ2:共通処理が明確ならprivateメソッドで
ruby
before_action :set_project, only: [:show, :edit, :update]
private
def set_project
@project = Project.find(params[:id])
end
→ 明確な共通点がある場合のみ採用(id依存など)
ステップ3:複雑なロジックはServiceクラスへ移動
ruby
class ProjectAuthorizationService
def initialize(user, project)
@user = user
@project = project
end
def can_edit?
@user.admin? || @project.owner == @user
end
end
コントローラでは:
ruby
def update
service = ProjectAuthorizationService.new(current_user, @project)
unless service.can_edit?
redirect_to root_path, alert: "権限がありません"
end
end
→ コントローラがスッキリします。
→ 「チェックの中身」がService層に切り出されて再利用できます。
DRYも大事だがSRP(単一責任の原則)についても考えよう
Railsの哲学「DRY」は強力ですが、
「同じ見た目の処理」=「同じ責務」ではない ことに注意です。
「重複して見える処理」でも、文脈が違うなら別に書いた方がよいです。
それが結果的に 保守性・可読性 を上げます。
Controllerを小さく保つコツ(チェックリスト)
チェック項目 | 理想的な状態 |
---|---|
コントローラの行数 | 100行未満が望ましい |
before_actionの数 | 2〜3個まで |
privateメソッド | 各アクションが何を使っているかが明確 |
複雑な条件分岐 | Service/Policyクラスへ移動 |
JSONレスポンス構築 | Serializer / Jbuilderへ委譲 |
まとめ
before_actionは便利だけど「最小限」にしましょう。
before_actionの正しい使い方
- 全アクション共通の軽い処理だけに使う
- 複雑なロジックはServiceやPolicyクラスに切り出す
- 「見た目の重複」よりも「責務の明確化」を優先する
Discussion