The rule of variable scope if different than that of Local Variable Scope although it shares some similarity. First we need to distinguish scope of instance and class (and constants).
Instance Variable Scope
Instance variables (the one that starts with @
) are scoped at the object level. Local variable are accessible from every object’s instance methods:
class Flamingo
def initialize
@flying_sound = 'flap flap'
end
def listen
@flying_sound
end
def speak
@speak
end
end
flamingo = Flamingo.new
flamingo.listen # => 'flap flap'
flamingo.speak # => nil
Contrary to local variable, referencing an uninitialized local variable will not throw a NameError
; you’d get nil
.
Class Variable Scope
Class variables start with @@
and are scoped at class level. They are accessible from class methods. They are also accessible from instance methods and are shared across all instances.
Constant Variable Scope
Constant variables, usually called just constants because you are not supposed to re-assign them, are variables beginning with a capital letter but generally written in all capitals. They have the trickiest scope: lexical scope.
class Animal
LEGS = 4
def leg_number
"#{self.class} has #{LEGS} legs"
end
end
class Flamingo < Animal
LEGS = 2
end
puts Animal.new.leg_number # => Animal has 4 legs
puts Flamingo.new.leg_number # => Flamingo has 4 legs
The last line provides an unexpected output due to lexical scoping of the constant LEGS
initialized in the Animal
class. Flamingo
inherits Animal#leg_number
by way of inheritance from its superclass, but the call to to_s
(using #{}
) passing in constant LEGS
is done inside the Animal
class despite the inheritance. In this context, the first on only constant named LEGS
that Ruby sees is the one inside Animal
class.
There are two ways to remedy to this situation:
-
Overriding
Animal#leg_number
withFlamingo#leg_number
method -
Using a scope resolution operator (
::
).
Overriding
class Animal
LEGS = 4
def leg_number
"#{self.class} has #{LEGS} legs"
end
end
class Flamingo < Animal
LEGS = 2
def leg_number
"#{self.class} has #{LEGS} legs"
end
end
puts Animal.new.leg_number # => Animal has 4 legs
puts Flamingo.new.leg_number # => Flamingo has 2 legs
By overriding Animal#leg_number
with Flamingo#leg_number
, we are changing the scope form the superclass to the class. When looking for constant LEGS
, Ruby look inside the current scope and finds LEGS = 2
.
Scope Resolution Operator
class Animal
LEGS = 4
def leg_number
"#{self.class} has #{self.class::LEGS} legs"
end
end
class Flamingo < Animal
LEGS = 2
end
puts Animal.new.leg_number # => Animal has 4 legs
puts Flamingo.new.leg_number # => Flamingo has 2 legs
In this case, we are still in the context of the Animal
, however we are not looking for LEGS
anymore : we are looking for self.class::LEGS
. Calling self.class
is equivalent to calling the class of the current instantiated object, i.e. Flamingo
. What Ruby is doing is looking for Flamingo::LEGS
which exists, and is pointing to integer 2
.