Shibuya.rb(2/15)でPryの活用について発表

久々の更新。
もうちょっと筆まめにならないと。


今日は、Shibuya.rbに参加してきました。
今回のメインのテーマはRackミドルウェアを読むってことで、
各自、読みたいミドルを選んで、テーブルに分かれて、
それぞれ読んでみるという感じです。


私は、Rack::Auth::Basicを読んでました。
シンプルですぐに完結しているので読みやすい感じだったと思います。
私のテーブルでは、皆Macだったので、画面共有を使って、
同じ画面で一緒にソースを見て、順に追っていくという感じで、読んでみました。
テーブルごとにモニタがあれば、一緒に読む時にはかなり捗るんだろうけど。


中盤にLT枠があったので、突発で資料も無しにPryについて話をさせてもらいました。
テーマがソースコードリーディングだったので、、
最近、ソースコードを読んだり、Railsでspec書いたりする時に、
これは便利だ!と感じたことを話すいい機会だと思ったわけです。
というわけで、ざっくりと話した内容について書いてみます。


pryを起動したら、まずはhelpと打ってみましょう。
ずらずらとコマンドが出てきます。
この中で、メインで活用したいコマンドは以下です。

  • cd: 引数で指定したオブジェクトのコンテキストに移動する
  • ls: 自分が今居るコンテキストで参照できるメソッド、変数を一覧で見る
  • show-method: 引数で指定したメソッドの定義されているコードを見る。(ファイル、行数も分かる)
  • edit: エディタでpryに入力するコードを記述できる。
  • edit-method: 引数で指定したメソッドを定義しているファイルをエディタで開く。


cdとlsはこんな感じで使えます。

[5] pry(main)> a=[]
=> []
[6] pry(main)> cd a
[7] pry(#<Array>):1> ls
Enumerable#methods: all?  any?  chunk  collect_concat  detect  each_cons  each_entry  each_slice  each_with_index  each_with_object  entries  find  find_all  flat_map  grep  group_by  inject  max  max_by  member?  min  min_by  minmax  minmax_by  none?  one?  partition  reduce  slice_before  sort_by
Array#methods: &  *  +  -  <<  <=>  ==  []  []=  assoc  at  clear  collect  collect!  combination  compact  compact!  concat  count  cycle  delete  delete_at  delete_if  drop  drop_while  each  each_index  empty?  eql?  fetch  fill  find_index  first  flatten  flatten!  frozen?  hash  include?  index  insert  inspect  join  keep_if  last  length  map  map!  pack  permutation  place  pop  pretty_print  pretty_print_cycle  product  push  rassoc  reject  reject!  repeated_combination  repeated_permutation  replace  reverse  reverse!  reverse_each  rindex  rotate  rotate!  sample  select  select!  shelljoin  shift  shuffle  shuffle!  size  slice  slice!  sort  sort!  sort_by!  take  take_while  to_a  to_ary  to_s  transpose  uniq  uniq!  unshift  values_at  zip  |
self.methods: __binding_impl__
locals: _  _dir_  _ex_  _file_  _in_  _out_  _pry_

(実際には戻り値には色が付いてます。)


こんな感じで、配列のインスタンスにコンテキストに入ってlsを実行すると、
配列が持っているインスタンスメソッドや、インクルードしているモジュールで定義されているメソッド、
他にインスタンス変数なんかも参照できます。
また、ls -vを使うと、スーパークラスも含めてメソッドを参照できます。
irbで、pp hoge.methods.sortとかやることがあると思いますが、
pryのコマンドは定義されているコンテキストごとにまとまって表示されるため、非常に見やすいところがいい感じです。


show-methodはこんな感じで使います。

[3] pry(User):1> show-method database_authenticatable 

From: /Users/joker/pasokara_player_rails3/vendor/bundle/ruby/1.9.1/gems/devise-2.0.0/lib/devise/schema.rb @ line 16:
Number of lines: 8
Owner: Devise::Schema
Visibility: public

def database_authenticatable(options={})
  null    = options[:null] || false
  default = options.key?(:default) ? options[:default] : ("" if null == false)
  include_email = !respond_to?(:authentication_keys) || self.authentication_keys.include?(:email)

  apply_devise_schema :email,              String, :null => null, :default => default if include_email
  apply_devise_schema :encrypted_password, String, :null => null, :default => default, :limit => 128
end


このように、メソッドが定義されているファイル、行数と定義内容が参照できます。
これは、MongoidをインクルードしているUserクラスに入り、deviseの認証定義をshow-methodしたところです。
この参照結果だけ見て、内容が全部分かることはあまり無いと思いますが、
ざっくりとした動作イメージの把握はできますし、
定義場所が明確に分かるので、細かく知りたい時は、コードを読む起点として活用できます。
ここで、edit-methodを使って、database_authenticatableを引数に渡すと、
環境変数EDITORで指定しているエディタでこの定義場所を開くことができます。
このまま付近を読んでもいいし、そのまま閉じれば、pryに戻ってくることができます。


editはpryで実行するrubyのコードをそのままエディタで記述して処理できるもので、
irbにおけるinteractie_editorと同様なので、詳しい説明は省きます。


これだけでもかなり便利なのですが、
pryには更に強力な機能があります。


今回、Rackミドルウェアを読む際にも活用しましたが、
コード中にbinding.pryと記述することで、
そのタイミングでインタラクティブモードに移行して
pry上でコードを実行できるようになります。


今回だとこんな感じ。

require "rubygems"
require "rack"
require "pry"

class Hello
  def call(env)
    binding.pry
    [200, {"Content-Type" => "text/plain"}, ["Hello World"]]
  end
end

use Rack::Auth::Basic do |user, pass|
  user == "test" && pass == "password"
end

run Hello.new


rackupしてアクセスしてみるとこうなります。

[2012-02-16 02:02:52] INFO  WEBrick 1.3.1
[2012-02-16 02:02:52] INFO  ruby 1.9.3 (2011-10-30) [x86_64-darwin10.8.0]
[2012-02-16 02:02:52] INFO  WEBrick::HTTPServer#start: pid=76976 port=9292

From: /Users/joker/temp/config.ru @ line 8 in Hello#call:

     3: require "pry"
     4: 
     5: class Hello
     6:   def call(env)
     7:     binding.pry
 =>  8:     [200, {"Content-Type" => "text/plain"}, ["Hello World"]]
     9:   end
    10: end
    11: 
    12: use Rack::Auth::Basic do |user, pass|
    13:   user == "test" && pass == "password"

[1] pry(#<Hello>)> 


envと打ってみる。

[2] pry(#<Hello>)> env
=> {"GATEWAY_INTERFACE"=>"CGI/1.1",
 "PATH_INFO"=>"/",
 "QUERY_STRING"=>"",
 "REMOTE_ADDR"=>"127.0.0.1",
 "REMOTE_HOST"=>"localhost",
 "REQUEST_METHOD"=>"GET",
 "REQUEST_URI"=>"http://localhost:9292/",
 "SCRIPT_NAME"=>"",
 "SERVER_NAME"=>"localhost",
 "SERVER_PORT"=>"9292",
 "SERVER_PROTOCOL"=>"HTTP/1.1",
 "SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.9.3/2011-10-30)",
 "HTTP_HOST"=>"localhost:9292",
 "HTTP_USER_AGENT"=>
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:7.0.1) Gecko/20100101 Firefox/7.0.1",
 "HTTP_ACCEPT"=>
  "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
 "HTTP_ACCEPT_LANGUAGE"=>"ja,en-us;q=0.7,en;q=0.3",
 "HTTP_ACCEPT_ENCODING"=>"gzip, deflate",
 "HTTP_ACCEPT_CHARSET"=>"Shift_JIS,utf-8;q=0.7,*;q=0.7",
 "HTTP_CONNECTION"=>"keep-alive",
 "HTTP_AUTHORIZATION"=>"Basic dGVzdDpwYXNzd29yZA==",
 "HTTP_CACHE_CONTROL"=>"max-age=0",
 "rack.version"=>[1, 1],
 "rack.input"=>
  #<Rack::Lint::InputWrapper:0x00000100b48920
   @input=#<StringIO:0x00000100b5e400>>,
 "rack.errors"=>
  #<Rack::Lint::ErrorWrapper:0x00000100b48880 @error=#<IO:<STDERR>>>,
 "rack.multithread"=>true,
 "rack.multiprocess"=>false,
 "rack.run_once"=>false,
 "rack.url_scheme"=>"http",
 "HTTP_VERSION"=>"HTTP/1.1",
 "REQUEST_PATH"=>"/",
 "REMOTE_USER"=>"test"}


Rack::Auth::Basicでは、HTTP_AUTHORIZATIONヘッダから、
credentialsを取得しているという記述がありました。
じゃあ、内容を確認してみようってことで、
アクセスの途中で実行を止めて、envを見る、という形で活用してみました。
exitしてpryを終了すると続きのコードが流れてきます。


ちなみに、jugyoさんが作った、ir_bというgemでirb上でも同じことができます。
その場合は、binding.pryではなく、ir bという形で記述します。
同じコードでpryも起動できるように、pry用のインターフェースも用意してくれています。
(jugyo/ir_b - GitHub https://github.com/jugyo/ir_b)


他にもrspecのコードの中にbinding.pryを仕込むと、以下のようなことが出来ます。

[1] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_2>)> subject
=> #<Repository _id: 4f3be74c91ea182d9e00000a, _type: nil, url: "https://github.com/joker1007/pasokara_player3", username: "joker1007", name: "pasokara_player3">
[2] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_2>)> subject.username.should eq("joker1007")
=> true
[3] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_2>)> subject.username.should eq("joker")
RSpec::Expectations::ExpectationNotMetError: 
expected: "joker"
     got: "joker1007"

