I am encountering a strange bug in my code. I have a rails application with the following two files in the lib:
lib/module_one/module_two/class_one.rb
module ModuleOne
module Moduletwo
class ClassOne
class << self
def test
puts 'Class one'
ClassTwo.test
end
end
end
end
end
and
lib/module_one/module_two/class_two.rb
module ModuleOne
module ModuleTwo
class ClassTwo
def self.test
puts 'Class two'
end
end
end
end
Now my problem is, that when I go into the console and write:
ModuleOne::ModuleTwo::ClassOne.test
it throws the following: NameError: uninitialized constant ClassTwo
The strange thing is, that the problem seems to be connected to the use of class << self
instead of self.method
. If I change the class_one.rb file like this it works!:
module ModuleOne
module ModuleTwo
class ClassOne
def self.test
puts 'Class one'
ClassTwo.test
end
end
end
end
Im loading the files in application.rb like this:
config.autoload_paths += %W(#{config.root}/lib)
Is this a bug in rails, or is it just me getting something all wrong?
Im using rails 3.1.3 btw
So first off, the basic process for constant resolution is that ruby first searches the lexical scope of the receiver (the class or module enclosing the reference) for ClassTwo
and when it can't find it, it goes up a level ( Module.nesting
returns this search path) and so on. In your case that means looking for ModuleOne::ModuleTwo::ClassOne:ClassTwo
, then ModuleOne::ModuleTwo::ClassTwo
, then ModuleOne::ClassTwo
and so on.
If that fails the ruby looks in the inheritance hierarchy of the enclosing class/module (eg has the superclass of ClassOne defined something. The ancestors
method on module returns this search path. Lastly, the top level constants are searched.
Back to rails's magic loading. Here a const_missing
hook is added by rails that is called when ruby can't find the class, which basically tries to replicate this search logic, seeing at each step whether a file could have been loaded which would contain the missing constant.
Ideally ruby would pass the search path (ie the nesting) to search through, but unfortunately it doesn't: when you reference ClassTwo
, const_missing
gets called with just 'ClassTwo'.
Rails guesses the nesting by prepending that with the name of the class on which const_missing
is being called (ie the class enclosing the access to the constant). For example, in your second example it ends up with ModuleOne::ModuleTwo::ClassOne::ClassTwo
. You can see this easily enough by defining const_missing
to log what it's called with
class Object
def self.const_missing missing_name
puts "qualified name is #{self.name}::#{missing_name}"
super
end
end
Rails then strips off 'ClassOne' and tries ModuleOne::ModuleTwo::ClassTwo
and so on up the chain.
So why does class << self
make a difference? If you repeat your first case with the const_missing
logging you'd see that the logged qualified name is now just ::ClassTwo
. const_missing
is now being called on ClassOne's metaclass, and because class << self
hasn't been assigned to a constant, it has no name and thus rails' attempt to fudge the nesting doesn't work.
This opens the door to a horrible workaround:
module ModuleOne
module ModuleTwo
class ClassOne
class << self
def test
puts 'Class one'
ClassTwo.test
end
end
FOO = class << self; self; end
end
end
end
Because the class that const_missing gets called on now has a name (ModuleOne::ModuleTwo::ClassOne::FOO) rails' workaround now works.
Dave's workaround works I think because const_missing gets called on ModuleOne::ModuleTwo::ClassOne
rather than the anonymous eigenclass/metaclass.
The real fix would be for ruby to pass const_missing
a nesting. There is a bug logged against ruby to this effect although it has been open for a long time. So yes, this could be considered a bug in the magic loading stuff (there are other edge cases) but the underlying reason is a weakness in the ruby api that forces the use of brittle workarounds.
(Only a partial answer, but need formatting.)
It's because of how class << self
works.
For example, if you change it to:
class << self
def test
self::ClassTwo.test
end
end
it works fine.
Edit; too long for reasonable comment.
I'm poking around a bit... On an intuitive level it makes sense to me, I'm just not sure why yet. Don't know if I knew a real reason once, or if I'm just making it up.
I'm not sure why self
seems to refer to the module, though; the "Programming Ruby 1.9" book doesn't go in to enough depth on the class <<
semantics. I'll tweet something and refer to this question and someone smarter will create a real answer.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.