Forum: Ruby Dynamically modifying modules to avoid namespace collisions

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Bb4bdf2b184027bc38d4fb529770cde5?d=identicon&s=25 Wes Gamble (weyus)
on 2006-05-17 02:19
All,

I have a namespace collision problem between a Rubygem (htmltools 1.09)
and a Rails framework class (in ActionPack 1.12.1).

In order to use both, I will need to modify the namespace of one of
them.

However, I would really like to not have to modify the existing gems and
I was thinking that maybe there might be some clever way to use the
Module class to "wrap" the offending module redefine it's namespace.

Has anyone ever done anything like this?

Otherwise, I will be looking at "taking ownership" of the gem code and
moving it to a locally - controlled location essentially freezing the
version of that Gem for my application

Thanks,
Wes
Bb4bdf2b184027bc38d4fb529770cde5?d=identicon&s=25 Wes Gamble (weyus)
on 2006-05-17 02:21
Hey, I just thought of something.

Is there a way to unload ("un-require") a module at runtime?  Is there a
way to load ("re-require") a module at runtime?

Thanks,
Wes

Wes Gamble wrote:
> All,
>
> I have a namespace collision problem between a Rubygem (htmltools 1.09)
> and a Rails framework class (in ActionPack 1.12.1).
>
> In order to use both, I will need to modify the namespace of one of
> them.
>
> However, I would really like to not have to modify the existing gems and
> I was thinking that maybe there might be some clever way to use the
> Module class to "wrap" the offending module redefine it's namespace.
>
> Has anyone ever done anything like this?
>
> Otherwise, I will be looking at "taking ownership" of the gem code and
> moving it to a locally - controlled location essentially freezing the
> version of that Gem for my application
>
> Thanks,
> Wes
E34b5cae57e0dd170114dba444e37852?d=identicon&s=25 Logan Capaldo (Guest)
on 2006-05-17 02:37
(Received via mailing list)
On May 16, 2006, at 8:21 PM, Wes Gamble wrote:

> Hey, I just thought of something.
>
> Is there a way to unload ("un-require") a module at runtime?  Is
> there a
> way to load ("re-require") a module at runtime?
>
> Thanks,
> Wes
>

I think you may be over-estimating the relationship between modules
and files.
One does not require modules, one requires files. One file may have
many modules in it, or none. Regardless one can still 'require' that
file.

One way to hack something so it gets stuck in a module is:

module Protect
   eval(File.read("path/to/file.rb"))
end

Then you access all the classes defined in file.rb like:

Protect::Node.new # etc.
Bf6862e2a409078e13a3979c00bba1d6?d=identicon&s=25 Gregory Seidman (Guest)
on 2006-05-17 16:12
(Received via mailing list)
On Wed, May 17, 2006 at 09:34:34AM +0900, Logan Capaldo wrote:
[...]
} One does not require modules, one requires files. One file may have
} many modules in it, or none. Regardless one can still 'require' that
} file.
}
} One way to hack something so it gets stuck in a module is:
}
} module Protect
}   eval(File.read("path/to/file.rb"))
} end
}
} Then you access all the classes defined in file.rb like:
}
} Protect::Node.new # etc.

This is a great idiom, but it needs to be able to function as a (nearly)
drop-in replacement for require. This means that I need to be able to
find
the file in the search path that require would normally use. Consider
the
following:

def find_require_file(filename)
  # NEED IMPLEMENTATION HERE
end

def namespace_get(str)
  str.to_s.split("::").inject(Object) { |ns,name| ns.const_get(name) }
end

def require_within(module_name, filename)
  file = find_require_file(file)
  mod = module_name
  case module_name
    when Module,Class
      #noop
    else
      mod = namespace_get(mod)
  end
  mod.instance_eval { eval File.read(file) }
end

This is untested, and I don't know how to implement that
find_require_file,
but the idea is that the following should work:

module Foo
  module Bar
  end
end

require_within Foo::Bar, "date"

x = Foo::Bar::Date.today

Anyone want to implement find_require_file and test the idea?

--Greg
Bb4bdf2b184027bc38d4fb529770cde5?d=identicon&s=25 Wes Gamble (weyus)
on 2006-05-17 17:23
Greg,

This is a good idea.  Would make it easier to handle these sorts of
issues.
I may give it a shot.  I'll let you know if I come up with anything.

Wes
Bb4bdf2b184027bc38d4fb529770cde5?d=identicon&s=25 Wes Gamble (weyus)
on 2006-05-17 19:20
Greg,

How does this

str.to_s.split("::").inject(Object) { |ns,name| ns.const_get(name) }

work?

Wes

Gregory Seidman wrote:
> On Wed, May 17, 2006 at 09:34:34AM +0900, Logan Capaldo wrote:
> [...]
> } One does not require modules, one requires files. One file may have
> } many modules in it, or none. Regardless one can still 'require' that
> } file.
> }
> } One way to hack something so it gets stuck in a module is:
> }
> } module Protect
> }   eval(File.read("path/to/file.rb"))
> } end
> }
> } Then you access all the classes defined in file.rb like:
> }
> } Protect::Node.new # etc.
>
> This is a great idiom, but it needs to be able to function as a (nearly)
> drop-in replacement for require. This means that I need to be able to
> find
> the file in the search path that require would normally use. Consider
> the
> following:
>
> def find_require_file(filename)
>   # NEED IMPLEMENTATION HERE
> end
>
> def namespace_get(str)
>   str.to_s.split("::").inject(Object) { |ns,name| ns.const_get(name) }
> end
>
> def require_within(module_name, filename)
>   file = find_require_file(file)
>   mod = module_name
>   case module_name
>     when Module,Class
>       #noop
>     else
>       mod = namespace_get(mod)
>   end
>   mod.instance_eval { eval File.read(file) }
> end
>
> This is untested, and I don't know how to implement that
> find_require_file,
> but the idea is that the following should work:
>
> module Foo
>   module Bar
>   end
> end
>
> require_within Foo::Bar, "date"
>
> x = Foo::Bar::Date.today
>
> Anyone want to implement find_require_file and test the idea?
>
> --Greg
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-17 20:07
(Received via mailing list)
Wes Gamble wrote:
> How does this
>
>   str.to_s.split("::").inject(Object) { |ns,name| ns.const_get(name) }
>
> work?

