[Ruby][初心者向け] コンソールで例外が発生したときは、バックトレースを表示して原因を調べよう


# TL;DR

* コンソール (irb や pry) で例外が発生しても、バックトレースが表示されないので原因がわからないことがある。
* コンソールで例外時のバックトレースを表示するには
    * 特殊変数 [$@](https://docs.ruby-lang.org/ja/latest/method/Kernel/v/=40.html) を参照する。
    * [wtf](https://github.com/pry/pry/wiki/Exceptions#using-the-wtf-command) コマンドを使う ([pry](https://github.com/pry/pry) 限定) 。
        * ちなみに、現実世界の会話では `wtf (What the Fxck!)` なんてあまり使わないように 🙅‍♀️

# バージョン情報

```shell-session
$ ruby -v
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin18]

$ bin/rails -v
Rails 6.0.1  
```

# 実例

以下のモデルを作成した。

```app/models/hunter.rb
class Hunter < ApplicationRecord
  validates name, presence: true
end
```

早速 `bin/rails c` (irb) で動作を確認してみると謎のエラーが発生した。

```irb
irb(main):008:0> Hunter.create!(name: 'クラピカ')
Traceback (most recent call last):
        1: from (irb):8
NoMethodError (undefined method `Hunter' for #<Hunter:0x00007f8a322f52d8>)
```

Hunter#Hunter というメソッドがないって言われる……。なんだこの [HUNTER×HUNTER](https://www.shonenjump.com/j/rensai/hunter.html) みたいなへんちくりんなエラーは 🤔

それにしても、他に情報が何も出力されないのでエラーの原因がさっぱりわからない。こういうときはメソッドの呼び出し状況であるバックトレースを出力する。Ruby では [$@](https://docs.ruby-lang.org/ja/latest/method/Kernel/v/=40.html) という特殊変数でバックトレースを参照することができる。

```irb
irb(main):002:0> puts $@
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
# 略
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activerecord-6.0.1/lib/active_record/persistence.rb:55:in `create!'
(irb):8:in `irb_binding'
# 略
-e:1:in `<main>'
=> nil
```

バックトレースに `validate` というキーワードがあったのでバリデーション周りを疑ってみる。すると本来シンボルであるはずの validates の引数がシンボルになっていなかったのが原因だと判明した！

```app/models/hunter.rb
class Hunter < ApplicationRecord
  # name が self.name すなわち Hunter.name と解釈されて "Hunter" を返していた。
  # name を :name に修正した。
  validates :name, presence: true
end
```

しかしこの方法はちょっと不便だ。もう一度 `$@` を参照しようとすると空になってしまう。

```irb
irb(main):002:0> puts $@
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
# 略
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activerecord-6.0.1/lib/active_record/persistence.rb:55:in `create!'
(irb):8:in `irb_binding'
# 略
-e:1:in `<main>'
=> nil
irb(main):003:0> puts $@

=> nil
```

そこで irb をより強力にしたツールである pry を使う。Gemfile に `pry-rails` を追加して bundle install しておく。

```rb:Gemfile
group :development, :test do
  gem 'pry-rails'
end
```

`bin/rails c` で動作を確認してみる。

```irb
[1] pry(main)> Hunter.create!(name: 'クラピカ')
NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
from /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
```

`wtf` と入力すると、例外のバックトレースが表示される。すべてではなく先頭から数行が表示される。

```irb
[2] pry(main)> wtf
Exception: NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
--
0: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
1: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
2: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
3: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
4: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:429:in `block in make_lambda'
```

もう一度入力してもバックトレースが表示される。おもしろいことに `wtf!` や `wtf!!?` のように `wtf` の後に `!` や `?` をつけると、つけた量に比例して表示されるバックトレースの行数が増える。

```
[3] pry(main)> wtf!
Exception: NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
--
0: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
1: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
2: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
3: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
4: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:429:in `block in make_lambda'
5: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:201:in `block (2 levels) in halting'
6: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:607:in `block (2 levels) in default_terminator'
7: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:606:in `catch'
8: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:606:in `block in default_terminator'
9: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:202:in `block in halting'
```

バックトレースを最後まで表示したい場合は `wtf -v` と入力する。

```irb
[4] pry(main)> wtf -v
Exception: NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
--
 0: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
 1: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
 2: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
 3: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
# 略
73: /Users/killua/.rbenv/versions/2.6.5/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
74: /Users/killua/.rbenv/versions/2.6.5/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
75: -e:1:in `<main>'
```

# 参考

* [Ruby リファレンスマニュアル » Kernel モジュール » $@](https://docs.ruby-lang.org/ja/latest/method/Kernel/v/=40.html)
* [pry/pry Wiki » Exceptions](https://github.com/pry/pry/wiki/Exceptions)