Rubyistよ、irbを捨ててPryを使おう


2011年 12月 26日

Pryは結構前からgithubのリポジトリを追いかけている人達には認知されていましたが、RailsCastsでも紹介されたことから、Ruby界で一気に広がりを見せています。
ちなみに発音はpra'i(ぷらい)です。英単語で「覗く」などを意味します。

今回はそんな便利なPryについて少し紹介したいと思います。

Pryはirbの代わりになるREPL

Pryを一言で説明すると、irbと同様にREPL環境を提供してくれます。
では、さっそくインストールしてみましょう。

$ gem install pry pry-doc

さて、これでインストールできました。Pryを起動しましょう。

$ pry
[1] pry(main)>

なんの変哲もないプロンプトですね。irb>からpry>に変わっただけです。
では、何か計算させてみましょうか。

[2] pry(main)> ->n{ i=0;j=1;n.times{j=i+i=j};i }.call(10)
=> 55

お気づきでしょうか? 式を評価した結果が色づけされていますよね。
irbでは評価した式の値を色づけする場合、wieble等、他のgemを使う必要がありましたが、Pryではデフォルトで色づけされています。
さらに設定ファイルから、自分好みの色に設定できます。

次はメソッドを書いてみましょう。

[3] pry(main)> def foo
[3] pry(main)*   ^

メソッドの定義を書いて改行すると自動でインデントされることがわかります。大変便利です。
もちろん、今まで通りirbと同じことがそのまま実現できます。

shellとの統合

Pryはshellと統合し、shellのコマンドをPryコンソール上で実行できます。
ただ、コマンドの前に’.’を付けて実行します。

[1] pry(main)> .ls
[2] pry(main)> .pwd
[3] pry(main)> .git log --pretty=oneline

従来irbではsystem()を使って評価していましたが、統合されたことにより煩わしさから解放されています。
また、統合されていることにより、次のようにRubyの#{}も評価して式展開してくれます。

[4] pry(main)> x = "class Hoge"
[5] pry(main)> .find . -name "*.rb" | xargs grep #{x}

組み込みコマンドのshell-modeを使うと、プロンプトにカレントディレクトリが表示され、tabキーでディレクトリ名を補完できるようになり、よりファイル操作が簡単になります。

[6] pry(main)> shell-mode
pry main:/Users/yoppi $ shell-mode
[8] pry(main)>

shell-modeを抜けるには、再度shell-modeコマンドを実行します。

組み込みコマンド

Pryにはいくつか組み込みコマンドが用意されています。
コマンドの一覧を見るにはPryコンソール上でhelpコマンドを実行してみましょう。

[1] pry(main)> help

以下に便利な組み込みコマンドの一部を紹介します。

  • hist: Pryコンソールで評価した過去の式の一覧を確認できます
  • ls: 現在のフレームで有効なオブジェクトを表示します。’デバッガとして使う’で詳しく紹介します
  • gem-install、gem-list: Pryコンソール上でgemをインストールしたり、gemの一覧を確認できます
  • gist-method: gemのgistをさらにラップしたものが使えます
  • shell-mode: Pryからシームレスにファイルにアクセスでき、Pryからファイルを操作したい場合はshell-modeになると便利です
  • !: Pryで評価しているフレームをリセットします
  • ri: riコマンドをPry上で実行します
  • show-doc: 対象メソッドのドキュメントを表示します
  • show-method: 対象メソッドの実装を表示します

やはりよく使うコマンドはhistlsでしょうか。

オブジェクトを調査する

Pry独自の概念として、オブジェクト間を移動できます。
デバッガのフレームに似ています。
lsコマンドで現在のオブジェクトの一覧を表示し、cdコマンドでそのオブジェクトに移動できます。

[1] pry(main)> a = "foo"
[2] pry(main)> ls
locals: _  _dir_  _ex_  _file_  _in_  _out_  _pry_  a  version
[3] pry(main)> cd a
[4] pry("foo"):1> nesting
Nesting status:
--
0. main (Pry top level)
1. "foo"
[5] pry("foo"):1> ls
Comparable methods: < <= > >= between?
String methods: ... 
[6] pry("foo"):1> upcase
=> "FOO"

この機能のおかげで、とある深くネストしたオブジェクトの調査もcdして潜っていけば、調査する範囲を狭めることができるので大変便利ですね。

簡易デバッガとして使う

Rubyのコードを書いていてある程度の量になってくると、個々のモジュールはRSpecによるユニットテストにがっちり守られていても、やはり、ソフトウェアを開発する以上デバッガの恩恵を受けたいものです。
往々にして、個々のモジュールを結合したときに、予想できなかった問題が発生するものだからです。
原因をすばやく追及するために、デバッガはプログラマにとって必要不可欠なツールでしょう。
実は、Pryの真の機能はREPLではなく、この先ほどのオブジェクトの調査機能にあるようにデバッガとしての機能によるものが多いです。

では、例としてRailsアプリを作っているときにデバッガで追いかけたいとしましょう。
適当なコントローラのアクションで次のコードを差し込みましょう。

binding.pry

そうすると、ここがブレークポイントになり、Pryコンソールが立ち上がります。
つまり自分でbinding.pryを評価する式を書くので、その式を評価するかどうかは、プログラマが自由に決定できるので、
条件付きブレークポイントを自由に書けることになります。

binding.pry if expression

さて、これでRailsを起動して アプリケーションを実行してみましょう。
binding.pryを書いたところで停止するはずです。
停止すると同時にPryコンソールが起動します。
まさにデバッガのように前後のコードが表示されつつ停止していることがわかるでしょう。
停止した時点で評価されたオブジェクトにアクセスできるので、デバッグを自由に進められます。
ステップ実行したければ、binding.pryを停止させたい箇所に埋め込みましょう。
Railsはconrollerに変更があれば再ロードするのでstep実行も簡単ですね。

普通のRubyアプリケーションであれば、edit-methodコマンドで現在のメソッド名を指定することでそのメソッドを編集でき、また、直接エディタで編集した場合reload-methodで読み込みなおすとstep実行できます。

ドキュメントに素早く参照できる

show-method、stat等のコマンドが用意されています。
また、シェル経由でriコマンドをそのまま使用できます。
VimやEmacs使い達は、それぞれのエディタ上で素早くドキュメントを牽けるようにしているはずですが、irbで作業しているときには牽けません。
Pryではこれらの組み込みコマンドが提供されていることで、素早くドキュメントにアクセスできます

[1] pry(main)> ri Array#sample
[2] pry(main)> require 'pathname'
[3] pry(main)> show-doc Pathname#children

gemやCの実装も読める

さて、Rubyのオブジェクトに対してメソッドを呼び出すわけですが、そのオブジェクトがどう実装されているか気になるときがあります。
特に、gemライブラリを使っているときに、APIがどんなことをしているのか素早く知りたいわけです。

[4] pry(main)> require 'nokogiri'
[5] pry(main)> show-method Nokogiri::HTML::Document.parse
From: /Users/yoppi/opt/local/ruby/ruby-1.9.3-p0/lib/ruby/gems/1.9.1/gems/nokogiri-1.4.4/lib/nokogiri/html/document.rb @ line 64:
Number of lines: 22
Owner: #<Class:Nokogiri::HTML::Document>
Visibility: public

def parse string_or_io, url = nil, encoding = nil, options = XML::ParseOptions::DEFAULT_HTML
...

また、Rubyの組み込みライブラリはCで実装されています。
よくRubyiest達はCで実装しているコードを眺めて、実装を楽しんだり、もっと改善できるところはなかな? と考えるときがあります。
Pryなら簡単に該当する組み込みライブラリのCのコードを読めます。
Cのコードを読むには、pry-docをインストールしておく必要があります。
たとえば、Array#sampleは同様に

[6] pry(main)> show-method Array#sample
static VALUE
rb_ary_sample(int argc, VALUE *argv, VALUE ary)
{
    VALUE nv, result, *ptr;
    long n, len, i, j, k, idx[10];
...

と、show-methodコマンドで牽くだけです。
これでC実装を素早く読めるので今まで実装を読まなかった人も読むようになり、Rubyの開発も活発になりそうです。
大変便利ですね。

Pryをカスタマイズする

さて、ここまでデフォルトの設定でPryを触ってきましたが、カスタマイズしてより便利にしましょう。
Pryのカスタマイズは、irbでは~/.irbrcで設定していたように、Pryも~/.pryrcファイルに設定します。
Pry-wiki Customizing Pryで詳しい説明があります。
ここではデフォルト設定されているもの以外で、知っておくと便利な機能を紹介します。

コマンドのprefix

組み込みコマンドに対してprefixを設定できます。この記事では直接コマンドを実行していましたよね。
しかし、そのコマンド名を変数名に使いたい場合があると困ることになります。
そこで、prefixを設定することで競合を避けられます。

Pry.config.command_prefix = "%"

と設定すると、コマンドを実行するときには’%’を付けて実行しなければ評価されないようになります。

[1] pry (main)> ls
NameError: undefined local variable or method `ls' for main:Object
[2] pry (main)> %ls
locals: _  _dir_  _ex_  _file_  _in_  _out_  _pry_  a  version

プロンプトの設定

僕は常にいくつかのRubyの版を切り替えて使っているので、irbを使用していたときもプロンプトにRubyのバージョンを表示させていました。
Pryでもプロンプトを柔軟にカスタマイズできるようになっています。
デフォルトのプロンプトは次のように設定されています。

DEFAULT_PROMPT = [
                proc { |target_self, nest_level, pry|
                  if nest_level == 0
                    "[#{pry.input_array.size}] pry(#{Pry.view_clip(target_self)})> "
                  else
                    "[#{pry.input_array.size}] pry(#{Pry.view_clip(target_self)}):#{nest_level}> "
                  end
                },

                proc { |target_self, nest_level, pry|
                  if nest_level == 0
                    "[#{pry.input_array.size}] pry(#{Pry.view_clip(target_self)})* "
                  else
                    "[#{pry.input_array.size}] pry(#{Pry.view_clip(target_self)}):#{nest_level}* "
                  end
                }
               ]

ではデフォルトのプロンプトに加えて、Rubyの版を表示させるように設定してみましょう。

Pry.config.prompt = [
  proc {|target_self, nest_level, pry|
    nested = (nest_level.zero?) ? '' : ":#{nest_level}" 
    "[#{pry.input_array.size}] #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}(#{Pry.view_clip(target_self)})#{nested}> "
  },
  proc {|target_self, nest_level, pry|
    nested = (nest_level.zero?) ?  '' : ":#{nest_level}"
    "[#{pry.input_array.size}] #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}(#{Pry.view_clip(target_self)})#{nested}* "
  }
]

pryを起動すると、

$ pry
[1] 1.9.3-p0(main)> 

Rubyの版が表示されてます。やりましたね。

参考文献

RailsCastsのビデオを一度視聴してみてください。Pryの使い方が一望できますし、説明もわかりやすいです。

より深く使いこなすにはドキュメントには一度目を通しましょう。ここで紹介してない機能もあります。