Railsでエラーページを動的に
適当なactionを用意してexceptions_appを変更すると、public/404.htmlなどの挙動を変えられます。
サンプルコード
class MyApp::Application < Rails::Application
config.exceptions_app = routes
end
MyApp.application.routes.draw do
match '/404' => 'errors#show', id: 404, via: :all
match '/422' => 'errors#show', id: 422, via: :all
match '/500' => 'errors#show', id: 500, via: :all
end
class ErrorsController < ApplicationController
end
<%# params[:id]とかを使って適当なエラー用のコンテンツを描画する %>
説明
rack middleware stack
Railsのデフォルトのrack middleware stackには、ActionDispatch::DebugExceptionsとActionDispatch::ShowExceptionsが含まれています。これは rake middleware
を実行することで確かめられます。これらはdevelopment環境、production環境に関わらず含まれていますが、Rails.configurationにより挙動が異なります。
ActionDispatch::Journey::Router
config/routes.rb で定義したルーティングは、内部ではActionDispatch::Journey::Routerというライブラリによって処理されます。このRouterもRack middlewareになっています。もしこのRouterが適切なルーティングを見つけられなかった場合、[404, {'X-Cascade' => 'pass'}, ['Not Found']]
というレスポンスが返ります。
ActionDispatch::DebugExceptions
ActionDispatch::Journey::Routerの外側にはActionDispatch::DebugExceptionsが居ます。もし X-Cascade: pass
というレスポンスヘッダが返ってきた場合、production環境ではActionController::RoutingErrorをraiseし、development環境ではエラーの内容を表示するHTMLを返します。この環境による違いは、Rails.configuration.consider_all_requests_localを変更することで変更できます。もしdevelopment環境でもproduction環境同様のエラー表示を試したい場合は、この値をfalseにすると良いでしょう。
ActionDispatch::ShowExceptions
DebugExceptionsの外側にはActionDispatch::ShowExceptionsが居ます。ShowExceptionsは、内側のRackアプリで例外が発生した場合、env["PATH_INFO"]
(※URLのパスを示す情報) を /404
などの対応するステータスコードを表す値に書き換えた上で、Rails.configuration.exceptions_appにリクエストの処理を委譲します。
ActionDispatch::ExceptionWrapper
「ActionController::RoutingErrorの例外に対応するステータスコードは404である」といった情報は、ActionDispatch::ExceptionWrapperが管理しています。この対応表はRails.configuration.action_dispatch.rescue_responsesと紐付いており、例えばActiveRecordはRailsアプリ初期化時にこの対応表にActiveRecord::RecordNotFoundを404として扱うように追加したりしています。
ActionDispatch::PublicExceptions
Rails.configuration.exceptions_appはデフォルトの状態ではActionDispatch::PublicExceptionsが設定されています。このrack applicationは、/404に対してpublic/404.htmlを返却するような動作をします。エラー時の挙動を変えたい場合は、Rails.configuration.exceptions_appを別のrack applicationに変更すれば良いわけです。