It takes a string on the form "A::B::C" and splits it into an array;
["A", "B", "C"]. When calling #inject on that array, you iterate over
each element, yielding them to the block (the second parameter). The
first parameter is the result of the last call to the block, or, if it's
the first element, the default value, here Object.

   %w(A B C).inject(Object) {|parent, child| parent.const_get(child) }

is the same as

   Object.const_get(:A).const_get(:B).const_get(:C)


Hope that made it clearer


Daniel
E34b5cae57e0dd170114dba444e37852?d=identicon&s=25 Logan Capaldo (Guest)
on 2006-05-17 22:06
(Received via mailing list)
On May 17, 2006, at 10:11 AM, Gregory Seidman wrote:

> def find_require_file(filename)
>   # NEED IMPLEMENTATION HERE
> end

def find_require_file(filename)
   $LOAD_PATH.each do |directory|
     [ ".rb", ".so", ".dll" ].each do |ext|
       if File.exists?(f = File.join(directory, filename + ext))
         return f
       end
     end
   end
   # otherwise we didn't find it
   raise 'Could not find #{filename} in $LOAD_PATH.'
end
E34b5cae57e0dd170114dba444e37852?d=identicon&s=25 Logan Capaldo (Guest)
on 2006-05-17 22:16
(Received via mailing list)
On May 17, 2006, at 4:03 PM, Logan Capaldo wrote:

>       if File.exists?(f = File.join(directory, filename + ext))
>         return f
>       end
>     end
>   end
>   # otherwise we didn't find it
>   raise 'Could not find #{filename} in $LOAD_PATH.'
> end
>
>
>

Actually in retrospect, I should have used double quotes in the raise
and if you know you're always gonna be eval-ing these files, you
probably want to get rid of the .dll and .so extensions.
Bf6862e2a409078e13a3979c00bba1d6?d=identicon&s=25 Gregory Seidman (Guest)
on 2006-05-17 22:50
(Received via mailing list)
On Thu, May 18, 2006 at 02:20:17AM +0900, Wes Gamble wrote:
} Greg,
}
} How does this
}
} str.to_s.split("::").inject(Object) { |ns,name| ns.const_get(name) }
}
} work?

Magic :-)

So suppose you have a string "Foo::Bar" and you want to retrieve the
module
(or class, or other constant) Bar from the module (or class) Foo. (For
simplicity we will assume that both are modules.) First, we make sure
it's
a string and split it on "::" which results in the array ["Foo", "Bar"
].
Now we use inject with its optional starting object.

For the first iteration of inject, ns = Object and name = "Foo" so the
value of ns.const_get(name) is Foo (the module). On the next iteration,
ns = Foo and name = "Bar" so the value of ns.const_get(name) is Bar (the
module). That's the end of the iteration, so the value of the entire
line
is Bar, which is the value of the method call, which is what you wanted
in
the first place.

} Wes
--Greg
E34b5cae57e0dd170114dba444e37852?d=identicon&s=25 Logan Capaldo (Guest)
on 2006-05-18 03:04
(Received via mailing list)
On May 17, 2006, at 4:14 PM, Logan Capaldo wrote:

>> def find_require_file(filename)
>>
>>
>>
>
> Actually in retrospect, I should have used double quotes in the
> raise and if you know you're always gonna be eval-ing these files,
> you probably want to get rid of the .dll and .so extensions.
>
>

One more thing, this won't work with gems unless you use require_gem.

e.g.

% irb
irb(main):001:0>
irb(main):002:0* def find_require_file(filename)
irb(main):003:1>   $LOAD_PATH.each do |directory|
irb(main):004:2*     [ ".rb", ".so", ".dll" ].each do |ext|
irb(main):005:3*       if File.exists?(f = File.join(directory,
filename + ext))irb(main):006:4>         return f
irb(main):007:4>       end
irb(main):008:3>     end
irb(main):009:2>   end
irb(main):010:1>   # otherwise we didn't find it
irb(main):011:1*   raise 'Could not find #{filename} in $LOAD_PATH.'
irb(main):012:1> end
=> nil
irb(main):013:0> find_require_file('parse_tree')
RuntimeError: Could not find #{filename} in $LOAD_PATH.
         from (irb):11:in `find_require_file'
         from (irb):13
irb(main):014:0> require 'rubygems'
=> true
irb(main):015:0> find_require_file('parse_tree')
RuntimeError: Could not find #{filename} in $LOAD_PATH.
         from (irb):11:in `find_require_file'
         from (irb):15
irb(main):016:0> require_gem 'ParseTree'
=> true
irb(main):017:0> find_require_file('parse_tree')
=> "/usr/local/ruby/lib/ruby/gems/1.8/gems/ParseTree-1.4.1/lib/
parse_tree.rb"
irb(main):018:0>

Since of how rubygems alter the load path. If you are using a gem, as
long as it doesn't use the deprecated (and evil) autorequire
attribute, you should still be able to safely stick it in its own
namespace.
This topic is locked and can not be replied to.