Dynamically modifying modules to avoid namespace collisions

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

···

--
Posted via http://www.ruby-forum.com/.

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

--
Posted via http://www.ruby-forum.com/\.

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.

···

On May 16, 2006, at 8:21 PM, Wes Gamble wrote:

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

--
Posted via http://www.ruby-forum.com/\.

[...]
} 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

···

On Wed, May 17, 2006 at 09:34:34AM +0900, Logan Capaldo wrote:

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

···

--
Posted via http://www.ruby-forum.com/.

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

--
Posted via http://www.ruby-forum.com/\.

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

···

On May 17, 2006, at 10:11 AM, Gregory Seidman wrote:

def find_require_file(filename)
  # NEED IMPLEMENTATION HERE
end

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

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.

···

On May 17, 2006, at 4:03 PM, Logan Capaldo wrote:

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

} 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

···

On Thu, May 18, 2006 at 02:20:17AM +0900, Wes Gamble wrote:

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.

···

On May 17, 2006, at 4:14 PM, Logan Capaldo wrote:

On May 17, 2006, at 4:03 PM, Logan Capaldo wrote:

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

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.