9

I need to override Kernel.load in order to watch and process some Ruby files we've written for monitoring. However, it seems immune to such shenanigans.

It's easy to override require and require_relative, but load sits under them and bottlenecks the actual file read if I remember right.

Here's why it seems to be protected from overriding:

Kernel.module_eval do

  alias_method :original_require, :require
  def require(filename)
    require_result = original_require(filename)
    puts "required #{filename}"

    require_result
  end

  alias_method :original_load, :load
  def load(filename, wrap=true)
    load_result = original_load(filename, wrap)
    puts "loaded #{filename}"

    load_result
  end
end
include Kernel

require 'open-uri'
puts 'done'

Running that outputs:

required uri/rfc2396_parser
required uri/rfc3986_parser
required uri/common
required uri/common
required uri/generic
required uri/generic
required uri/ftp
required uri/generic
required uri/http
required uri/http
required uri/https
required uri/generic
required uri/ldap
required uri/ldap
required uri/ldaps
required uri/generic
required uri/mailto
required uri
required stringio
required date_core
required date
required time
required open-uri
done

I'm content to only override require and require_relative. However, I'm curious what's going on with load.


Afterthoughts:

It looks like load isn't called by require or require_relative. Mea culpa. Good catch Matt.

This question is similar to "How to override require in Ruby?".

Good reading:

Jörg's comment

I also want to give some love to Module#prepend, which would allow you to simply use super instead of that ugly alias_method stuff, with the additional bonus that your modifications would actually show up in the ancestry chain and thus much easier to debug.

is very sensible and worth using.

Community
  • 1
  • 1
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • 3
    It is always interesting to see veteran SO users asking questions. – onebree Aug 05 '15 at 18:52
  • 3
    A quick look at the MRI source suggests that `require` doesn’t delegate to `load` as you suggest. They both seem to end up calling the C function [`rb_load_internal`](https://github.com/ruby/ruby/blob/v2_2_2/load.c#L641) (at least for Ruby files, there seems to be a different path for extensions) but the `load` method isn’t on the code path for `require`. (Or have I misunderstood your question?) – matt Aug 05 '15 at 19:01
  • I suspect you're right. I think I remember that there was a change during one of the big version updates that switched to `rb_load_internal`. Gotta go look through the errata. – the Tin Man Aug 05 '15 at 19:25
  • @matt Digging around I don't see anything back in 1.9.3 that shows require using load. My memory is probably on the blink again. Or I need to look in Ruby 1.8. Make your comment an answer and I'll accept it. – the Tin Man Aug 05 '15 at 20:48
  • 1
    Good candidates would be: replacing MRI with YARV (a completely different, independently developed codebase) in Ruby 1.9.0, integrating RubyGems, also in 1.9.0, and various performance improvements, bugfixes, and behavior changes since then (I remember quite vividly the fact that the version of RubyGems that was originally integrated into YARV was heavily changed from the original, and the resulting discussions that ensued on the ruby-core mailinglist. There were some problems with circular requires, exponential performance, inconsistent recognition of already loaded files, …) – Jörg W Mittag Aug 05 '15 at 23:23
  • 1
    I also want to give some love to `Module#prepend`, which would allow you to simply use `super` instead of that ugly `alias_method` stuff, with the additional bonus that your modifications would actually show up in the ancestry chain and thus much easier to debug. – Jörg W Mittag Aug 05 '15 at 23:26
  • Kudos @JörgWMittag, I'll look into your suggestions! Thanks! – the Tin Man Aug 06 '15 at 07:02

1 Answers1

2

Here are two simple examples that seem to work for overriding require and require_relative, based on examples in "When monkey patching a method, can you call the overridden method from the new implementation?".

module Kernel
  old_require = method(:require)
  define_method(:require) do |filename|
    puts "require #{filename}"
    old_require.call(filename)
  end

  old_require_relative = method(:require_relative)
  define_method(:require_relative) do |filename|
    puts "require_relative #{filename}"
    old_require_relative.call(filename)
  end
end

or

module KernelExtensions
  def require(filename)
    puts "require #{filename}"
    super
  end
  def require_relative(filename)
    puts "require_relative #{filename}"
    super
  end
end

class Object
  prepend KernelExtensions
end

Running the second using

module Kernel
  prepend KernelExtensions
end

didn't work, but since Object includes Kernel, using class Object overriding seems to work cleanly.

Community
  • 1
  • 1
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • There is a nice gem that does exactly what you did in your examples: https://github.com/rubyworks/backload – janpio Sep 27 '18 at 12:12