Search code examples
crubymonkeypatching

Ruby override Enumerable method


I'm trying to overwrite a method on the Enumerable module like this:

module Enumerable
  def collect(&block)
    puts 'collect'
    super
  end
end

(Note this is a trivial example).

Theoretically, when I call collect or map, Ruby should use my overridden version, right? But it's not. It always uses the built-in Enumerable method. Is it because collect is actually enum_collect and complied with the source?

[1,2,3].map(&:to_s) # never prints anything

Yes, I'm aware that Monkey-Patching is bad, etc, etc, and I'm aware there are alternatives including subclassing, etc, but I want to know if it's possible to overwrite a built-in C function with Ruby.

Enumerable.class_eval do
  def collect(&block)
    puts 'collect was class_eval'
    super
  end
end

 

eigen = class << Enumerable; self; end
eigen.class_eval do
  def collect(&block)
    puts 'collect was eigen'
    super
  end
end

 

module Enumerable
  def collect(&block)
    puts 'collect was opened up'
    super
  end
end

 

Array.send(:include, Enumerable)

and pretty much every combination thereof.

PS. This is Ruby 1.9.3, but ideally I'm looking for a solution that would work on all versions.


Solution

  • I think your problem is that Array defines its own collect method rather than using Enumerable's:

    collect {|item| block } → new_ary
    map {|item| block } → new_ary
    collect → an_enumerator
    map → an_enumerator

    Invokes block once for each element of self. Creates a new array containing the values returned by the block. See also Enumerable#collect.

    So you can monkey patch Enumerable#collect all you want but Array won't care because it doesn't use Enumerable#collect. You'll have better luck if you monkey patch Array#collect:

    class Array
      def collect
        #...
      end
    end
    

    You'll want to patch Array#map as well or just patch map and let the alias take care of collect.

    Note that Array#map is implemented in C so the C part had nothing to do with your problem.