Ruby / Rails デバッガことはじめ

Ruby でシステム開発をする上で欠かせないのがデバッガの存在です。しばしば Ruby はデバッグがし辛い (動的型付け・ディスパッチのため) と言われますが、デバッガをうまく活用することで問題解決をスピーディにすることができます。

# コンソールは何を使うべきか

いきなりですが Ruby のコンソールには2種類あります。

* `irb` … Ruby 言語処理系に組み込まれるコンソール
* `pry` … [Pry](https://github.com/pry/pry) による OSS

どちらを使うべきか、考えるまでもなく **Pry一択** です。

- 機能が多い (この記事で説明)
- シンタックスハイライト

まともな Rails プロジェクトなら Pry と [pry-rails](https://github.com/rweng/pry-rails) が導入されているはずです。以下、 Pry の機能を中心に紹介していきます。

## Rails から Pry を使う

### 設定

Gemfile に以下のとおり書いて `bundle install` します。 ([Railsの開発効率をあげる - Pryを使ってRailsのコンソールをパワーアップ & デバッグをする - Rails Webook](http://ruby-rails.hatenadiary.com/entry/20141024/1414081224) より)

```Gemfile
group :development, :test do
  gem 'pry-rails'  # rails console(もしくは、rails c)でirbの代わりにpryを使われる
  gem 'pry-doc'    # methodを表示
  gem 'pry-byebug' # デバッグを実施(Ruby 2.0以降で動作する)
  gem 'pry-stack_explorer' # スタックをたどれる
end
```

### pry コマンド

ターミナルで `pry` を実行すると起動します。

### コンソール

`rails console` (`rails c`) を実行すると、上記の Gem が有効であれば `irb` ではなく `pry` が起動します。

### ブレークポイント

プログラムの途中に `binding.pry` と書いておけば、そこがブレークポイントになって対話コンソールが起動します。Viewの中でも使えます。

```ruby
  binding.pry
```

```erb
<h1> ERB の例</h1>

<% binding.pry %>
```

```slim
h1 Slim の例

- binding.pry
```

この時 `rails server` (`rails s`) したコンソール画面で対話コンソールが起動します。これはデーモンモード (`rails server -d`) だと無視されるので、デバッグしたい場合はオプションなしの `rails server` を使うべきでしょう。

# Pry コマンド

## show-method

メソッドの定義を表示できます。

```
Ruby:2.3.3 - Rails:5.1.0.beta1 - pry(main)> show-method ActiveRecord::Base.transaction

From: /Users/***/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/activerecord-5.1.0.beta1/lib/active_record/transactions.rb @ line 209:
Owner: ActiveRecord::Transactions::ClassMethods
Visibility: public
Number of lines: 3

def transaction(options = {}, &block)
  connection.transaction(options, &block)
end
```

また、このような `def` 以外の方法で定義されたメソッドも

```ruby
class Account < ActiveRecord::Base
  has_one :profile
end
```

この通り表示してくれます。

```
Ruby:2.3.3 - Rails:5.1.0.beta1 - pry(main)> show-method Account#profile

From: /Users/***/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/activerecord-5.1.0.beta1/lib/active_record/associations/builder/association.rb @ line 110:
Owner: Account::GeneratedAssociationMethods
Visibility: public
Number of lines: 3

def #{name}(*args)
  association(:#{name}).reader(*args)
end
```

※インスタンスメソッドを記号 `#` に続けて表す記法は、ドキュメントではよく使いますが Ruby 言語の仕様にはありません。 `show-method` コマンドは Ruby とは別の Pry の機能です。

Ruby のコアなメソッドは C 言語で実装されています。この場合 `show-method` は C ソースコードを表示します。ご参考までに。

```
Ruby:2.3.3 - pry(main)> show-method Fixnum#+

From: numeric.c (C Method):
Owner: Fixnum
Visibility: public
Number of lines: 28

static VALUE
fix_plus(VALUE x, VALUE y)
{
    if (FIXNUM_P(y)) {
        long a, b, c;
        VALUE r;

        a = FIX2LONG(x);
        b = FIX2LONG(y);
        c = a + b;
        r = LONG2NUM(c);

        return r;
    }
    else if (RB_TYPE_P(y, T_BIGNUM)) {
        return rb_big_plus(y, x);
    }
    else if (RB_TYPE_P(y, T_FLOAT)) {
        return DBL2NUM((double)FIX2LONG(x) + RFLOAT_VALUE(y));
    }
    else if (RB_TYPE_P(y, T_COMPLEX)) {
        VALUE rb_nucomp_add(VALUE, VALUE);
        return rb_nucomp_add(y, x);
    }
    else {
        return rb_num_coerce_bin(x, y, '+');
    }
}
```

## 実行制御

- `next` 次の行に移動、実行
- `exit` 次のブレークポイントまで実行
- `exit!` プログラムを終了

### `exit` と `exit!` の違い

ターミナルから `pry` や `rails console` で起動した場合はほとんど同じ動作ですが、プログラム実行中に `binding.pry` で止まった場合は違いがあります。
複数の `binding.pry` を書いた場合や `each` ループの中に `binding.pry` があるなどブレークポイントが複数ある場合、 `exit` だと次の `binding.pry` で止まりますが、 `exit!` だと一発でプログラムから出られます。
また、RSpecから実行した場合、 `exit` で終了した場合はテストの評価が行われますが、 `exit!` で終了した場合はそれもなく即座に終了します。

## Pry 実行環境設定

Ruby ソースコードを作成し、 Rails プロジェクトのルートディレクトリに **.pryrc** という名前で置いておくと、 Pry 実行前に読み込まれます。頻繁に実行する処理は .pryrc に書いておくと便利です。

### コマンド連打

弊社の .pryrc には以下の記述があります。

```ruby
Pry::Commands.command(/^$/, 'repeat last command') do
  _pry_.run_command Pry.history.to_a.last
end
```

こう書いておくと、 Pry 実行中に何も入力しないで Enter を叩くと **直前のコマンドが実行される** 機能が追加されます。
プログラムのデバッグ中、怪しいところの少し前に `binding.pry` を書くことがしばしばあります。この場合、プログラムを進めるために

1. 最初は `next` コマンドを実行
1. 次に何も入力しないで Enter
1. また何も入力しないで Enter
1. …

と操作すると、 `next` コマンドが次々に実行され、バグのある場所まで早く到達できます。 Pry にはヒストリー機能もありますが、 :arrow_up: :leftwards_arrow_with_hook: :arrow_up: :leftwards_arrow_with_hook: :arrow_up: :leftwards_arrow_with_hook: …と繰り返し打つより楽です。
この機能は Pry コマンドに対して有効で、上記の `show-method` なども同様の事ができます。


### FactoryGirl 動作確認

.pryrc に以下のコードを書いておくと、RSpecで使う `create` や `build` が実行できるようになります。

```rb
include FactoryGirl::Syntax::Methods
```

この場合、 `create` を呼ぶとローカルのDBに本当に追加されるので、コンソール起動時に `--sandbox` オプションを追加することをお勧めします。

## ルーティング確認

pry-rails の機能になります。コマンド起動時に `show-routes` と実行するとルーティングが確認できます。

```
Ruby:2.3.3 - Rails:5.1.0.beta1 - pry(main)> show-routes
                                       Prefix Verb   URI Pattern                                                                  Controller#Action
                                     pub_root GET    /                                                                            pub/top#index
                                      pub_pre GET    /pre(.:format)                                                               pub/top#pre
                                      pub_faq GET    /faq(.:format)                                                               pub/static#faq
(以下略)
```

ルーティングを確認するためにコンソールを閉じる必要がない上、 [Spring](https://github.com/rails/spring) のおかげで `rake routes` より速いです。

## OSコマンド実行

Pry を使っていると、コンソールを一旦終了させてターミナルのコマンドを実行したくなる時があると思います。しかしこの場合、コンソールを終了させなくても以下のとおり `.` に続けてコマンドを入力すればOSコマンドとして実行されます。

```
Ruby:2.3.3 - Rails:5.1.0.beta1 - pry(main)> .ls app/views
errors layouts shared kaminari pub some_mailer
(以下略)
```

ただし、ファイル/ディレクトリ名の Tab キーサジェストは効かないので若干不便です。

## methods, instance_methods メソッド

Pry ではなく Ruby の機能ですが、レシーバーのメソッドを一覧表示できます。
 [awesome_print](https://github.com/awesome-print/awesome_print) を導入している場合、名前だけでなく引数も分かります。(関連: [awesome_print を pry の表示のデフォルトに使う](http://qiita.com/Linda_pp/items/d75d7c3953faa34a1f0e) )

### grep メソッド

ある名前のメソッドがありそうだがはっきりと思い出せない場合、 `grep` で候補を表示できます。

```
Ruby:2.3.3 - pry(main)> String.methods.grep /^to_/
[
    [0]            to_enum(*arg1)    Class (Kernel)
    [1]               to_s()         Class (Module)
    [2]            to_yaml(*options) Class (Object)
    [3] to_yaml_properties()         Class (Object)
]
Ruby:2.3.3 - pry(main)> String.instance_methods.grep /^to_/
[
    [0]               to_c()         String (unbound)
    [1]            to_enum(*arg1)    Class (Kernel)
    [2]               to_f()         String (unbound)
    [3]               to_i(*arg1)    String (unbound)
    [4]               to_r()         String (unbound)
    [5]               to_s()         Class (Module)
    [6]             to_str()         String (unbound)
    [7]             to_sym()         String (unbound)
    [8]            to_yaml(*options) Class (Object)
    [9] to_yaml_properties()         Class (Object)
]
```

## 特定のテストだけ実行

これも Pry ではなく Ruby のテストフレームワーク [RSpec](https://relishapp.com/rspec) の機能ですが紹介します。ファイル名に続けて `:` と行数を指定することで、その行を含むスコープのテストだけ実行できます。

```
$ rspec spec/models/some_spec.rb:8
Run options: include {:locations=>{"./spec/models/some_spec.rb"=>[8]}}

Randomized with seed 42283

(中略)

Finished in 2.4 seconds (files took 23.94 seconds to load)
1 example, 0 failures

Randomized with seed 42283

Coverage report generated for RSpec to /Users/***/Repositories/***/coverage. 152 / 11787 LOC (1.29%) covered.
Coverage report Rcov style generated for RSpec to /Users/***/Repositories/***/coverage/rcov
```

# 実際のデバッグ

Rails の Controller や API のバグは、エラーメッセージに情報が少ないのでしばしば判定が難しいです。 API 開発中の例を挙げます。

```
Failures:

  1) (テストケース)
     Failure/Error: expect(response.body).to match_json_expression(expected_json)
     
       expected {"error"=>{"code"=>500, "reason"=>"internalServerError", "message"=>"Internal Server Error", "errors"=>[]}} to match JSON expression #<JsonExpressions::Matcher:0x007fed9e155318 @json={:some_id=>6, :some_token=>"d831b4f301ffdf7032972d632b0e3bc1431b70c9"}, @options={}, @last_error="(JSON ROOT) does not contain the key calling_transaction_id", @captures={}>
       (JSON ROOT) does not contain the key calling_transaction_id
     Shared Example Group: ...(以下略)
```

このログだと「500 Internal Server Error が出た」事しか分からず、具体的に
 `app/` 中のどこを修正すればいいか読み取れません。
こういう時、私がよく使っている方法を紹介します。

## ブレークポイントの設定

Internal Server Error という事は API までは処理が通っているので、 API の先頭に `binding.pry` を置きます。

```
Frame number: 0/139

From:
(ソースコード)

    27:     patch ':id/' do
    28:       binding.pry
 => 29:       # 何かの処理

(中略)

Ruby:2.3.3 - Rails:5.1.0.beta1 - pry(#<#<Class:0x007fa577f85100>>)> 
```

この時、上記の「特定のテストだけ実行」と組み合わせて落ちるテストだけ指定すると、余計なところで止まらなくて済みます。

## エラーが出るまで next

次に `next` を連打します。500を起こしている場合、途中でエラーハンドラに飛ぶはずです。

```
From: /Users/***/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/grape-0.17.0/lib/grape/middleware/error.rb @ line 37 Grape::Middleware::Error#call!:

    29: def call!(env)
    30:   @env = env
    31: 
    32:   begin
    33:     error_response(catch(:error) do
    34:       return @app.call(@env)
    35:     end)
    36:   rescue StandardError => e
 => 37:     is_rescuable = rescuable?(e.class)
    38:     if e.is_a?(Grape::Exceptions::Base) && (!is_rescuable || rescuable_by_grape?(e.class))
    39:       handler = ->(arg) { error_response(arg) }
    40:     else
    41:       raise unless is_rescuable
    42:       handler = find_handler(e.class)
    43:     end
    44: 
    45:     handler.nil? ? handle_error(e) : exec_handler(e, &handler)
    46:   end
    47: end
```

ここで初めて、エラー情報が格納されている変数 (`e` だったり `exception` だったり) が使えるようになります。

## バックトレース表示

`e` の中身を見てみると

```
Ruby:2.3.3 - Rails:5.1.0.beta1 - pry(#<#<Class:0x007fa376eda028>>)> e
#<NoMethodError: undefined method `-@' for nil:NilClass>
```

と、どこかの変数が想定外の `nil` になっている事までは分かります。どこで `nil` になっているかは次のメソッドで分かります。

```
Ruby:2.3.3 - Rails:5.1.0.beta1 - pry(#<#<Class:0x007fa376eda028>>)> puts e.backtrace
/Users/***/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/activesupport-5.1.0.beta1/lib/active_support/core_ext/time/calculations.rb:159:in `ago'
/Users/***/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/activesupport-5.1.0.beta1/lib/active_support/time_with_zone.rb:306:in `rescue in -'
/Users/***/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/activesupport-5.1.0.beta1/lib/active_support/time_with_zone.rb:306:in `-'
/Users/***/Repositories/***/app/models/some.rb:99:in `block in cancel_call_of'
(以下略)
```

**ポイント:** `puts` を付けた方が見やすいです。 `e.backtrace` は String の Array を返すため、そのまま表示すると見づらいですが `puts` によりいい感じに改行してくれます。
また `grep` や `grep_v` などと組み合わせるとより見やすくなります。

ここでやっと some.rb の99行目でエラーを起こしている事が分かりました。これさえ分かれば修正はすぐそこです。ちなみにこの時はテストデータ (`let`) の指定が不足していたためで、アプリのバグはありませんでした。めでたしめでたし。

# 最後に

Pry は非常に高機能なツールです。筆者本人も一部しか使いこなせていないので、お薦めの使い方があれば是非共有しましょう。