lib/method_observer.rb



class MethodObserver

  attr_reader :target
  attr_accessor :result

  class ObserverCannotObserveTwiceError < StandardError
    def initialize(message = 'observer cannot observe twice')
      super
    end
  end

  def observe(target)
    raise ObserverCannotObserveTwiceError if @target
    @target = target
    make_observable(target)
  end

  def self.instances
    @instances ||= {}
  end

  def self.new(*args)
    o = super
    instances[o.object_id] = o
    o
  end

  private
    def make_observable(target)
      methods_to_observe.each do |method|
        target.instance_eval %{
          def #{method}(*args, &block)
            observer = #{self.class}.instances[#{self.object_id}]
            observer.send(:before_#{method}, *args, &block) if observer.respond_to? :before_#{method}
            observer.result = super
            observer.send(:after_#{method}, *args, &block) if observer.respond_to? :after_#{method}
            observer.result
          end
        }
      end
    end

    def methods_to_observe
      (methods_for(:before) + methods_for(:after)).uniq
    end

    def methods_for(name)
      methods.grep(/^#{name}_/).map { |n| n.to_s.gsub(/^#{name}_/, '').intern }
    end
end