Ruby でシステム開発をする上で欠かせないのがデバッガの存在です。しばしば Ruby はデバッグがし辛い (動的型付け・ディスパッチのため) と言われますが、デバッガをうまく活用することで問題解決をスピーディにすることができます。
コンソールは何を使うべきか
いきなりですが Ruby のコンソールには2種類あります。
-
irb
… Ruby 言語処理系に組み込まれるコンソール -
pry
… Pry による 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!
プログラムを終了
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 には以下の記述があります。
Pry::Commands.command(/^$/, 'repeat last command') do
_pry_.run_command Pry.history.to_a.last
end
こう書いておくと、 Pry 実行中に何も入力しないで Enter を叩くと 直前のコマンドが実行される 機能が追加されます。
プログラムのデバッグ中、怪しいところの少し前に binding.pry
を書くことがしばしばあります。この場合、プログラムを進めるために
- 最初は
next
コマンドを実行 - 次に何も入力しないで Enter
- また何も入力しないで Enter
- …
と操作すると、 next
コマンドが次々に実行され、バグのある場所まで早く到達できます。 Pry にはヒストリー機能もありますが、 …と繰り返し打つより楽です。
この機能は Pry コマンドに対して有効で、上記の show-method
なども同様の事ができます。
FactoryGirl 動作確認
.pryrc に以下のコードを書いておくと、RSpecで使う create
や build
が実行できるようになります。
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
によりいい感じに改行してくれます。
また grep
や grep_v
などと組み合わせるとより見やすくなります。
ここでやっと some.rb の99行目でエラーを起こしている事が分かりました。これさえ分かれば修正はすぐそこです。ちなみにこの時はテストデータ (let
) の指定が不足していたためで、アプリのバグはありませんでした。めでたしめでたし。
最後に
Pry は非常に高機能なツールです。筆者本人も一部しか使いこなせていないので、お薦めの使い方があれば是非共有しましょう。