Tutorial :How do I run code before and after a method in a sub class?



Question:

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.


Solution:1

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  


Solution:2

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  


Solution:3

Sounds like you're looking for hooks into object lifecycle events. You'll have to build this into your base object and provide a little DSL -- I'm thinking you're after something like ActiveRecord Callbacks. Here's how we might modify your example to allow something like that:

class AbstractBuilder    attr_reader :time_taken      def construct! # i.e., build, and also call your hooks      @@prebuild.each { |sym| self.send(sym) }      build      @@postbuild.each { |sym| self.send(sym) }    end        def construct_with_timer      started_at = Time.now      construct!      @time_taken = Time.now - started_at        puts "!!! Build time: #@time_taken"    end      class << self      def before_build(fn); @@prebuild ||= []; @@prebuild << fn; end      def after_build(fn);  @@postbuild ||= []; @@postbuild << fn; end    end  end    class MyBuilder < AbstractBuilder    before_build :preprocess    after_build  :postprocess      def build; puts "BUILDING"; sleep(3); end    def preprocess;  puts "Preparing to build..."; end    def postprocess; puts "Done building. Thank you for waiting."; end  end    builder = MyBuilder.new  builder.construct_with_timer    # => Preparing to build...  # => BUILDING  # => Done building. Thank you for waiting.  # => !!! Build time: 3.000119  


Solution:4

This is a textbook-definition use case for Aspect-Oriented Programming. It generally offers a cleaner separation of concerns. In this arena, Ruby offers Aquarium and AspectR. However, you may not want to add another dependency to your project. As such, you might still consider using one of the other approaches.


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »