Search code examples
ruby-on-railsrubymonkeypatching

How to log any time any method has been monkey patched in Rails


I would like to print the class and method any time a method gets redefined in a Rails app, even if it happens in an included gem. I understand there is a hook called method_added that gets called back on redefining a method, but I do not know how to use it to catch anything that gets redefined.

How do I use method_added?

I added this to boot.rb:

class Module
  def method_added(name)
    puts "adding #{self.name.underscore.capitalize}  #{name}\n"
  end
end

But that seems to be catching every single method in every single class?


Solution

  • You can use the Module#method_added hook to keep a record of all methods that are being defined, and check whether you have already seen the same method before:

    require 'set'
    
    module LoggingMethodAdded
      def method_added(meth)
        @methods ||= Set.new
        puts "Monkey patching #{meth} in #{self}!" if @methods.include?(meth)
        @methods << meth
        super
      end
    end
    
    class Module
      prepend LoggingMethodAdded
    end
    
    class Foo; def foo; end end
    
    class Foo; def foo; end end
    # Monkey patching foo in Foo!
    
    module Bar; def foo; end end
    
    module Bar; def foo; end end
    # Monkey patching foo in Bar!
    

    This, however, will only work for methods that are added after you have loaded your hook method. The obvious alternative would be to check the already defined methods instead of recording them yourself:

    def method_added(meth)
      puts "Monkey patching #{meth} in #{self}!" if (instance_methods(false) | private_instance_methods(false)).include?(meth)
      super
    end
    

    But this doesn't work: it is not exactly specified when the method_added hook gets executed; it might get executed after the method has been defined, which means the check will always be true. (At least that's what happened in my tests.)