Dear RubyGems: Perhaps a better way to override require

(7rans) #1

I may have found a much better way to override Kernel#require. Here's
my current code:

  require 'uri'

  module Kernel::NanoKernel

    FILE_ESC_RE = %r{[^a-zA-Z_0-9.\-]}

    def require( fpath )
      fs = fpath.split('/')
      fn = fs.pop
      dn = fs.join('/')
      dn += '/' unless dn.empty?
      en = URI.escape( File.basename( fn ), FILE_ESC_RE )
      super( "#{dn}#{en}" )
    end

  end

  class Object
    include NanoKernel
  end

I appears to work flawlessly. Agree? Or do you see a bug waiting to
bite?

Thanks,
T.

(Chad Fowler) #2

Tom, what problem are you trying to solve? I mean, I see that you're
overriding require, but I'm not sure how it's supposed to be better.
I'm not implying that it _isn't_. Just trying to understand the
intent.

···

On 8/11/05, Trans <transfire@gmail.com> wrote:

I may have found a much better way to override Kernel#require. Here's
my current code:

  require 'uri'

  module Kernel::NanoKernel

    FILE_ESC_RE = %r{[^a-zA-Z_0-9.\-]}

    def require( fpath )
      fs = fpath.split('/')
      fn = fs.pop
      dn = fs.join('/')
      dn += '/' unless dn.empty?
      en = URI.escape( File.basename( fn ), FILE_ESC_RE )
      super( "#{dn}#{en}" )
    end

  end

  class Object
    include NanoKernel
  end

I appears to work flawlessly. Agree? Or do you see a bug waiting to
bite?

--
Chad Fowler
http://chadfowler.com
http://rubycentral.org
http://rubygarden.org
http://rubygems.rubyforge.org (over 700,000 gems served!)

(7rans) #3

Hi Chad,

I have a library that allows one to require individual methods by name.
Unfortuantely some file systems have punctuation limitations on file
names, so I need to "escape" the names and then override Kernel#require
to take that into account. I did it the tradiional way using alias, but
for that caused conflicts with RubyGems, which does the same thing. In
figuring out what to do about it I hit on the above solution.

I simply thought you might like to know about this, since it seems to
be a more robust solution. Also, I just wanted to be sure I didn't
overlook something, i.e. if anyone else sees a problem this way of
doing it.

Thanks,
T.

(Chad Fowler) #4

Ahh, I get it. The point (that I missed) wasn't the actual
implementation of require
but the fact that you were overriding it using a mixin instead of an
alias. Seems reasonable. I don't see any obvious problems either,
but I'd let the question linger here....anyone else?

···

On 8/11/05, Trans <transfire@gmail.com> wrote:

Hi Chad,

I have a library that allows one to require individual methods by name.
Unfortuantely some file systems have punctuation limitations on file
names, so I need to "escape" the names and then override Kernel#require
to take that into account. I did it the tradiional way using alias, but
for that caused conflicts with RubyGems, which does the same thing. In
figuring out what to do about it I hit on the above solution.

I simply thought you might like to know about this, since it seems to
be a more robust solution. Also, I just wanted to be sure I didn't
overlook something, i.e. if anyone else sees a problem this way of
doing it.

--
Chad Fowler
http://chadfowler.com
http://rubycentral.org
http://rubygarden.org
http://rubygems.rubyforge.org (over 700,000 gems served!)

(Ryan Leavengood) #5

Chad Fowler said:

Ahh, I get it. The point (that I missed) wasn't the actual
implementation of require
but the fact that you were overriding it using a mixin instead of an
alias. Seems reasonable. I don't see any obvious problems either,
but I'd let the question linger here....anyone else?

This does seem better, but only if everytime someone overrides require in
an included module they be sure to call super.

Also it seems to be the alias method is fine, as long as everyone uses a
unique name for the old method:

module Kernel
  alias :_old_require_ :require
  def require(s)
    puts 'New require, now calling _old_require_...'
    _old_require_(s)
  end
end

module Kernel
  alias :_some_old_require_ :require
  def require(s)
    puts 'New require, now calling _some_old_require_...'
    _some_old_require_(s)
  end
end

require 'English'
puts $PID
__END__

C:\_Ryan\ruby>ruby require_test.rb
New require, now calling _some_old_require_...
New require, now calling _old_require_...
3408

If unique names aren't used, the result is an infinite loop.

Ryan

(7rans) #6

Ryan Leavengood wrote:

This does seem better, but only if everytime someone overrides require in
an included module they be sure to call super.

Also it seems to be the alias method is fine, as long as everyone uses a
unique name for the old method:

module Kernel
  alias :_old_require_ :require
  def require(s)
    puts 'New require, now calling _old_require_...'
    _old_require_(s)
  end
end

module Kernel
  alias :_some_old_require_ :require
  def require(s)
    puts 'New require, now calling _some_old_require_...'
    _some_old_require_(s)
  end
end

require 'English'
puts $PID
__END__

C:\_Ryan\ruby>ruby require_test.rb
New require, now calling _some_old_require_...
New require, now calling _old_require_...
3408

If unique names aren't used, the result is an infinite loop.

That's what I expected too. But when I tried, it didn't work so well.
My aliased version was working fine until I installed RubyGems.
Appaerently it was running my version twice. Thus escaping twice, "[]"
-> "%5B%5D" -> "%255B%255D". Perhaps this is b/c RubyGems uses a rescue
clause and retries and so ends up passing the name to my routine again.
Not sure.

But you also make a good point about my inheritance chain version.
Someone else using the alias technique after mine would by pass mine
altogether. It works with RubyGems only b/c RubyGems loads first.

