Wanting to see if it would be possible to somehow dynamically modify Module.nesting I hacked around looking for ways to do this for a long time in 1.8 to no avail. But it seems that it’s somewhat trivial in 1.9.
module Kernel
def with_module(*consts, &blk)
slf = blk.binding.eval('self')
l = lambda { slf.instance_eval(&blk) }
consts.reverse.inject(l) {|l, k| lambda { k.class_eval(&l) } }.call
end
end
Allows you to do:
module X
module Y
module Z
end
end
module Y2
class Z2
end
end
end
x, @x = 5, 6
with_module(X) do
p Y, Y2 # => X::Y, X::Y2
with_module(Y, Y2) do
p Z, Z2 # => X::Y::Z, X::Y2::Z2
p x, @x # => 5, 6
end
end
Without losing the scope of your blocks.
Now should you ever do this? Probably not. It’s usually better to actually nest inside the modules you want to be in the lexical scope of. Or another technique, used sometimes testing situations, is to shorten a long constant name with an abbreviation constant like:
XYZ10 = X::Y::Z::Ten ... XYZ10.new
Please let me know if you think of a use case which one might consider “valid” or useful for with_module.
One interesting aspect of figuring out how to make this method work was discovering some special properties of the *eval() methods.
The following will not work:
class X
class Y
end
end
l = lambda { p Y }
X.class_eval { l.call } # uninitialized constant Y (NameError)
But this will
l = lambda { p Y }
X.class_eval(&l) # => X::Y
And so will this
l = lambda { p Y }
s = self
X.class_eval { s.instance_eval(&l) } # => X::Y
When you eval a block directly rather than calling call on it, it will use the lexical scope of the calling scope rather than its own lexical scope at the time the block was created.
module G
class N; end
$l = lambda { p N }
end
# using the lexical scope of the lambda when it was created
$l.call # => G::N
# using the current lexical scope + X
X.class_eval &$l # uninitialized constant X::N

“When you eval a block directly rather than calling call on it, it will use the lexical scope of the calling scope rather than its own lexical scope at the time the block was created.”
Ah, not only does this make sense, but its also a nice way to sort out my confusion about those differences in a single sentence :-)
Comment by Toledo — May 19, 2009 @ 12:59 pm
Your “with_const” should really be called “with_module”, no? I don’t know why you are fiddling with the binding of the block, but it’s not doing anything, really. You can rewrite your method as:
def with_module(*mods, &blk)
mods.inject(blk) {|blk, mod| lambda{ mod.module_eval(&blk) }}.call
end
The fact is, you can use module_eval directly:
x, @x = 5, 6
X.module_eval do
p Y, Y2 # => X::Y, X::Y2
Y.module_eval do
Y2.module_eval do
p Z, Z2 # => X::Y::Z, X::Y2::Z2
p x, @x # => 5, 6
end
end
end
In Ruby 1.8 or 1.9, module_eval doesn’t have the same semantics for constants, so indeed I don’t know of a good way to do that in 1.8.
Cheers
Comment by Marc-Andre Lafortune — May 21, 2009 @ 2:22 pm
Hey Marc
Good point about the name, with_module makes more sense.
The reason why I fiddle with the binding of the block and why you CANNOT use module_eval directly is that module_eval modifies self. Which means @x will not be 5 it will be whatever @x is for the module you are currently evaling, most likely nil.
class X;end; @x=5; X.module_eval { @x } # => nil
So by fiddling with the block I can always eval with the same self the block was using.
Comment by coderrr — May 21, 2009 @ 2:48 pm
Oh, yes, sorry I didn’t see that. Cute result, then!
Comment by Marc-Andre Lafortune — May 22, 2009 @ 6:26 am
[...] to the metaclass of our ProxyObject instance, which doesn’t help us at all. But… my last blog post to the rescue! In ruby 1.9 we can dynamically add modules to our lexical scope. So all we need to [...]
Pingback by Fixing constant lookup in DSLs in Ruby 1.9 « coderrr — June 2, 2009 @ 8:16 pm