Dynamically modifying modules to avoid namespace collisions


#1

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


#2

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 G. 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


#3

On May 16, 2006, at 8:21 PM, Wes G. 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.


#4

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


#5

On Wed, May 17, 2006 at 09:34:34AM +0900, Logan C. 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


#6

Wes G. 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


#7

Greg,

How does this

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

work?

Wes

Gregory S. wrote:

On Wed, May 17, 2006 at 09:34:34AM +0900, Logan C. 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


#8

On May 17, 2006, at 10:11 AM, Gregory S. 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


#9

On May 17, 2006, at 4:03 PM, Logan C. 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.


#10

On Thu, May 18, 2006 at 02:20:17AM +0900, Wes G. wrote:
} Greg,
}
} How does this
}
} str.to_s.split("::").inject(Object) { |ns,name| ns.const_get(name) }
}
} work?

Magic :slight_smile:

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


#11

On May 17, 2006, at 4:14 PM, Logan C. 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:infind_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.