From: Kornelius Kalnbach Date: 2010-02-13T02:17:23+09:00 Subject: [ruby-core:28157] [Feature #2740] Extend const_missing to pass in the nesting Issue #2740 has been updated by Kornelius Kalnbach. > In order to achieve this, I propose that we extend the const_missing > method to take an optional second parameter containing the nesting: > > class Foo > class Bar > def self.const_missing(id, nesting) > id == :Baz > nesting == [Foo::Bar] # first case > nesting == [Foo::Bar, Foo] # second case > end > end > end > > This would allow people who wish to do their own custom constant > loading (such as Rails) to do so in a way that is consistent with Ruby's > own constant lookup. In order to avoid backward-compatibility issues, we > can check the arity of the const_missing method, and only pass in the > nesting if a second parameter was provided. Interesting idea. However, I see two problems with the proposal: 1. It changes the const_missing API, breaking code. It would be nice to find a way around this. 2. The feature Module.nesting is dependent on the "point of the call". As you described, const_missing is not. I'm not sure about the implications; Module.nesting is defined in eval.c, while const_missing and const_get are defined in variable.c. There may be another way around this. method_missing makes it possible to use Ruby's inheritance chain again by calling super. So, what if we just follow the constant lookup nesting chain again when const_missing didn't return anything? You wouldn't even have to write the logic yourself. The implementation could look something like this: class Module # classic-style const_missing def const_missing(id) namespace = "#{name}::" unless self == Object puts "Searching for #{namespace}#{id}..." nil end end class Module # new-style constant lookup, given nesting # This would have to be implemented in C. def const_get2 id, nesting nesting += [Object] # Don't forget top level. # As usual: Try to find existing constant (without const_missing). for mod in nesting # imitating constant lookup without const_missing if mod.constants.map { |c| c.to_sym }.include? id.to_sym return mod.const_get(id) end end # New style: Call const_missing on each nesting level. for mod in nesting value = mod.const_missing id return value if value end # As usual: Raise NameError if nothing was found. raise NameError, "uninitialized constant #{name}::#{id}" end end class Foo class Bar const_get2(:Baz, Module.nesting) # >> Searching for Foo::Bar::Baz... # >> Searching for Foo::Baz... # >> Searching for Baz... #~> uninitialized constant Foo::Bar::Baz (NameError) end end class Foo::Bar const_get2(:Baz, Module.nesting) # >> Searching for Foo::Bar::Baz... # >> Searching for Baz... #~> uninitialized constant Foo::Bar::Baz (NameError) end This solves 1., but not 2. Calling const_get and would still depend on Module.nesting. Also, a constant with a value of nil or false would be interpreted as "not found". Maybe we could define a protocol for this, something like StopIteration? NoConstantError = Class.new NameError class Module def const_missing(id) ... raise NoConstantError end end class Module def const_get2 id, nesting ... for mod in nesting begin return mod.const_missing(id) rescue NoConstantError # continue end end ... end end On the other hand, maybe implicit constant lookup through the module nesting *and* automatic loading is a bit too much ;-) ---------------------------------------- http://redmine.ruby-lang.org/issues/show/2740 ---------------------------------------- http://redmine.ruby-lang.org