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
の親である、 StandardError
を rescue
に指定すれば捕捉することができます
[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]