Search code examples
rubymonkeypatching

How to patch File and CoreExtensions for Timecop


I need to monkey-patch File. Timecop doesn't affect the time that the file system reports, which is what File.atime uses and in turn that's what HttpClient uses when posting a file to a server, which in turn means VCR doesn't quite work as desired. AFAIK, this means I can't use refinements.

I don't understand what's going on here:

class File
  def atime
    "this one happens"
  end
end

module CoreExtensions
  module File
    module TimecopCompat
      def atime
        "this one does not"
      end
    end
  end
end

File.include CoreExtensions::File::TimecopCompat

File.new('somefile').atime # --> "this one happens"

Why does the module-based monkey patching not happen? What do I need to change for it to work? Is there a different kind of monkey-patching that I should be using?


Solution

  • The issue is related to the way include appends the module to the ancestor chain. "Ruby modules: Include vs Prepend vs Extend" provides a very detailed overview of the differences between include and prepend.

    Take a look at these two examples:

    class Foo
      def hello
        "1"
      end
    end
    
    module Bar
      def hello
        "2"
      end
    end
    
    Foo.include Bar
    
    Foo.new.hello
    # => "1"
    Foo.ancestors
    # => [Foo, Bar, Object, Kernel, BasicObject]
    

    versus

    class Foo
      def hello
        "1"
      end
    end
    
    module Bar
      def hello
        "2"
      end
    end
    
    Foo.prepend Bar
    
    Foo.new.hello
    # => "2"
    Foo.ancestors
    # => [Bar, Foo, Object, Kernel, BasicObject]
    

    Basically, you want to use prepend in your case as include won't override the existing method.