(compared using ==)
from /Users/joker/gemfilestats/vendor/bundle/ruby/1.9.1/gems/rspec-expectations-2.8.0/lib/rspec/expectations/fail_with.rb:32:in `fail_with'
[4] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_2>)>

subjectには、Mongoidのオブジェクトが入っています。
このオブジェクトに対して、即座にshouldを呼び出し、テストコードの動きを確認できます。
また、そのコンテキストで使えるメソッドに対して補完が効くため、
have_と打って、TABを押すと、こんな感じでマッチャーが見れたりします。

[19] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_2>)> subject.should have
have                     have_and_belong_to_many  have_at_least            have_at_most             have_exactly             have_field               have_fields              have_many                
have_many_related        have_one                 have_one_related         have_instance_method 


rspecを対話的に実行するなら、a_matsudaさん作のinteractive_rspecなんかもあります。
irbでシンプルに実行するなら、こちらの方が良いでしょう。
(amatsuda/interactive_rspec - GitHub https://github.com/amatsuda/interactive_rspec)


今回、話した事は、irbの拡張設定をやったり、拡張するgemを導入することで、
ある程度、irbでも実現することが可能だったりします。
色付けたり、エディタで編集したり、実行途中で止めたり。
pryは設定無しで、こういった機能をすぐに利用できる点は非常にありがたいと思いますし、
cdによるコンテキストの移動、ls, show-methodによる参照は非常に強力で、これはirbには無い機能です。
これらの機能は、ちょっとした時に覚えておくと凄く便利感を味わえます。
cd, ls, show-method, binding.pry, とりあえずこれだけ覚えておきましょう。


一方で、ruby-debug等と違ってデバッガでは無いため、
ステップ実行、ブレークポイントの指定などは出来ません。
一連のフローを検証したい時は、ruby-debug等を活用し、場合によって使い分けていくのが良いと思います。


Shibuya.rbでは、シェルを直接操作してデモしながら発表という形だったので、
発表内容にいくつか付け加えて、まとめてみました。
公式のwikiには結構色々書いてあるんですが、
活用法については、日本語の情報ってあんまり無かったような気がします。
この記事が誰かの参考になれば幸いです。


追記:
書いた後で、Twitterで情報を頂いて、ruby-debugにpryコマンドを追加できる、ruby-debug-pryなるものがあるらしいです。
全然知らなかった。まだまだ奥が深いですね。
(AndrewO/ruby-debug-pry - GitHub https://github.com/AndrewO/ruby-debug-pry)


後、スタックを追っ掛けて移動できるpry-stack_explorerというプラグインもあります。
こっちは知ってたんですが、まだ利用したことが無いため、今回は話に入れてませんでした。
ついでにこちらも貼っておきます。
(banister/pry-stack_explorer - GitHub https://github.com/banister/pry-stack_explorer)


よし、今度使ってみよう。
Thanks! @Kirika_K2