Rails で例外を発生させたい際は,raise
...つまり RuntimeError をよく使用するかと思います。
しかし,サービス上の制約から,特定の状況下で例外を発生させる場合,raise
だけでは物足りなくなる時があります。raise
では「何かまずいことが起きてしまいました!」程度のことしか伝えてくれません。まぁ,引数に渡す message を見れば理解できるかもですが...
兎にも角にも,特定の状況下に対する例外が存在するなら,その例外に対して名前を付けてあげましょう。
カスタム例外を設定すると,発生時に「何に対する例外か」がパッと理解できるようになりますし,特定の動作に誘導することも容易になりますので,良いことづくめです!
- 参考 URL 等
□ 本文
■ 前提情報
使用するアプリケーション
Railsチュートリアルで作成する SampleApp における、users_controller のusers#edit
に着目して実装します。
classUsersController<ApplicationControllerbefore_action:correct_user,only: [:edit,:update]#...private#...# 正しいユーザーかどうか確認defcorrect_user# GET /users/:id/edit# PATCH /users/:id@user=User.find(params[:id])redirect_to(root_url)unlesscurrent_user?(@user)end#...end
カスタム例外の設定規則
StarndardError
を継承する- クラス名の末尾に
Error
を付ける
■ カスタム例外の設定方法
実装自体は,とても単純なのですが,設定場所にいくつか種類がありますので,紹介していきます。
実装例1: 発生ファイルに直接設定
その名の通り,例外が発生するファイル自身に設定します。
特定のクラスに強く結びつける方法であることから,Model 層や Service 層で見かけたりします。
classUsersController<ApplicationControllerclassNotPermittedError<StandardError;endbefore_action:correct_user,only: [:edit,:update]#...private#...defcorrect_user@user=User.find(params[:id])raiseNotPermittedError,"あなたにリクエスト権限がありません"unlesscurrent_user?(@user)end#...end
実装例2: app/ 配下に設定
自作の module を app/ 配下に設定します。
validators や services と同じ考え方で配置する感じですかね。
moduleApplicationErrorclassNotPermittedError<StandardError;endend
classUsersController<ApplicationControllerbefore_action:correct_user,only: [:edit,:update]#...private#...defcorrect_user@user=User.find(params[:id])raiseApplicationError::NotPermittedError,"あなたにリクエスト権限がありません"unlesscurrent_user?(@user)end#...end
実装例3: lib/ 配下に設定
lib ディレクトリの存在目的から見ると,王道パターンかも。
なお,lib 直下の配置が気になる場合,適宜ディレクトリを挟んで設定してください。
#...Bundler.require(*Rails.groups)# ↓ 追加コードrequire_relative'../lib/exception.rb'moduleSampleApp#...end
moduleApplicationclassError<StandardError;endclassNotPermittedError<Error;endend
classUsersController<ApplicationControllerbefore_action:correct_user,only: [:edit,:update]#...private#...defcorrect_user@user=User.find(params[:id])raiseApplicationError::NotPermittedError,"あなたにリクエスト権限がありません"unlesscurrent_user?(@user)end#...end
■ エラーハンドリング
さて,これでカスタム例外は作成完了ですが,仕上げが残っています。
このままでは,例外を発生させたままです。
ユーザ側から見ると 500 エラー画面が出てきて,なぜ強制終了したのか理由がわかりませんし,サービスの操作感として連続性が失われるのも避ける必要があります。
元のコードでは,不正なアクセスをしたユーザに対して,root_url にリダイレクトさせていますので,カスタム例外が発生した際は,同じようにリダイレクトさせましょう。
また、例外を握り潰さないために、サーバ側に理由を説明するためのログを残しましょう。
classApplicationController<ActionController::Base#...rescue_fromApplication::NotPermittedError,with: :redirect_root_pagedefredirect_root_pageRails.logger.info"ルート URL にリダイレクト: #{exception.message}"ifexceptionredirect_toroot_url,flash: {danger: "閲覧権限がありません"}end#...end
※ シンプルに表記することを目的として application_controller.rb に記入していますが,色々追加されるファイルでもあるため,concerns に切り出すと尚可読性が高まるでしょう。
□ 余談
OSS におけるカスタム例外の設定方法も調べてみると,見事にバラバラだったので,プロジェクト毎に設定方法が異なるかもしれない
具体的な命名も設定場所も異なるため、プロジェクトに合わせて、柔軟に対応しましょう。
今回使用した PR です→カスタム例外の設定 by masayuki-0319 · Pull Request #9 · masayuki-0319/sample_app