Railsでエラーページを表示する.

いつもpublic/404.htmlとか編集してる程度だったので、Railsの仕組みを使ったエラーページを作ろうかと色々試行錯誤してみた.


因にエラーページは通常はProductionモードでHTTPアクセスをローカルホスト( localhost / 127.0.0.1 )以外でアクセスしたときに確認できます.

エラーページの実装方法

方法はググってみると、ざっと3種類あった.

  • public/404.htmlを書き換える
  • ApplicationController内でrescue_fromを追記
  • ApplicationController内でrescue_action_in_publicメソッドをオーバライド


色々やるなら、最後のrescue_action_in_publicオーバライドする方法が一番よさそう. ただ欲を言うとRails自身もProductionモードのときちゃんと、404.htmlとか表示してるからその機能っぽくしたい.

Railsのコードを読んでみた.

ActionController::RescueにDEFAULT_RESCUE_RESPONSESなんてモノがあるじゃないか!コレを使ってRailsは例外からHTTPステータスコードに変換して404.htmlを表示してた.

    DEFAULT_RESCUE_RESPONSES = {
      'ActionController::RoutingError'             => :not_found,
      'ActionController::UnknownAction'            => :not_found,
      'ActiveRecord::RecordNotFound'               => :not_found,
      'ActiveRecord::StaleObjectError'             => :conflict,
      'ActiveRecord::RecordInvalid'                => :unprocessable_entity,
      'ActiveRecord::RecordNotSaved'               => :unprocessable_entity,
      'ActionController::MethodNotAllowed'         => :method_not_allowed,
      'ActionController::NotImplemented'           => :not_implemented,
      'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
    }


ステータスコード自体はActionController::StatusCodes::STATUS_CODESで管理してた.

    STATUS_CODES = {
      :
      :
      200 => "OK",
      :
      404 => "Not Found",
      :
      500 => "Internal Server Error",
      :
      :
    }

例外毎にHTTPステータスを割り当てて表示する方法.

Railsのコードを読んで大体なにしてるか分かったので、404.htmlの用に例外からステータスコードを判定して表示するコードを書いてみた.


renderでレイアウトとか色々してるけどこの辺は普通のコントローラと書き方は同じ.

class ApplicationController < ActionController::Base

  # 例外とステータスの関連付け
  rescue_responses.update({
    'Foo::BarError' => :not_found
  })

  protected

    # 確認をhttp://localhost 等で確認するときはコメントアウト
    # が定石
    #def local_request?
    #  false
    #end
  
    def rescue_action_in_public(exception) #:doc:
      status_message_sym = response_code_for_rescue(exception) || :internal_server_error
      @http_status_code = SYMBOL_TO_STATUS_CODE[status_message_sym]
      
      respond_to do |format|
        format.html { render :template => "commons/error.html", :layout => 'application', :status => @http_status_code }
      end
    end


end

テンプレートをRAILS_ROOT/views/commons/error.html.erbに作成して、Productionモードで確認.

<%
title = "#{@http_status_code} #{ActionController::StatusCodes::STATUS_CODES[@http_status_code]}"
-%>
<h1><%= title %></h1>

<p>
<% if @http_status_code == 403 # Forbidden -%>
アクセス権限がありません.
<% elsif @http_status_code == 404 # Not Found -%>
お探しのページが見つかりませんでした.
<% elsif @http_status_code == 422 # Unprocessable Entity -%>
指定されたエンティティの処理ができませんでした.
<% elsif @http_status_code == 500 # Internal Server Error -%>
システムに障害が発生しました.
<% end -%>
</p>