My first thoughts are some thing like this:
class AbstractBuilder
attr_reader :time_taken
def build_with_timer
started_at = Time.now
build
@time_taken = Time.now - started_at
end
def build
raise 'Implement this method in a subclass'
end
end
class MyBuilder < AbstractBuilder
def build
sleep(5)
end
end
builder = MyBuilder.new.build_with_timer
puts builder.time_taken
I would suspect there is a better way which offers better flexibility, for example ideally I'd like to call 'build' on an instance of MyBuilder instead of 'build_with_timer' and always have the execution time recorded.
I did consider using alias_method from initialize or even using a module mixin instead of class inheritance which would override the build method calling super in the middle (not sure if that would work). Before I go down the rabbit hole I thought I'd see if there is an established practice.
I had a stab at a version to achieve what you want. This version doesn't require the subclass to have any extra code either.
class AbstractBuilder
@@disable_override = false
def before_method
puts "before"
end
def after_method
puts "after"
end
def self.method_added name
unless @@disable_override
if name == :build
@@disable_override = true # to stop the new build method
self.send :alias_method, :sub_build, :build
self.send :remove_method, :build
self.send :define_method, :build do
before_method
sub_build
after_method
end
@@disable_override = false
else
puts "defining other method #{name}"
end
end
end
end
class MyBuilder < AbstractBuilder
def build
puts "starting build"
sleep(5)
puts "built."
end
def unnaffected_method
# this method won't get redefined
end
end
b = MyBuilder.new
b.build
Outputs
defining other method unnaffected_method
before
starting build
built.
after
I'd play with alias_method
:
module Timeable
def time_methods *meths
meths.each do |meth|
alias_method "old_#{meth}", meth
define_method meth do |*args|
started_at = Time.now
res = send "old_#{meth}", *args
puts "Execution took %f seconds" % (Time.now - started_at)
res
end
end
end
end
class Foo
def bar str
puts str
end
end
Foo.extend Timeable
Foo.time_methods :bar
Foo.new.bar('asd')
#=>asd
#=>Execution took 0.000050 seconds
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With