In thinking it over some, I suspect the best solution is either to
change the built-in require to support an observer-type pattern, so one
can tap into it. Or we simply need to agree on a standard, which I
imagine would be basically:

  module Kernel
    # Always available bypass.
    # This should be built into ruby.
    alias __require__ require
  end

  module ARequireOverider
    def require( path )
      # ...
      begin
        super( newpath ) # <----.
      rescue LoadError # or vise-versa
        super( path ) # <----'
      end
    end
  end

  class Object
    inculde ARequireOverider
  end

T.

(Mauricio Fernandez) #7

Also it seems to be the alias method is fine, as long as everyone uses a
unique name for the old method:

[...]

module Kernel
  alias :_some_old_require_ :require
  def require(s)
    puts 'New require, now calling _some_old_require_...'
    _some_old_require_(s)
  end
end

require 'English'
puts $PID
__END__

C:\_Ryan\ruby>ruby require_test.rb
New require, now calling _some_old_require_...
New require, now calling _old_require_...
3408

If unique names aren't used, the result is an infinite loop.

I often prefer something like:

batsman@tux-chan:/tmp$ cat gfdhoidhi.rb

module Kernel
    meth = instance_method(:require)
    define_method(:require) do |s|
        puts "New require(1), reusing old definition"
        meth.bind(self).call(s)
    end
end

module Kernel
    meth = instance_method(:require)
    define_method(:require) do |s|
        puts "New require(2), reusing old definition"
        meth.bind(self).call(s)
    end
end

require 'English'
puts $PID
batsman@tux-chan:/tmp$ ruby gfdhoidhi.rb
New require(2), reusing old definition
New require(1), reusing old definition
4582

especially in 1.9, of course.

···

On Fri, Aug 12, 2005 at 06:14:21AM +0900, Ryan Leavengood wrote:

--
Mauricio Fernandez

(Jim Weirich) #8

Were you modifying the string in-place? If so, try it without destructively
modifying the string and either solution should work then.

···

On Friday 12 August 2005 05:41 am, Trans wrote:

> If unique names aren't used, the result is an infinite loop.

That's what I expected too. But when I tried, it didn't work so well.
My aliased version was working fine until I installed RubyGems.
Appaerently it was running my version twice. Thus escaping twice, "[]"
-> "%5B%5D" -> "%255B%255D". Perhaps this is b/c RubyGems uses a rescue
clause and retries and so ends up passing the name to my routine again.
Not sure.

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

(Jim Weirich) #9

[... solution using define_method elided ...]

Clever ... I like that.

···

On Friday 12 August 2005 06:26 am, Mauricio Fernández wrote:

> If unique names aren't used, the result is an infinite loop.

I often prefer something like:

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

(7rans) #10

Mauricio Fernández wrote:

I often prefer something like:

batsman@tux-chan:/tmp$ cat gfdhoidhi.rb

module Kernel
    meth = instance_method(:require)
    define_method(:require) do |s|
        puts "New require(1), reusing old definition"
        meth.bind(self).call(s)
    end
end

module Kernel
    meth = instance_method(:require)
    define_method(:require) do |s|
        puts "New require(2), reusing old definition"
        meth.bind(self).call(s)
    end
end

require 'English'
puts $PID
batsman@tux-chan:/tmp$ ruby gfdhoidhi.rb
New require(2), reusing old definition
New require(1), reusing old definition
4582

especially in 1.9, of course.

Although, there still needs to be a way to selectively bypass. For
instance I have a method Kernel#require_all, which requires files based
on a glob pattern eg.

  require_all 'dir/*'

But I can't have this routing through my escaping require; I need to by
pass that.

T.

···

On Fri, Aug 12, 2005 at 06:14:21AM +0900, Ryan Leavengood wrote:

(Joel VanderWerf) #11

Mauricio Fernández wrote:

especially in 1.9, of course.

Do you mean because of support for yielding from the define_method
block, or something else?

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

(Austin Ziegler) #12

Very clever. Probably worth doing in the future. :wink:

-austin

···

On 8/12/05, Jim Weirich <jim@weirichhouse.org> wrote:

On Friday 12 August 2005 06:26 am, Mauricio Fernández wrote:
> > If unique names aren't used, the result is an infinite loop.
> I often prefer something like:
[... solution using define_method elided ...]
Clever ... I like that.

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

(7rans) #13

Learned something new pertaining to this today. You can override
require:

  module Kernel
    def require
      #...
    end
  end

And yet still access the original funtionality via the module-method:

  Kernel.require( ... )

Very handy.

T.

(7rans) #14

Sigh, on second thought, Kernel.require likely bypasses Gems too. Not
so handy.

T.

(gabriele renzi) #15

Trans ha scritto:

Learned something new pertaining to this today. You can override
require:

  module Kernel
    def require
      #...
    end
  end

And yet still access the original funtionality via the module-method:

  Kernel.require( ... )

Very handy.

just use super:

def require file
  new stuff
  super
end

the problem is that you can't stack overriding methods, you always get back to the original one

(7rans) #16

gabriele renzi wrote:

Trans ha scritto:
> Learned something new pertaining to this today. You can override
> require:
>
> module Kernel
> def require
> #...
> end
> end
>
> And yet still access the original funtionality via the module-method:
>
> Kernel.require( ... )
>
> Very handy.

just use super:

def require file
  new stuff
  super
end

the problem is that you can't stack overriding methods, you always get
back to the original one

Basicially my original idea, but w/o the module. Good idea. Thanks.

After trying every possibility multiple times using super does seem to
work the best.

T.