エラーページの実装方法

f:id:ryoutaku_jo:20190508215556p:plain

【結論】

Railsでは、エラーが発生した場合、デフォルトで設定されたエラー画面が表示される

・このエラー画面を任意の内容に変更する事が可能である

・なお、HTTPエラー毎に、エラーを検知する為のコードを記述する必要がある

【目次】

【本題】

エラーページについて

Railsでは、エラーが発生した場合、デフォルトで設定されたエラー画面が表示されます。

開発環境においては、詳細なエラー内容が表示され非常に有用ですが、一方は本番環境のエラー画面は非常に簡素なものです。

何らかのバグで、このエラー画面がユーザーの目に入った場合、サイトの信頼性を損なう危険性があります。 もちろんエラーを出さない事が最優先ですが、必ずバグの出ないシステムの構築は困難な為、信頼性を損ねない為にも、エラー画面はカスタマイズしておく事が重要です。

今回は、そのエラーページ実装について触れます。

エラーを補足する(エラーハンドリング)

実装にあたっては、まずエラーを補足するコードを記述する必要があります。 今回は、HTTPエラーの「403・404・500」のエラーを対象とします。

エラーの検知は全ての箇所で共通の為、application_controllerに記述します。 但し、記述量が多いので、今回はConcernに切り出しています。

module ErrorsHanlder
  extend ActiveSupport::Concern

  included do
    if Rails.env.production? || Rails.env.staging? || Rails.env.test?
      rescue_from StandardError, with: :rescue_500
      rescue_from ApplicationController::Forbidden, with: :rescue_403
      rescue_from ActionController::RoutingError, with: :rescue_404
      rescue_from ActiveRecord::RecordNotFound, with: :rescue_404
    end
  end

  private

  def rescue_403(exception = nil)
    if exception
      logger.warn "403 Forbidden exception: #{exception.message}"
    else
      logger.warn '403 Forbidden'
    end
    @status_code = 403
    render 'errors/error', status: :forbidden
  end

  def rescue_404(exception = nil)
    if exception
      logger.warn "404 Not Found exception: #{exception.message}"
    else
      logger.warn '404 Not Found'
    end
    if request.xhr?
      render json: {}, status: :not_found
    else
      @status_code = 404
      render 'errors/error', status: :not_found
    end
  end

  def rescue_500(exception = nil)
    if exception
      logger.error "500 Internal Server Error exception: #{exception.message}"
    else
      logger.error '500 Internal Server Error'
    end
    if request.xhr?
      @status_code = 500
      render json: {}, status: :internal_server_error
    else
      @status_code = 500
      render 'errors/error', status: :internal_server_error
    end
  end
end

あとは、これをapplication_controllerに呼び出します。

class ApplicationController < ActionController::Base
  class Forbidden < ActionController::ActionControllerError; end

  include UserActivityHanlder

(略)

エラーページにルーティングする

次にエラーを検知したら、所定のエラーページに飛ぶようにします。 まずroutes.rbにルーティングを記述します。

  get 'fatal_test', to: 'errors#fatal_test'
  get 'error_test', to: 'errors#error_test'

  match '*path', to: 'errors#not_found', via: :all

そして、errors_controller.rbを作成して、処理を記述します。

class ErrorsController < ApplicationController
  protect_from_forgery except: [:not_found]
  skip_before_action :require_login, only: [:fatal_test, :error_test]

  def not_found
    rescue_404(ActionController::RoutingError.new("No route matches #{request.request_method} #{request.path}"))
  end

  def fatal_test
    logger.fatal('fatal_test')
    render json: { test: 'ok' }
  end

  def error_test
    logger.error('error_test')
    render json: { test: 'ok' }
  end
end

任意のビューを作成する

最後にビューを作成します。 エラーは三種類ですが、下記のファイル一種類で対応します。

<section class="content-header">
  <h1><%= t('common.errors_hanlder.title', status_code: @status_code) %></h1>
</section>
<section class="content">
  <div class="error-page">
    <h2 class="headline text-yellow"> <%= t('common.errors_hanlder.stator_code', status_code: @status_code) %></h2>
    <div class="error-content">
      <h3><i class="fa fa-warning text-yellow"></i><%= t("common.errors_hanlder.#{@status_code}.summary") %></h3>
      <p>
        <%= t("common.errors_hanlder.#{@status_code}.description") %>
        <%= link_to t('common.errors_hanlder.retrurn_link'), root_path %>
      </p>
    </div>
  </div>
  </div>
</section>

ビューファイル一つで対応できる理由は、下記のように辞書ファイルにエラー別の文言を記述して、動的にコンテンツを生成しているからです。

ja:
  common:
    errors_hanlder:
      title: "%{status_code} Error Page"
      stator_code: "%{status_code}"
      retrurn_link: こちらからダッシュボードへ戻れます。
      403:
        summary: 権限がありません
        description: このページへアクセスする権限がありません。
      404:
        summary: ページが見つかりません
        description: このページは移動もしくは削除されており存在しません。
      500:
        summary: エラーが発生しました
        description: ご不便をおかけし申し訳ございません。時間を置いてもう一度お試しください。

これで実装完了です。

参考情報

Rails 5の404/500エラーページ、簡単作成手順 | 酒と涙とRubyとRailsと

《今日の学習進捗(3年以内に10000時間に向けて)》

エラーハンドリングのテストコードの実装でかなり躓いてしまった。 任意でエラーを出す方法がいくら探しても見つからなかった事が要因の一つだが、 最も大きいのはテストコード(RSpec)に対する理解不足だと考えている。 GW中に個人で開発をする中でも、それは痛感した。 テストコードについては、製品の品質に大きく関わる要素なので、学習の優先度を上げて取り組みたい。

また、CKEditorの実装において、画像アップロード周りをかなり無茶なアソシエーションで実装してしまい、 修正が必要な部分が多々あった。とにかく動くことを最優先に取り組んだ弊害の一つだと考えている。 動く事も重要だが、その後の手戻りを考えると、きちんと設計を行った上で、実装に進んだ方が良いと改めて感じた。

学習開始からの期間 :152日
今日までの合計時間:1486h
一日あたりの平均学習時間:9.8h
今日までに到達すべき目標時間:1388h
目標との解離:98h
「10,000時間」まで、

残り・・・「8514時間!」