Skip to content

Instantly share code, notes, and snippets.

@stevecj
Created March 16, 2012 18:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stevecj/2051705 to your computer and use it in GitHub Desktop.
Save stevecj/2051705 to your computer and use it in GitHub Desktop.
Understanding and unscrambling Ruby's weird module nesting behavior
# Get ourselves a clean, top-level binding.
def main_binding
binding
end
module ModuleUtils
module ModuleMethods ; end
self.extend ModuleMethods
module ModuleMethods
# Get a binding with a Module.nesting list that contains the
# given module and all of its containing modules as described
# by its fully qualified name in inner-to-outer order.
def module_path_binding(mod)
raise ArgumentError.new(
"Can't determine path nesting for a module with a blank name"
) if mod.name.to_s.empty?
m, b = nil, main_binding
mod.name.split('::').each do |part|
m, b =
eval(
"[ #{part} , #{part}.module_eval('binding') ]",
b
)
end
raise "Module found at name path not same as specified module" unless m == mod
b
end
end
end
module A
class B
module C
end
end
end
module X
module Y
# Since we are in a code execution path that includes "module X"
# and "module Y", but not "module A" and "module B", X and X::Y
# are in the current module nesting, but A::B and A are not.
# Calling .module_eval adds the module it was called for, so
# A::B::C is in the nesting.
p A::B::C.module_eval('Module.nesting')
# => [A::B::C, X::Y, X]
# Get a binding with a sane module nesting for the path to A::B::C.
abc_mod_binding = ModuleUtils::module_path_binding(A::B::C)
# Module.nesting in this binding context makes sense for A::B::C
p eval('Module.nesting', abc_mod_binding)
# => [A::B::C, A::B, A]
# Methods are closures that hold onto the current Module.nesting
# in the code execution context from which they were "def" defined.
# Think of executing "def" kind of like passing a block to a
# module method that stores it for later invocation.
abc_mod = A::B::C
def abc_mod.foo ; Module.nesting ; end
def abc_mod.bar ; C ; end
# Foo executes in the context of the screwed up module nesting.
# A::B::C -is- in the nesting though, so apparently
# "def <module>.<method> ..." acts pretty much like
# "<module>.module_eval('def ...", ensuring that at least the
# self module is in the nesting list.
p A::B::C.foo
# => [A::B::C, X::Y, X]
# Even though A::B::C is in Module.nesting, so the method would
# see constants within that, it cannot see itself through an
# unqualified "C" reference since it does not see the A::B module
# in which "C" constant is defined.
p begin ; A::B::C.bar ; rescue Exception => e ; e ; end
# => #<NameError: uninitialized constant X::Y::C>
# Create some methods on A::B::C using our nice, clean A::B::C
# binding. These will execute in that context instead of the
# current X::Y context.
eval( 'def self.bar ; Module.nesting ; end' , abc_mod_binding )
eval( 'def self.baz ; C ; end' , abc_mod_binding )
# Runs in the clean A::B::C nesting context that was in the
# current binding when it was defined.
p A::B::C.bar
# => [A::B::C, A::B, A]
# Can see its self (A::B::C) as "C" since constant "C" containing
# that module is defined in A::B, which is 2nd in the
# Module.nesting list [A::B::C, A::B, A].
p A::B::C.baz
# => A::B::C
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment