【実践Rails学習ノート#4】例外処理500を実装する

前回に引き続き実践Ruby on Railsの学習ノートです。

今回は例外処理を行って例外ページに飛ばす、という機能を実装していきます。

以下のような流れで作っていきます。

  1. controllerでraiseして例外を発生させる
  2. 共通のcontrollerで例外処理を行う振る舞いとそのメソッドを定義
  3. さっき作ったメソッドでrenderされるviewを作成

関連記事

totutotu.hatenablog.com

事前知識

例外とは

Railsプログラマが普段目にする例外は、Exceptionクラスそのもののインスタンスではなく、Exceptionクラスの子孫クラスのインスタンスです。

ということでRaisでは全ての例外はExceptionクラスを継承したなんらかのクラスのインスタンスということになるそうです。

というか正確には、Exception::StandardErrorを継承する感じになります。これはRailsというよりRubyのしきたりですねーー

http://qiita.com/kasei-san/items/75ad2bb384fdb7e05941

話を戻すと、

ActiveRecord::RecordNotFoundという例外の継承関係は、

ActiveRecord::ActiveRecordError <- StandardError <- Exception

といった形になっています。

pryとかでみてみたところ、ActiveRecordはモジュールとしてincludeされてるみたいですね。

例外の発生させ方

例外は以下のように、raiseメソッドを用いてraise <クラス名>, '<メッセージ>'で発生させます。

raise ArgumentError, 'the first argument must be a string'

引数なしだとStandardErrorクラスを継承したRunTimeErrorになります。

例外処理は以下のように書きます。

begin 
  X
rescue E1 => e1
  Y1
rescue E2 => e2
  Y2
ensure
  Z
end

Xの処理中に例外が発生し、それがE1クラスの例外ならY1の処理を、E2クラスの例外ならY2の処理を、どちらでもなかった場合はシステムエラーとなります。

Zの処理は例外が発生しなくてもいずれの場合も最後に実行されます。 (Zを定義できる意味はなんなのだろうか...。わからなかったので知っ てる人教えてください...)

ということで実装していきます。

準備

まずはproduction環境でもcacheしないようにしてアプリケーションを再起動させなくても変更を再読み込みするようにしておきます。

# config/environments/production.rb
  config.cache_classes = false

production環境で実行している場合はコンソールにログは出力されずに、log/production.logに保存される。

500 Internal Sever Error

まず、500の例外処理を実装してみます。

例外を起こす

staff#indexが呼ばれたらかならずraiseするようにしてみます。

# app/controllers/staff/top_controller.rb                          
class Staff::TopController < ApplicationController
  
  def index
    raise
    render action: 'index'
  end

end

これでlocalhost:3000/staffなんかにアクセスするとRails標準のエラーページが出ると思います。

例外処理を書く

そしたら例外処理を書いてみます。共通のcontrollerに以下のように書きます。

# app/controller/top_controller
class ApplicationController < ActionController::Base

...

  rescue_from Exception, with: :rescue500

  private
  
...

  def rescue500(e)
    @exception = e
    render 'errors/internal_server_error', status: 500
  end
end

rescue_fromはcontrollerの中で発生した例外を処理する。

ここではrescue500メソッドを呼ぶように設定していて、rescure500はexceptionオブジェクトを引数としてエラーページをレンダリングするような内容になっています。この場合はapp/views/errors/internal_server_error.erbが標準では呼ばれます。

viewをつくる

さきほど指定したviewを作っていきます。

▶ vi app/views/errors/internal_server_error.slim                   
#error
  h1 500 Internal Server Error
  p We're very sorry for making a system error.

stylesheetは共通部分として作成します。

// app/assets/stylesheets/shared/errors.css.sass
$dark_gray: #666666
$very_light_gray: #fafafa

div#error
  width: 600px
  margin: 20px auto
  padding: 20px
  border-radius: 10px
  border: solid 4px $dark_gray
  background-color: $very_light_gray
  text-align: center
  p.url 
    font-family: monospace

sharedディレクトリのスタイルシートもsprocketsの読み込み対象とします。

/* app/assets/stylesheets/staff.css */
...
 *= require_tree ./shared
...

そしたらprecompileします。

$ bin/rake assets:precompile RAILS_ENV=production

そしてゲストOS側からサーバーを立ち上げて、localhost:3000/staffにアクセスしてみる...

さきほどデザインしたページが出てきて

We're very sorry for making a system error.

と表示されました!ナイス!!

流れまとめ

ということでここまでのエラー処理からエラー処理ページ作成までの流れをもう一度まとめておくと、

  1. controllerでraiseして例外を発生させる
  2. 共通のcontrollerで例外処理を行う振る舞いとそのメソッドを定義
  3. さっき作ったメソッドでrenderされるviewを作成

という感じで作ってきました。

次は403 Forbiddenつくっていきます!