TL;DR

rescueのデフォルト引数は StandardError

rescueで捕捉できるのは、引数に指定した例外クラス or 引数に指定した例外クラスを継承したクラス

rescueでは StandardError を捕捉するべき


[1] pry(main)> begin

[1] pry(main)* do_something()
[1] pry(main)* rescue => e
[1] pry(main)* puts 'ERROR', e # e is an exception object containing info about the error.
[1] pry(main)* end
ERROR
undefined method `do_something' for main:Object
=> nil

[2] pry(main)> begin

[2] pry(main)* do_something()
[2] pry(main)* rescue ActiveRecord::RecordNotFound => e
[2] pry(main)* puts 'ERROR', e # Only rescues RecordNotFound exceptions, or classes that inherit from RecordNotFound
[2] pry(main)* end
NoMethodError: undefined method `do_something' for main:Object
from (pry):7:in `<main>'

上記2つの違いは rescue の引数に ActiveRecord::RecordNotFound の有無です。

指定しない場合は undefined method `do_something' for main:Object が捕捉されます。

指定した場合は捕捉されず、begin end後に例外が発生します。

なぜなら、 ActiveRecord::RecordNotFound を引数に指定した場合、 ActiveRecord::RecordNotFound か、 ActiveRecord::RecordNotFound を継承したエラーのみ、 rescue で捕捉することができるためです。

NoMethodError は、 ActiveRecord::RecordNotFound を継承していない、かつ、 ActiveRecord::RecordNotFound ではないので捕捉することができません。

補足すると、NoMethodErrorは

StandardError > NameError > NoMethodError

ActiveRecord::RecordNotFoundは

StandardError > ActiveRecord::ActiveRecordError

となっています。

話がそれましたが、たとえば NoMethodError の親である、 StandardErrorrescue に指定すれば捕捉することができます

[5] pry(main)> begin

[5] pry(main)* do_something()
[5] pry(main)* rescue StandardError => e
[5] pry(main)* puts 'ERROR', e # Only rescues RecordNotFound exceptions, or classes that inherit from RecordNotFound
[5] pry(main)* end
ERROR
undefined method `do_something' for main:Object
=> nil

さらに、 StandardError の親である、 Exception を指定しても同じです。

[9] pry(main)> begin

[9] pry(main)* do_something()
[9] pry(main)* rescue Exception => e
[9] pry(main)* puts 'ERROR', e # Only rescues RecordNotFound exceptions, or classes that inherit from RecordNotFound
[9] pry(main)* end
ERROR
undefined method `do_something' for main:Object
=> nil

Exception の親となる例外のクラスは存在しません。

だとすると、エラーの親である、 Exception を捕捉すればいいのではないかと考えますが、それは良くないです。

なぜなら、 Exception はシステムレベルの例外も含まれます。アプリケーションレベルの例外を捉えたいのではないでしょうか。

例として、ctrl + c で実行中のプログラムを終了する場合、そこで処理は終了するべきですが、このように書くと終了しません。

[10] pry(main)> 1.upto(10) do |i|

[10] pry(main)* begin
[10] pry(main)* p i
[10] pry(main)* sleep(1)
[10] pry(main)* rescue Exception => e
[10] pry(main)* p e.class
[10] pry(main)* p "catch!"
[10] pry(main)* end
[10] pry(main)* end
1
2
3
^CInterrupt # ctrl + c を押した
"catch!"
4
5
^CInterrupt # ctrl + c を押した
"catch!"
6
7
^CInterrupt # ctrl + c を押した
"catch!"
8
9
^CInterrupt # ctrl + c を押した
"catch!"
10
=> 1

このような動作は想定していないと思います。

また、このような例もあります。 Exception を継承した、例外クラスを捕捉します。

[12] pry(main)> class MyError1 < Exception; end

