savonarola/pulse_meter_core

View on GitHub
lib/pulse_meter/observer.rb

Summary

Maintainability
A
25 mins
Test Coverage
module PulseMeter
  class Observer
    extend PulseMeter::Mixins::Utils

    class << self
      # Removes observation from instance method
      # @param klass [Class] class
      # @param method [Symbol] instance method name
      def unobserve_method(klass, method)
        with_observer = method_with_observer(method)
        if has_method?(klass, with_observer)
          block = unchain_block(method)
          klass.class_eval &block
        end
      end

      # Removes observation from class method
      # @param klass [Class] class
      # @param method [Symbol] class method name
      def unobserve_class_method(klass, method)
        with_observer = method_with_observer(method)
        method_owner = metaclass(klass)
        if has_method?(method_owner, with_observer)
          block = unchain_block(method)
          method_owner.instance_eval &block
        end
      end

      # Registeres an observer for instance method
      # @param klass [Class] class
      # @param method [Symbol] instance method
      # @param sensor [Object] notifications receiver
      # @param proc [Proc] proc to be called in context of receiver each time observed method called
      def observe_method(klass, method, sensor, &proc)
        with_observer = method_with_observer(method)
        unless has_method?(klass, with_observer)
          block = chain_block(method, sensor, &proc)
          klass.class_eval &block
        end
      end

      # Registeres an observer for class method
      # @param klass [Class] class
      # @param method [Symbol] class method
      # @param sensor [Object] notifications receiver
      # @param proc [Proc] proc to be called in context of receiver each time observed method called
      def observe_class_method(klass, method, sensor, &proc)
        with_observer = method_with_observer(method)
        method_owner = metaclass(klass)
        unless has_method?(method_owner, with_observer)
          block = chain_block(method, sensor, &proc)
          method_owner.instance_eval &block
        end
      end

      protected

      def define_instrumented_method(method_owner, method, receiver, &handler)
        with_observer = method_with_observer(method)
        without_observer = method_without_observer(method)
        method_owner.send(:define_method, with_observer) do |*args, &block|
          start_time = Time.now
          begin
            self.send(without_observer, *args, &block)
          ensure
            begin
              delta = ((Time.now - start_time) * 1000).to_i
              receiver.instance_exec(delta, *args, &handler)
            rescue StandardError
            end
          end
        end
      end

      private

      def unchain_block(method)
        with_observer = method_with_observer(method)
        without_observer = method_without_observer(method)
        me = self

        Proc.new do
          if me.send(:has_method?, self, without_observer)
            alias_method(method, without_observer)
            remove_method(without_observer)
          else
            #for inherited child methods if we unobserve them after parent class
            remove_method(method)
          end
          remove_method(with_observer)
        end
      end

      def chain_block(method, receiver, &handler)
        with_observer = method_with_observer(method)
        without_observer = method_without_observer(method)
        me = self

        Proc.new do
          me.send(:define_without_observer, self, method, without_observer)
          me.send(:define_instrumented_method, self, method, receiver, &handler)
          alias_method(method, with_observer)
        end
      end

      def define_without_observer(method_owner, method, without_observer)
        if has_method?(method_owner, method)
          #for redefined methods
          unless has_method?(method_owner, without_observer)
            method_owner.send(:alias_method, without_observer, method)
          end
        else
          #for inherited methods
          unless method_owner.method_defined?(without_observer)
            method_owner.send(:alias_method, without_observer, method)
          end
        end
      end

      def has_method?(method_owner, method)
        method_owner.instance_methods(false).include?(method.to_sym)
      end

      def metaclass(klass)
        klass.class_eval do
          class << self
            self
          end
        end
      end

      def method_with_observer(method)
        "#{method}_with_#{underscore(self).tr('/', '_')}"
      end

      def method_without_observer(method)
        "#{method}_without_#{underscore(self).tr('/', '_')}"
      end
    end
  end
end