旧聞になってしまいますが、RubyConf2013@Miami beach で発表してきました。
K.Sasada: Object management on Ruby 2.1, Oral Presentation at 13th Inernational Ruby Conference. at Miami beach, Florida on November 9th to 11th of 2013. rubyconf2013-ko1_pub.pdf (1288KB)
なお、すべての発表は http://www.atdot.net/~ko1/activities/ にまとめてあります。
特定のメソッドの呼び出し履歴が欲しい、みたいなケースは時々あるんじゃないかと思います。
というわけで、Ruby 2.0 から導入された Module#prepend (と、一応 keyword 引数)を利用して、そういう履歴を簡単に取得するためのメソッドを書いてみました。
class MethodTracer class AllTracer attr_reader :records def initialize @records = [] end def record args, call_site, result @records << [args, call_site, result] end def clear @records.clear end end class StatisticsTracer attr_reader :args, :call_sites, :results def initialize @args = Hash.new(0) @call_sites = Hash.new(0) @results = Hash.new(0) end def record args, call_site, result @args[args] += 1 @call_sites[call_site.to_s] += 1 @results[result] += 1 end def clear [@args, @call_sites, @results].each{|col| col.clear} end end def self.trace(cls, mid, statistics: false) tracer = (statistics ? StatisticsTracer : AllTracer).new mod = Module.new{ define_method(mid){|*args| result = super(*args) tracer.record(args, caller_locations(1, 1)[0], result) result } } cls.prepend mod tracer end end class C def foo(a, b, c) self end end require 'pp' tracer = MethodTracer.trace(C, :foo) # C.new.foo(1, 2, 3) C.new.foo(4, 5, 6).foo(1, 2, 3) # pp tracer.records #=> # [[[1, 2, 3], "t.rb:59:in `<main>'", #<C:0x2b68d64>], # [[4, 5, 6], "t.rb:60:in `<main>'", #<C:0x2b68cec>], # [[1, 2, 3], "t.rb:60:in `<main>'", #<C:0x2b68cec>]] tracer = MethodTracer.trace(C, :foo, statistics: true) # C.new.foo(1, 2, 3) C.new.foo(4, 5, 6).foo(1, 2, 3) # pp tracer.args pp tracer.call_sites pp tracer.results #=> # {[1, 2, 3]=>2, [4, 5, 6]=>1} # {"t.rb:60:in `<main>'"=>1, "t.rb:61:in `<main>'"=>2} # {#<C:0x3426834>=>1, #<C:0x3426730>=>2}
MethodTracer.trace に、トレースしたいクラスとメソッドを指定します。
久々に Ruby script をちゃんと書いた。
いや、テストも無いのにちゃんと書いたとか言うな、って感じだろうかのう。
今時だと gist にはったりするんだろうけど、どうやって使うのかわからないという上弱。せめて色づけくらいはしたいのう。
ちなみに、untrace がないのは問題か?
調子に乗って、もう一つ。
今時、キーワード引数に対応していないようなレガシーなメソッドを書く人なんて居ないと思いますが、まぁ、そんなメソッドも時にはあるかもしれません。というわけで、すべてのメソッドをキーワード引数に対応する keyworder メソッドを書いてみました。
def keyworder cls, mid method = cls.instance_method(mid) mod = Module.new{ define_method(mid){|**kw| params = [] method.parameters.each{|(_, param)| params << kw.fetch(param) } super(*params) } } cls.prepend mod end class C def foo a, b, c p [a, b, c] end end keyworder C, :foo C.new.foo(a: 1, b: 2, c: 3) #=> [1, 2, 3]
C#foo は keyword 引数未対応ですが、ちゃんと C.new.foo(a: 1, b: 2, c: 3) のように、キーワード引数で呼べるようになっているのがわかるかと思います。