prepend.rb 2.2KB

    # 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: # # - Create an anonymous stub module (hereinafter <Stub>) and define # <Stub>#<method> that calls #<method>_without_<Wrapper> for each # instance method of <Wrapper>. # # - Rename <Original>#<method> to #<method>_without_<Wrapper> for each # instance method of <Wrapper>. # # - Include <Stub> and <Wrapper> into <Original> in that order. # # 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 class Module 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) next if original.owner != self 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 %{ def #{name}(#{arglist}) __send__(:#{name_without}, #{arglist}) end } else stub.module_eval %{ def #{name}(#{arglist}) #{name_without}(#{arglist}) end } end module_eval { alias_method name_without, name remove_method name } } include stub include mod end private :prepend end unless Module.method_defined?(:prepend)