0

To centralize the error handling of my application, I created the class ErrorHandling. This class should log all my errors, notify me via Slack, etc.

class ErrorHandler
    def self.handle_error
        yield
    rescue => e
        log_error(e.message)
        notify_via_slack(e.message)
        # etc.
    end
end

I need to wrap all the methods of MyClass manually within ErrorHandler.handle_error to make use of the central error handling.

class MyClass
    def method1
        ErrorHandler.handle_error do
            raise StandardError, 'my error msg'
        end
    end

    def method2
        ErrorHandler.handle_error do
            raise StandardError, 'my error msg'
        end
    end
end

This will pollute my code unnecessarily. I thought about dynamically overriding my methods, so I don't have to wrap them manually.

class MyClass
    def method1
        raise StandardError, 'my error msg'
    end

    def method2
        raise StandardError, 'my error msg'
    end

    instance_methods(false).each do |method_name| # instance_methods(false) => [:method1, :method2]
        define_method(method_name) do
            ErrorHandler.handle_error do
                super()
            end
        end
    end
end

Unfortunately this code won't work. Is it possible to dynamically override the methods of MyClass?

My goal is to dynamically override all instance methods of a class to make use of the central error handling, so I don't have to manually modify every method.

Thanks for your help!

Sepp
  • 347
  • 4
  • 12
  • Why not `ErrorHandler.handle_error { Application.run }`, or something alone that line instead of wrapping every single method? Or send the notification from an [`at_exit`](http://ruby-doc.org/core-2.5.1/Kernel.html#method-i-at_exit) hook if `$!` is present. – Stefan Oct 16 '18 at 10:04
  • `super` calls the overridden method, but you are not overriding anything, you are over**writing** it. So, it's gone and you cannot call it anymore. – Jörg W Mittag Oct 17 '18 at 22:21

2 Answers2

3

If you want to call super you should use Module#prepend:

ims = instance_methods(false)
prepend(Module.new do
  ims.each do |method_name|
    define_method(method_name) do
      ErrorHandler.handle_error { super() }
    end
  end
end)
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • 1
    Your approach works just great in my case! Thanks a lot! _You forgot to close the parentheses `prepend(....)`_ – Sepp Oct 16 '18 at 11:38
2

I suggest you to use the Proxy pattern, that will allow you to inject any instance of MyClass into the proxy and then, in the proxy, you can define any extra behaviour you want.

Here you have an exercise that teach you how to create a proxy:

https://github.com/edgecase/ruby_koans/blob/master/src/about_proxy_object_project.rb

At the end you will have something like

class Proxy
  def initialize(target_object)
    @target_object = target_object
  end

  def missing_method(method_symbol, *args, &block)
    begin
      @target_object.__send__(method_symbol, *args, &block)
    rescue Exception => ex
      SlackNotifier.notify(ex)
      raise ex
    end
  end
end