Rambulanceでお手軽かつ柔軟にRailsの動的エラーページを作る
はじめに
本記事はプロもくチャット Advent Calendar 2023の18日目です。
Railsには標準でエラーページのviewが用意されていますが、このように味気ないものになっています。
本番用のアプリにはこのようなデフォルトページではなく、オリジナルのエラーページを作成するケースがほとんどかと思います。
エラーページの実装方法には、静的なページにする方法と動的なページにする方法があります。
静的なエラーページ
public/404.html
などを編集すれば良いだけです。
静的ページで事足りるのであれば、基本的には静的ページを使用した方が良いです。
動的なエラーページ
ApplicationController
でrescue_from
を設定して例外補足する方法が多く紹介されていますが、これはこれで問題があります(ここは今回の本筋ではないので詳細は割愛します)。
そこで本記事では、動的エラーページのおすすめとして、Rambulanceというgemを使う方法を紹介します。
本記事でやること
- Rambulanceを導入して、
ActionController::RoutingError
やActiveRecord::RecordNotFound
が発生した場合に表示する404のカスタムエラーページを作る -
ActionController::RoutingError
とActiveRecord::RecordNotFound
が発生した場合に、それぞれ別の404ページを表示する
手順
Rambulance導入
まずはRambulanceをインストールするため、Gemfileに以下を追加して、bundle install
を実行します。
gem 'rambulance'
次に以下を実行します。
$ rails g rambulance:install
このコマンドにより、以下のファイルが生成されます。
- 各エラーページのviewファイル
app/views/errors/bad_request.html.erb
app/views/errors/forbidden.html.erb
app/views/errors/internal_server_error.html.erb
app/views/errors/not_found.html.erb
app/views/errors/unprocessable_entity.html.erb
- エラーページに使用されるレイアウト
app/views/layouts/error.html.erb
- Rambulanceの設定ファイル
config/initializers/rambulance.rb
この時点で、生成したviewに対応するステータスコードのエラーを発生させると、それぞれのエラーページを表示することができます。
もちろん、生成したviewのHTML/CSSを編集すればその通りに表示されます。
詳細設定
上記により動的なカスタムエラーページを表示することができますが、config/initializers/rambulance.rb
を編集することでさらに詳細な設定を行うことができます。
config/initializers/rambulance.rb
はデフォルトで以下のようになっています。
Rambulance.setup do |config|
# 例外クラスとステータスコードの組み合わせ(1)
config.rescue_responses = {}
# 使用するレイアウト(2)
config.layout_name = "error"
# カスタムエラーのviewファイルを格納するディレクトリ(3)
config.view_path = "errors"
end
この場合、Rambulanceの設定は以下のような設定になっています。
- 例外クラスと、その例外が起こった時に返すステータスコードの組み合わせはデフォルトのまま(追加設定なし)
- カスタムエラーのレイアウトには
app/views/layouts/error.html.erb
を使用する - カスタムエラーのviewファイルは
app/views/errors
ディレクトリに格納されているものを使う
2, 3についてはそのままなので説明は割愛します。
1の例外クラスとステータスコードの組み合わせは、デフォルトでは以下のようになっています。
ActionController::RoutingError => :not_found,
AbstractController::ActionNotFound => :not_found,
ActionController::MethodNotAllowed => :method_not_allowed,
ActionController::UnknownHttpMethod => :method_not_allowed,
ActionController::NotImplemented => :not_implemented,
ActionController::UnknownFormat => :not_acceptable,
ActionDispatch::Http::MimeNegotiation::InvalidType => :not_acceptable,
ActionController::MissingExactTemplate => :not_acceptable,
ActionController::InvalidAuthenticityToken => :unprocessable_entity,
ActionController::InvalidCrossOriginRequest => :unprocessable_entity,
ActionDispatch::Http::Parameters::ParseError => :bad_request,
ActionController::BadRequest => :bad_request,
ActionController::ParameterMissing => :bad_request,
Rack::QueryParser::ParameterTypeError => :bad_request,
Rack::QueryParser::InvalidParameterError => :bad_request,
ActiveRecord::RecordNotFound => :not_found,
ActiveRecord::StaleObjectError => :conflict,
ActiveRecord::RecordInvalid => :unprocessable_entity,
ActiveRecord::RecordNotSaved => :unprocessable_entity
これに加えて、例えばActiveRecord::RecordNotUnique
の例外が発生した時にunprocessable_entity.html.erb
のエラーページを表示させたいとします。
その場合は、以下のように例外クラス名のStringをキー、ステータスコードのエイリアスを値として設定を追加しましょう。
Rambulance.setup do |config|
# 例外クラスとステータスコードの組み合わせ
- config.rescue_responses = {}
+ config.rescue_responses = {
+ "ActiveRecord::RecordNotUnique" => :unprocessable_entity
+}
# 使用するレイアウト
config.layout_name = "error"
# カスタムエラーのviewファイルを格納するディレクトリ
config.view_path = "errors"
end
ステータスコードを拡張する
上記の方法で、カスタムエラーページを作成することができました。
基本的には各ステータスコードに対応するviewファイルは最大1つとなっています。
しかし、同じステータスコードでもエラー内容によってエラーページを出し分けたいという場合もあるかもしれません。
例えば以下のような場合です。
-
ActiveRecord::RecordNotFound
が発生した場合は404エラーとしてrecord_not_found.html.erb
を表示する - それ以外の404系のエラー(
ActionController::RoutingError
など)が発生した場合はnot_found.html.erb
を表示する
Rambulanceでは、ステータスコードのエイリアスとエラーページのviewファイル名が一致している必要があります。
そして404のエイリアスは:not_found
であり、:record_not_found
というエイリアスは存在しません。
したがって、現状では404エラーとして表示できるのはnot_found.html.erb
のみです。
このエイリアスはRackの組み込みとして定義されており、他の値を使うことはできません。
具体的には、エイリアスとして以下のものが用意されています。
各ステータスコードのエイリアス
{
:continue=>100,
:switching_protocols=>101,
:processing=>102,
:early_hints=>103,
:ok=>200,
:created=>201,
:accepted=>202,
:non_authoritative_information=>203,
:no_content=>204,
:reset_content=>205,
:partial_content=>206,
:multi_status=>207,
:already_reported=>208,
:im_used=>226,
:multiple_choices=>300,
:moved_permanently=>301,
:found=>302,
:see_other=>303,
:not_modified=>304,
:use_proxy=>305,
:temporary_redirect=>307,
:permanent_redirect=>308,
:bad_request=>400,
:unauthorized=>401,
:payment_required=>402,
:forbidden=>403,
:not_found=>404,
:method_not_allowed=>405,
:not_acceptable=>406,
:proxy_authentication_required=>407,
:request_timeout=>408,
:conflict=>409,
:gone=>410,
:length_required=>411,
:precondition_failed=>412,
:content_too_large=>413,
:uri_too_long=>414,
:unsupported_media_type=>415,
:range_not_satisfiable=>416,
:expectation_failed=>417,
:misdirected_request=>421,
:unprocessable_content=>422,
:locked=>423,
:failed_dependency=>424,
:too_early=>425,
:upgrade_required=>426,
:precondition_required=>428,
:too_many_requests=>429,
:request_header_fields_too_large=>431,
:unavailable_for_legal_reasons=>451,
:internal_server_error=>500,
:not_implemented=>501,
:bad_gateway=>502,
:service_unavailable=>503,
:gateway_timeout=>504,
:http_version_not_supported=>505,
:variant_also_negotiates=>506,
:insufficient_storage=>507,
:loop_detected=>508,
:network_authentication_required=>511
}
以下のような関連付けを行なったところで、record_not_found.html.erb
は表示できません。
config.rescue_responses = {
+ 'ActiveRecord::RecordNotFound' => :record_not_found
}
上述のように、:record_not_found
というエイリアスは用意されていないためです。
逆に考えると、ステータスコードのエイリアスを拡張して:record_not_found
を定義できれば、エラーページにも使用することができます。
以下の記述を追加することで、404エラーに対応するエイリアスとして:record_not_found
を定義することができます。
+ Rack::Utils::SYMBOL_TO_STATUS_CODE[:record_not_found] = 404
config.rescue_responses = {
'ActiveRecord::RecordNotFound' => :record_not_found
}
これにより、以下のような404エラーページの出し分けが可能になります。
-
ActiveRecord::RecordNotFound
が発生した場合は404エラーとしてrecord_not_found.html.erb
を表示する - それ以外の404系のエラー(
ActionController::RoutingError
など)が発生した場合はnot_found.html.erb
を表示する
まとめ
Rambulanceを使うことで手軽に動的なエラーページを作成できます。
また、ステータスコードのエイリアスを拡張することで、エラークラスとエラーページの関連付けをより柔軟に行うことができます。
ただ大前提として、静的ページで事足りるのであれば静的ページを使うようにした方が良いです。
それでも動的ページが必要な場合は、ぜひこの方法をお試しください!
Discussion