はじめに
業務でrescue_fromメソッドの挙動について調べる機会があったので、記事にまとめておこうと思います。
もし内容に間違い等ありましたらご指摘いただけると幸いです。
rescue_fromはスタック形式で積まれる
rescue_fromを複数記述した場合、下から上の順に動作します。下記の場合、rescue_from FugaError->rescue_from HogeErrorの順に動作していきます。
class ApplicationController < ActionController::Base
rescue_from HogeError, with: :hoge_error_handler # 2番目に動作
rescue_from FugaError, with: :fuga_error_handler # 1番目に動作
rescue_fromは発生した例外クラスの継承元まで評価する
例えば下記のような場合、
class ApplicationController < ActionController::Base
rescue_from HogeError, with: :hoge_error_handler
rescue_from FugaError, with: :fuga_error_handler
# 以下省略
class HogeError < FugaError # HogeErrorクラス は FugaErrorクラス を継承している
# 以下省略
class Hoge
def raise_hoge_error
raise HogeError
end
end
raise_hoge_errorメソッドでraiseしたHogeErrorは、FugaErrorクラスを継承しているためrescue_from FugaErrorで引っかかり、fuga_error_handlerが実行されることになります。
このため、例えば**rescue_from StandardErrorなどを下の方に書いてしまうと、StandardErrorクラスを継承した例外クラスは全てrescue_from StandardErrorで引っかかることになります**。
下記の場合、raiseしたFugaErrorはrescue_from FugaErrorではなくrescue_from StandardErrorで引っかかることになります。
class ApplicationController < ActionController::Base
rescue_from HogeError, with: :hoge_error_handler
rescue_from FugaError, with: :fuga_error_handler
rescue_from StandardError, with: :standard_error_handler # raise_fuga_errorメソッドでraiseしたFugaErrorはここで引っかかる
# 以下省略
class FugaError < StandardError # FugaErrorクラス は StandardErrorクラス を継承している
# 以下省略
class Fuga
def raise_fuga_error
raise FugaError
end
end
このようなケースを危惧してか、Railsガイドには以下のように書かれています。
rescue_fromにExceptionやStandardErrorを指定すると、Railsでの正しい例外ハンドリングが阻害されて深刻な副作用が生じる可能性があります。よほどの理由がない限り、この指定はおすすめできません。
例外クラスをラップしている場合、外側の例外から順にrescue_fromのスタックを一巡していく
class ApplicationController < ActionController::Base
rescue_from FooError, with: :foo_error_handler
rescue_from HogeError, with: :hoge_error_handler
rescue_from FugaError, with: :fuga_error_handler
# 以下省略
class Foo
def error_wrap
raise FooError # FooErrorクラス は StandardErrorクラス を継承しているものとする
rescue
raise BarError # FooErrorクラス を BarErrorクラス でラップ
end
end
error_wrapメソッドが実行されたとき、まずはBarErrorクラスがrescue_fromのスタックを一巡します。上記の場合はどのrescue_fromにも引っかからないため、続けてラップの内側のFooErrorクラスがrescue_fromのスタックを一巡し、最終的にrescue_from FooErrorで引っかかることになります。