Ruby: Or, how to get the anonymous module from Kernel#load with wrap=true?
With the same technique than in my earlier article it is also possible to capture the anonymous wrapper module that Kernel#load method with wrap=true uses to build the sandbox. This way you can load configuration or other data without polluting your namespace.
File b.rb:
class Foo
def bar
puts "bar"
end
end
throw :wrapper, Module.nesting.last
File main2.rb:
mod = catch :wrapper do
load File.expand_path('b.rb'), true
end
print "Anonymous module is '#{mod}' (#{mod.class})\n"
mod::Foo.new.bar
print "This will not work due the sandbox:\n"
Foo.new.bar
Output:
Anonymous module is '<Module:0x007fbccc2ace08>' (Module)
bar
This will not work due the sandbox:
main2.rb:7:in `<main>': uninitialized constant Foo (NameError)
This can be packaged to a module for easier usage. Unfortunately the call of Module.nesting needs to be lexically in the loaded file for this to work so it cannot be embedded to Sandbox module without some trickery like providing an empty block for the exit method. One implementation could be the following.
File c.rb:
class Foo
def bar
puts "bar"
end
end
Sandbox.exit {}
File main3.rb:
module Sandbox
def self.load(file)
catch :sandbox do
Kernel.load file, true
end
end
def self.exit(&block)
throw :sandbox, block.binding.eval("Module.nesting.last")
end
end
mod = Sandbox.load('c.rb')
print "Anonymous module is '#{mod}' (#{mod.class})\n"
mod::Foo.new.bar
print "This will not work due the sandbox:\n"
Foo.new.bar
Output:
Anonymous module is '<Module:0x007f4d8b4f8500>' (Module)
bar
This will not work due the sandbox:
main3.rb:16:in `<main>': uninitialized constant Foo (NameError)
Simple and elegant :)