Ruby
pry

PryでTempfileオブジェクトを評価するとFileと表示される

TL;DR

  • PryでTempfileオブジェクトを評価すると、Fileと表示される
  • これはPryのデフォルトインスペクタがppであり、Tempfileクラスがprint_prettyの処理をFileクラスに移譲しているから

現象

PryでTempfileオブジェクトを作って評価すると、Fileと表示される。

[1] pry(main)> f = Tempfile.new
=> #<File:/var/folders/5x/jy4d20b97jgcsgf4mt7dwwqr0000gn/T/20180808-1401-kvolc2>

もちろんTempfileクラスはFileクラスではなく、inspectするとちゃんとTempfileと表示される

[2] pry(main)> f.inspect
=> "#<Tempfile:/var/folders/5x/jy4d20b97jgcsgf4mt7dwwqr0000gn/T/20180808-1401-kvolc2>"

そもそもTempfileクラスはFileクラスとis aの関係にない。

[3] pry(main)> f.is_a?(File)
=> false

irbではこのようなことは起きない。

irb(main):001:0> require "tempfile"
=> true
irb(main):002:0> f = Tempfile.new
=> #<Tempfile:/var/folders/5x/jy4d20b97jgcsgf4mt7dwwqr0000gn/T/20180808-1422-in132m>

原因

TL;DRにも書いたが、原因はPryのデフォルトインスペクタがppであり、ppがオブジェクトのpretty_printを要求するが、TempfileFileのDelegateClassになっており、inspectは自前で持っているが、pretty_printは持っていないので、その処理をFileに移譲してしまうから。

従って、irbでもデフォルトインスペクタをppにすると同じことが起きる。

$ irb --inspect pp
irb(main):001:0> require "tempfile"
=> true
irb(main):002:0> f = Tempfile.new
=> #<File:/var/folders/5x/jy4d20b97jgcsgf4mt7dwwqr0000gn/T/20180808-1493-fvvhrn>
irb(main):003:0> 

逆に、Tempfilepretty_printを実装すればPryでもこの問題は起きない。

[1] pry(main)> f = Tempfile.new  # Fileと表示される
=> #<File:/var/folders/5x/jy4d20b97jgcsgf4mt7dwwqr0000gn/T/20180808-1501-1z0rdk5>

[2] pry(main)> class Tempfile
[2] pry(main)*   def pretty_print(q)  # Tempfileクラスにpretty_printを追加
[2] pry(main)*     q.text inspect
[2] pry(main)*   end  
[2] pry(main)* end  
=> :pretty_print

[3] pry(main)> f  # Tempfileと表示される
=> #<Tempfile:/var/folders/5x/jy4d20b97jgcsgf4mt7dwwqr0000gn/T/20180808-1501-1z0rdk5>
[4] pry(main)> 

また、Pryのデフォルトインスペクタをsimpleにしてもこの問題は起きない。

[1] pry(main)> f = Tempfile.new
=> #<File:/var/folders/5x/jy4d20b97jgcsgf4mt7dwwqr0000gn/T/20180808-1569-1sbfpbu>

[2] pry(main)> f # デフォルトではFileと評価される
=> #<File:/var/folders/5x/jy4d20b97jgcsgf4mt7dwwqr0000gn/T/20180808-1569-1sbfpbu>

[3] pry(main)> change-inspector simple
Switched to the 'simple' inspector!

[4] pry(main)> f # simpleインスペクタではTempfileと評価される
#<Tempfile:/var/folders/5x/jy4d20b97jgcsgf4mt7dwwqr0000gn/T/20180808-1569-1sbfpbu>

まとめ

ちょっとTempfileについて調べてたらPryの変な挙動を見つけたのでまとめてみた。Tempfileだけでなく、inpsectは実装しているがpretty_printは実装されていないDelegateClass全般で同じ問題がおきるはず。一番まっとうな解決策はrequire "pp"されたときに、Tempfilepretty_printメソッドを追加することなんだろうけど、特に実害も無いし、issue立てたりプルリクを作るほどでも無い気がする・・・?

その他

Pryのインスペクタにはもう一つ、clippedがある。これでTempfileオブジェクトのinspectを評価するとStringになってしまう。

[1] pry(main)> f = Tempfile.new
=> #<File:/var/folders/5x/jy4d20b97jgcsgf4mt7dwwqr0000gn/T/20180808-1591-1uujsu6>

[2] pry(main)> f.inspect # デフォルトインスペクタではTempfileと表示される
=> "#<Tempfile:/var/folders/5x/jy4d20b97jgcsgf4mt7dwwqr0000gn/T/20180808-1591-1uujsu6>"

[3] pry(main)> change-inspector clipped
Switched to the 'clipped' inspector!

[4] pry(main)> f.inspect # clippedインスペクタではStringと表示されてしまう。
#<String:0x7fe23d102710>

[5] pry(main)> 

これは、Pryのclippedインスペクタが、60文字を超えるとオブジェクトのクラス名+IDを表示する仕様だから。

[1] pry(main)> change-inspector clipped
Switched to the 'clipped' inspector!
[2] pry(main)> "X"*58
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
[3] pry(main)> "X"*59
#<String:0x7ff6a60a5c18>
[4] pry(main)> 

そういう仕様だと言われたらそうなんだけど、Tempfileオブジェクトをinspectした結果が「String」とか言われたらちょっとびっくりしない?