Problems extending Enumerable

March 8th, 2016 - Bonn

I wrote how to add a simple method to Enumerable in a previous post

module Enumerable
  def measure(&block)
    Benchmark.measure do
      ActiveRecord::Base.logger.silence do
        self.tqdm.each(&block)
      end
    end
  end
end

I wanted to follow the points in this article 3 Ways to Monkey-patch Without Making a Mess, but there seems to be some cases in which this technique can’t be applied, at least the part regarding putting your method in a module.

In order to make other developers aware of this monkey patches, we start by requiring it inside an initializer in config/initializers/monkey_patches.rb

require 'core_extensions/enumerable/progress_benchmark'

then I add a file at lib/core_extensions/enumerable/progress_benchmark following rails conventions

module CoreExtensions
  module Enumerable
    module ProgressBenchmark

      def measure(&block)
        Benchmark.measure do
          ActiveRecord::Base.logger.silence do
            self.tqdm.each(&block)
          end
        end
      end  

    end
  end
end

Enumerable.include CoreExtensions::Enumerable::ProgressBenchmark

if we try it now in the console:

[].measure
#NoMethodError: undefined method `method' for []:Array

it would exist if we include Enumerable now:

class Foo
  include Enumerable
end

Foo.new.measure
#NoMethodError: super: no superclass method `each'  (it's ok, it has been included )

Seems that including methods this way doesn’t work when you refer to classes that have already included that module. These posts speak about a limitation on the current ruby implementation:

This however works well just reopening the module, as originally.

Active Support reopens as well

A workaround would be do something like this, to iterate through the object space, find all the classes that had previously included Enumerable and include it again:

ObjectSpace.each_object(Class) do |klass|
  if klass.include?(Enumerable )
    klass.include CoreExtensions::Enumerable::ProgressBenchmark)
  end
end

Not sure whether this is a good idea yet.