So last time I left you with the promise that I’d return with a solution so that the number of times a certain method was called is a class method which makes more sense than if it was an instance method. So here is the “improved version”:
call_counter.rb:
module CallCounter
def count_calls_to(method_name)
original_method = instance_method(method_name)
unless class_variable_defined?(:@@call_counter):
class_variable_set(:@@call_counter, {})
end
call_counter = class_variable_get(:@@call_counter)
define_method(method_name) do |*args|
call_counter[method_name] ||= 0
call_counter[method_name] += 1
bound_original_method = original_method.bind(self)
bound_original_method.call(*args)
end
metaclass = class << self; self; end
metaclass.instance_eval do
define_method(:calls_to) do |m|
call_counter[m].nil? ? 0 : call_counter[m]
end
define_method(:reset_counters) do
call_counter.each_key do |k|
call_counter[k] = 0
end
end
end
end
end
What has changed is the introduction of a class variable that counts the calls on all watched methods and that the number of calls on each method is queried by calls_to(<method name>) instead of calls_to_<method_name>. A bit less magic.
call_foo.rb:
require "call_counter"
class CallFoo
extend CallCounter
def foo; "foo"; end
def bar; "bar"; end
count_calls_to :foo
count_calls_to :bar
end
(a snippet of) call_counter_spec.rb:
require "call_foo"
require "spec"
describe CallFoo do
before(:each) do
@call_foo = CallFoo.new
CallFoo.reset_counters
end
it "should be able to count several methods' calls" do
4.times { @call_foo.foo }
CallFoo.calls_to(:foo).should == 4
7.times { @call_foo.bar }
CallFoo.calls_to(:bar).should == 7
end
end
I’m still not 100% content with this solution, the programming interface is nice now but it would be cool to get rid of the class variable somehow, possibly replacing it with closures. If you know how to achieve it, please leave a comment.
ps. You can also get the whole code in nice colored format or the raw text version.