Exceptional Railsの発表内容について

Exceptional Rails by Shinichi Maeshima - Kaigi on Rails 2023 についての質問、感想、ご意見を書くトピックです。

「いいね!」 1

例外をどう使うか、はプロジェクトで原則が必要なのかなあと考えさせられました。

「いいね!」 1

@willnet 発表とても面白かったです!ありがとうございました。

1点相談です。Rails APIモードを使用しており、例外クラスに対応するstatus codeを決定させるだけでなく、動的にレスポンスJSONを組み立てたいという要件について考えています。

やりたいこと(要件)

前提

例外発生時、クライアントに対して決まった構造のJSONレスポンスを返さなければならない。

{
  "code":"文字列",
  "message": "文字列"
}

1. エラークラスごとにJSONの各valueを決定する

Railsが定義するActiveRecord::RecordNotFoundraiseされたら以下のレスポンスを返したい。

// status code = 404 としつつ、以下のJSONをresponse bodyとして返す
{
  "code":"not_found",
  "message": "指定されたリソースが見つかりませんでした。"
}

ユーザーコードで定義した独自の例外、MyCustomPaymentGatewayIsDeadErrorraiseされたら以下のレスポンスを返したい。

// status code = 500 としつつ、以下のJSONをresponse bodyとして返す
{
  "code":"payment_gateway_is_dead",
  "message": "連携先のシステムでエラーが発生しました。時間をおいて再度お試しください。"
}

2. エラークラスによっては値を実行時に動的に決定したい

場合によってはraiseされた箇所で例外にセットするメッセージをレスポンスに含めたい。

raise MyCustomFooError, "動的に決まるエラーメッセージ"
// status code = 500 としつつ、以下のJSONをresponse bodyとして返す
{
  "code":"foo",
  "message": exception.message // 例外発生箇所で動的に決定
}

思いついたやり方

rescue_from StandardErrorしてハンドラを自前で頑張って書いて実現できたものの、推奨されない(Rails wayでない)感じでしっくりきていません。

rescue_fromExceptionStandardError を指定すると、Railsでの正しい例外ハンドリングが阻害されて深刻な副作用が生じる可能性があります。よほどの理由がない限り、このような指定はおすすめできません。
Action Controller の概要 - Railsガイド

相談したいこと

上記のような要件に対して適切な実装方法として思いつくものがあればアドバイスいただければ幸いです。

※発表内で触れていたrambulanceを知らなかったのでこれから調べてみようと思っているところです。

:thought_balloon:基本的にはデフォルトの仕組みで静的レスポンスを返すべきと発表でもおっしゃっていたので、そもそも要件がイケてない…という感想になるかもです。が、そこは変えようのない外部システムの要求するインタフェースみたいなものとして、前提とさせてください :pray:

「いいね!」 1

ここでの「動的にレスポンスJSONを組み立てたい」というのは文章を読む限り、DBからデータを引っ張ってくるというものではなくて例外オブジェクトがあれば組み立てられるものですよね。であればActionDispatch::PublicException に似たRackアプリケーションを自作してconfig.exceptions_appに設定するのが良いんじゃないかなと思いました。

動的なエラーページ生成を推奨しないのは、普段本番のエラーページを確認しないからデグレしていても気づきづらいぞ、というのとそもそもの前提(例: DBにアクセスできる)が崩れたときの考慮も必要で大変だぞという話で、今回はテストコードさえちゃんと書いておけばそれほど難しくない要件のように読めました。

はい、おっしゃる通り例外オブジェクトがあれば組み立てられるものです。

exception appを自作するという選択肢を検討したことがなかったので、例外オブジェクトからレスポンスJSONを組み立てる処理をどこに書くのがRails wayかわからぬまま、rescue_fromのハンドラとして書いておりました。

ActionDispatch::PublicExceptionのコードを読みつつ自作にトライしてみます。ありがとうございます!

「いいね!」 1