Search code examples
rubymodulemonkeypatching

ruby monkey patching a module's classmethod


A gem I'm using (gem "A") relies on another gem (gem "B") whose method I'm trying to patch. When I patch gem "B" and call a method from gem "A", the monkey patch is ignored. My patch looks like the following:

module B
  class<< self
    def patched_method()
      raise
    end
  end
end

I've inserted statements throughout to print the location of the method, using the form

puts B.method(:patched_method).source_location

When called before the patch, it points to the location of gem "B". After the patch it points to the location where I've defined the patch, yet it still doesn't call my patch!

Right now my patch just calls raise so I can verify that it's being called, but I've also tried printing text and calling my actual patch code, none of which works. What am I doing wrong?

Gem and code specifics

I'm trying to patch the Numerizer gem's numerize method which is used by the Chronic gem.

I've tried to patch multiple ways, but the most recent were:

module NumerizerExpand
  module ClassMethods
    def self.numerize(value)
      raise
    end
  end

  def self.included(receiver)
    receiver.extend ClassMethods
  end
end
Numerizer.send(:include, NumerizerExpand)

and

Numerizer.instance_eval do
  class<< self
    def my_numerize(value)
      puts "here"
      raise
    end
    alias_method :numerize, :my_numerize
  end
end

For the second method, I've tried using both instance_eval and module_eval but neither seem to work.

I've created a gist on github that contains the script I'm running to test this.


Solution

  • You're patching the wrong class :)

    require 'numerizer'
    require 'chronic'
    
    puts Chronic::Numerizer.method(:numerize).source_location
    
    
    class Chronic::Numerizer
        def self.numerize(value)
          puts "here"
          raise
        end
    end
    
    puts Chronic::Numerizer.method(:numerize).source_location
    
    #p Chronic::Numerizer.numerize(3)
    p Chronic.parse('January 2nd')
    

    outputs

    bbozo@eva:~/dev/SO_question_1$ ruby wii.rb
    /home/bbozo/.rvm/gems/ruby-2.2.3/gems/chronic-0.10.2/lib/chronic/numerizer.rb
    72
    wii.rb
    8
    here
    wii.rb:10:in `numerize': unhandled exception
            from /home/bbozo/.rvm/gems/ruby-2.2.3/gems/chronic-0.10.2/lib/chronic/parser.rb:100:in `pre_normalize'
            from /home/bbozo/.rvm/gems/ruby-2.2.3/gems/chronic-0.10.2/lib/chronic/parser.rb:226:in `tokenize'
            from /home/bbozo/.rvm/gems/ruby-2.2.3/gems/chronic-0.10.2/lib/chronic/parser.rb:60:in `parse'
            from /home/bbozo/.rvm/gems/ruby-2.2.3/gems/chronic-0.10.2/lib/chronic.rb:90:in `parse'
            from wii.rb:17:in `<main>'
    

    Apparently the chronic team decided they don't want to maintain one class as a separate dependency and they just copy-pasted it and included it into the namespace.

    You were patching a gem which is in reality not used by chronic. A swine's trick :) but reasonable for the chronic people to do.