LoginSignup
88
78

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-04-21

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

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

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

  • irb … Ruby 言語処理系に組み込まれるコンソール
  • pryPry による OSS

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

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

まともな Rails プロジェクトなら Pry と pry-rails が導入されているはずです。以下、 Pry の機能を中心に紹介していきます。

Rails から Pry を使う

設定

Gemfile に以下のとおり書いて bundle install します。 (Railsの開発効率をあげる - Pryを使ってRailsのコンソールをパワーアップ & デバッグをする - Rails Webook より)

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の中でも使えます。

  binding.pry
<h1> ERB の例</h1>

<% binding.pry %>
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 以外の方法で定義されたメソッドも

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! プログラムを終了

exitexit! の違い

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

Pry 実行環境設定

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

コマンド連打

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

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

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

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

と操作すると、 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で使う createbuild が実行できるようになります。

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 のおかげで 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 を導入している場合、名前だけでなく引数も分かります。(関連: awesome_print を pry の表示のデフォルトに使う )

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 の機能ですが紹介します。ファイル名に続けて : と行数を指定することで、その行を含むスコープのテストだけ実行できます。

$ 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 によりいい感じに改行してくれます。
また grepgrep_v などと組み合わせるとより見やすくなります。

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

最後に

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

88
78
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
88
78