module Mechanize::Prependable

Fake implementation of prepend(), which does not support overriding inherited methods nor methods that are formerly overridden by another invocation of prepend().

Here's what <Original>.prepend(<Wrapper>) does:

This way, a call of <Original>#<method> is dispatched to <Wrapper><method>, which may call super which is dispatched to <Stub>#<method>, which finally calls <Original>#<method>without<Wrapper> which is used to be called <Original>#<method>.

Usage:

class Mechanize
  # module with methods that overrides those of X
  module Y
  end

  unless X.respond_to?(:prepend, true)
    require 'mechanize/prependable'
    X.extend(Prependable)
  end

  class X
    prepend Y
  end
end

Private Instance Methods

prepend(mod) click to toggle source
# File lib/mechanize/prependable.rb, line 39
def prepend(mod)
  stub = Module.new

  mod_id = (mod.name || 'Module__%d' % mod.object_id).gsub(/::/, '__')

  mod.instance_methods.each { |name|
    method_defined?(name) or next

    original = instance_method(name)
    if original.owner != self
      warn "%s cannot override an inherited method: %s(%s)#%s" % [
        __method__, self, original.owner, name
      ]
      next
    end

    name = name.to_s
    name_without = name.sub(/(?=[?!=]?\z)/) { '_without_%s' % mod_id }

    arity = original.arity
    arglist = (
      if arity >= 0
        (1..arity).map { |i| 'x%d' % i }
      else
        (1..(-arity - 1)).map { |i| 'x%d' % i } << '*a'
      end << '&b'
    ).join(', ')

    if name.end_with?('=')
      stub.module_eval %Q{
        def #{name}(#{arglist})
          __send__(:#{name_without}, #{arglist})
        end
      }
    else
      stub.module_eval %Q{
        def #{name}(#{arglist})
          #{name_without}(#{arglist})
        end
      }
    end
    module_eval {
      alias_method name_without, name
      remove_method name
    }
  }

  include(mod, stub)
end