Edited at

[Rails] rescue_fromに関する留意点


はじめに

業務でrescue_fromメソッドの挙動について調べる機会があったので、記事にまとめておこうと思います。

もし内容に間違い等ありましたらご指摘いただけると幸いです。


rescue_fromはスタック形式で積まれる

rescue_fromを複数記述した場合、下から上の順に動作します。下記の場合、rescue_from FugaError->rescue_from HogeErrorの順に動作していきます。


application_controller.rb

class ApplicationController < ActionController::Base

rescue_from HogeError, with: :hoge_error_handler # 2番目に動作
rescue_from FugaError, with: :fuga_error_handler # 1番目に動作


rescue_fromは発生した例外クラスの継承元まで評価する

例えば下記のような場合、


application_controller.rb

class ApplicationController < ActionController::Base

rescue_from HogeError, with: :hoge_error_handler
rescue_from FugaError, with: :fuga_error_handler
# 以下省略


hoge_error.rb

class HogeError < FugaError  # HogeErrorクラス は FugaErrorクラス を継承している

# 以下省略


hoge.rb

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したFugaErrorrescue_from FugaErrorではなくrescue_from StandardErrorで引っかかることになります。


application_controller.rb

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はここで引っかかる
# 以下省略


fuga_error.rb

class FugaError < StandardError  # FugaErrorクラス は StandardErrorクラス を継承している

# 以下省略


fuga.rb

class Fuga

def raise_fuga_error
raise FugaError
end
end

このようなケースを危惧してか、Railsガイドには以下のように書かれています。


rescue_fromにExceptionやStandardErrorを指定すると、Railsでの正しい例外ハンドリングが阻害されて深刻な副作用が生じる可能性があります。よほどの理由がない限り、この指定はおすすめできません。



例外クラスをラップしている場合、外側の例外から順にrescue_fromのスタックを一巡していく


application_controller.rb

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
# 以下省略


foo.rb

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で引っかかることになります。


参考文献