【結論】
・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時間!」