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に変更すれば良いわけです。

参考リンク :link: