Ruby: explicit scoping on a class definition
If you define the Oyster INSIDE the MyAnimals class definition, then you get the answer that legs_in_oyster is 2.
If you define the Oyster separately--that is, you define it after LEGS = 2 has passed out of scope, you get the response of 4.
This suggests to me that the nested class is behaving differently than a namespace does, perhaps more like a closure.
---EDIT---
irb(main):076:0> class MyAnimals::RunningRoach < Animal; def using_legs; LEGS; end; end
=> nil
irb(main):077:0> MyAnimals::RunningRoach.new.kind_of?(MyAnimals)
=> false
irb(main):078:0> MyAnimals::RunningRoach.new.kind_of?(Animal)
=> true
irb(main):081:0> class MyAnimals::Mantis < MyAnimals; def killing_legs; LEGS; end; end
=> nil
irb(main):082:0> MyAnimals::Mantis.new.kind_of?(Animal)
=> false
irb(main):083:0> MyAnimals::Mantis.new.kind_of?(MyAnimals)
=> true
irb(main):084:0> MyAnimals::Mantis.new.killing_legs
=> 2
irb(main):085:0> MyAnimals::RunningRoach.new.using_legs
=> 4
According to "The Ruby Programming Language", constants are looked up in the Lexical Scope of the place where they are used first, and in the inheritance hierarchy second. So what is the lexical scope of something that inherits Animal? Animal itself, right? The MyAnimals class redefines LEGS, so anything that uses LEGS, and is defined inside MyAnimals, will look for LEGS inside MyAnimals first.
I think this example explains it best. Ruby searches for the constant definition in this order:
- The enclosing scope
Any outer scopes (repeat until top level is reached)Any outer scopes (up to but not including the top level- Included modules
- Superclass(es)
- Top level
- Object
- Kernel
EDIT
Thanks to Mark Amery for pointing out this error. The top-level is only reached in the case where there are no enclosing scopes and/or superclasses. The linked example actually makes this clear, sadly I read it wrong.
An example for this case:
FOO = 'I pity the foo!'
module One
FOO = 'one'
class Two
FOO = 'two'
def self.foo
FOO
end
end
class Three < Two
def self.foo
FOO
end
end
end
class Four
class Five < Four
def self.foo
FOO
end
end
end
describe FOO do
it "depends where it is defined" do
expect(FOO).to eq 'I pity the foo!' # top-level
expect(One::FOO).to eq 'one' # module
expect(One::Two.foo).to eq 'two' # class
expect(One::Three.foo).to eq 'one' # outer scope (One) comes before superclass
expect(Four::Five.foo).to eq 'I pity the foo!' # top-level
end
end