🍤

Railsのcontrollerは凄いぜ!

に公開

きっかけは1行のcreate

def create = Post.create!(post_params)
      .tap { it.convert_to_streaming.call_later }
      .tap { it.image.call_later }
      .tap { redirect_to hq_post_path(it), notice: "投稿を作成しました" }

最近プロジェクトでこんなcreateアクションを書いたところ、Rails仲間のテンションが一気に爆上がりしました。Ruby 3.1以降で使える右代入やtapチェーンを並べると、成功したときに起こる出来事が縦に揃うので読みやすさが段違いになります。Post.create!の戻り値をitに流し込み、「配信の準備」「画像生成」「最後のリダイレクト」とハッピーパスのイベントを自然に記述できるのがポイントです。

.tapでハッピーパスを縦に並べる

tapはブロックを評価したあと、自身をそのまま返すメソッドです。だからこそ、複数の副作用を挟みながらも値を壊さず次の処理に渡せます。

Post.create!(post_params)
    .tap { it.convert_to_streaming.call_later }   # 非同期で配信準備
    .tap { it.image.call_later }                     # 画像生成ジョブを予約
    .tap { redirect_to hq_post_path(it), notice: "投稿を作成しました" }

Railsコントローラでありがちなifネストやreturnだらけの分岐に悩まされず、「成功したときはこう進む」という流れが一直線に読めます。ビジネスロジックはサービスオブジェクトに寄せるとしても、ハッピーパスの読みやすさだけはコントローラで守りたいときに効きます。

「エラーパス」も綺麗に書きたい

とはいえ現実には、バリデーションエラーや外部APIエラーなど、ハッピーパス以外のパターンも丁寧に扱いたいシチュエーションが山ほどあります。ここでrescueを差し込むとまた縦読みが崩れてしまうし、ifで分岐するとハッピーパスが横に押しやられてしまいます。

そんなときに頼れるのがRails標準のrescue_fromナリ。後から例外ハンドリングを宣言できるので、ハッピーパスのコードを崩さずに安心して戻り経路を増やせます。

rescue_fromで後付けする

rescue_fromはActionController::Baseに用意された仕組みで、指定した例外が発生したときに任意のメソッドへ処理を移すナリ。コントローラの冒頭で宣言するだけで、ハッピーパスのコードはそのまま保ちつつエラーパスを後付けできます。

class Hq::PostsController < ApplicationController
  rescue_from ActiveRecord::RecordInvalid, with: :render_invalid
  rescue_from ExternalApi::Error, with: :notify_external_failure

  def create = Post
    .create!(post_params)
    .tap { it.convert_to_streaming.call_later }
    .tap { it.image.call_later }
    .tap { redirect_to hq_post_path(it), notice: "投稿を作成しました" }

  private

  def render_invalid(error)
    flash.now[:alert] = error.record.errors.full_messages.to_sentence
    render :new, status: :unprocessable_entity
  end

  def notify_external_failure(error)
    Sentry.capture_exception(error)
    redirect_to hq_posts_path, alert: "配信予約に失敗しました"
  end
end

ハッピーパスはcreateメソッド内にきれいに並べたまま、rescue_fromで失敗時の挙動を別メソッドへ引き受けさせています。ハンドラの追加・変更は宣言を1行増やすだけなので、レビューでも「この例外を捕まえようナリ」と提案しやすくなります。

実践ポイント

  • ハッピーパスを最優先で縦に描く。 成功時のストーリーが一直線だと、レビューでの「まず成功時どう動くか」を追いやすい。
  • レスキュー経路は後から宣言する。 rescue_fromならハンドラを後付けでき、仕様追加のたびにrescueを足して崩れる心配が減る。
  • コントローラは入口としての役割に集中させる。 細かな副作用はtapのブロックでサービスやジョブに委ね、責務の境界がはっきりする。

まとめ

Railsコントローラはまだまだ進化させられます。tapチェーンでハッピーパスを縦に揃え、rescue_fromで失敗経路を後付けするだけで、毎日のレビュー体験がぐっと軽くなりました。ハッピーパスもエラーパスも両方きちんと書ききりたいとき、ぜひ試してみてください。

GitHubで編集を提案

Discussion