=> nil
[13] pry(main)> begin
[13] pry(main)* raise MyError1
[13] pry(main)* rescue => e
[13] pry(main)* puts "Exception handled! #{e}"
[13] pry(main)* end
MyError1: MyError1
from (pry):32:in `<main>'

間違えました。これでは Exception を捕捉できません。

rescue に引数を指定しない場合、デフォルトで StandardError が引数になるからです。

今回の場合、引数を指定しないので、 StandardError が引数です。

Exception > StandardError

となっているため、 Exception を捕捉することができませんでした。なので

[15] pry(main)> class MyError1 < Exception; end

=> nil
[16] pry(main)> begin
[16] pry(main)* raise MyError1
[16] pry(main)* rescue Exception => e
[16] pry(main)* puts "Exception handled! #{e}"
[16] pry(main)* end
Exception handled! MyError1
=> nil

このようにすることで、 Exception を捕捉することができます。


参考にさせていただきました

https://qiita.com/ngron/items/4c319ae7340b72c7e566

http://ruby-doc.com/docs/ProgrammingRuby/

https://blog.toshimaru.net/ruby-standard-error/

https://qiita.com/tsubasakat/items/6825bcefcad26da3471b

https://www.honeybadger.io/blog/ruby-exception-vs-standarderror-whats-the-difference/

下記の通りすると、例外クラスを見ることができます。(2パターン)

1 @scivola さんに教えてくいただきました

exceptions = []

tree = {}
ObjectSpace.each_object(Class) do |cls|
next unless cls.ancestors.include? Exception
next if exceptions.include? cls
next if cls.superclass == SystemCallError # avoid dumping Errno's
exceptions << cls
cls.ancestors.delete_if {|e| [Object, Kernel].include? e }.reverse.inject(tree) {|memo,cls| memo[cls] ||= {}}
end
tree_printer = Proc.new do |t, level = 0|
t.sort_by { |c,| c.name }.each do |c, v|
puts (' ' * level) + c.to_s
tree_printer[v, level + 1]
end
end
tree_printer[tree]

2 http://blog.nicksieger.com/articles/2006/09/06/rubys-exception-hierarchy/

exceptions = []

tree = {}
ObjectSpace.each_object(Class) do |cls|
next unless cls.ancestors.include? Exception
next if exceptions.include? cls
next if cls.superclass == SystemCallError # avoid dumping Errno's
exceptions << cls
cls.ancestors.delete_if {|e| [Object, Kernel].include? e }.reverse.inject(tree) {|memo,cls| memo[cls] ||= {}}
end

indent = 0
tree_printer = Proc.new do |t|
t.keys.sort { |c1,c2| c1.name <=> c2.name }.each do |k|
space = (' ' * indent); space ||= ''
puts space + k.to_s
indent += 2; tree_printer.call t[k]; indent -= 2
end
end
tree_printer.call tree

BasicObject

ActiveSupport::Tryable
JSON::Ext::Generator::GeneratorMethods::Object
PP::ObjectMixin
ActiveSupport::Dependencies::Loadable
ActiveSupport::ToJsonWithActiveSupportEncoder
ActiveSupport::Dependencies::Blamable
Exception
CGI::InvalidEncoding
EventMachine::FileNotFoundException
IRB::Abort
MonitorMixin::ConditionVariable::Timeout
NoMemoryError
OAuth::RequestProxy::UnknownRequestType
OAuth::Signature::UnknownSignatureMethod
Pry::InputLock::Interrupt
ScriptError
LoadError
AbstractController::Helpers::MissingHelperError
ActionController::MissingRenderer
CodeRay::PluginHost::HostNotFound
CodeRay::PluginHost::PluginNotFound
FFI::NotFoundError
FFI::PlatformError
Gem::LoadError
Gem::ConflictError
Gem::MissingSpecError
Gem::MissingSpecVersionError
NotImplementedError
SyntaxError
Dotenv::FormatError
SecurityError
SignalException
Interrupt
Sidekiq::Shutdown
StandardError
AbstractController::ActionNotFound
AbstractController::Error
AbstractController::DoubleRenderError
ActionController::ActionControllerError
ActiveModel::ValidationError
ActiveRecord::ActiveRecordError
ActiveRecord::RecordNotFound
NameError
NoMethodError
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~省略
Zlib::Error
Zlib::BufError
Zlib::DataError
Zlib::GzipFile::Error
Zlib::GzipFile::CRCError
Zlib::GzipFile::LengthError
Zlib::GzipFile::NoFooter
Zlib::MemError
Zlib::NeedDict
Zlib::StreamEnd
Zlib::StreamError
Zlib::VersionError
SystemExit
Gem::SystemExitException
SystemStackError
fatal
=> [BasicObject]