Variable Scope and Visibility
Syntax#
- $global_variable
- @@class_variable
- @instance_variable
- local_variable
Remarks#
Class variables are shared in the class hierarchy. This can result in surprising behavior.
class A
@@variable = :x
def self.variable
@@variable
end
end
class B < A
@@variable = :y
end
A.variable # :y
Classes are objects, so instance variables can be used to provide state that is specific to each class.
class A
@variable = :x
def self.variable
@variable
end
end
class B < A
@variable = :y
end
A.variable # :x
Local Variables
Local variables (unlike the other variable classes) do not have any prefix
local_variable = "local"
p local_variable
# => local
Its scope is dependent on where it has been declared, it can not be used outside the “declaration containers” scope. For example, if a local variable is declared in a method, it can only be used inside that method.
def some_method
method_scope_var = "hi there"
p method_scope_var
end
some_method
# hi there
# => hi there
method_scope_var
# NameError: undefined local variable or method `method_scope_var'
Of course, local variables are not limited to methods, as a rule of thumb you could say that, as soon as you declare a variable inside a do ... end
block or wrapped in curly braces {}
it will be local and scoped to the block it has been declared in.
2.times do |n|
local_var = n + 1
p local_var
end
# 1
# 2
# => 2
local_var
# NameError: undefined local variable or method `local_var'
However, local variables declared in if
or case
blocks can be used in the parent-scope:
if true
usable = "yay"
end
p usable
# yay
# => "yay"
While local variables can not be used outside of its block of declaration, it will be passed down to blocks:
my_variable = "foo"
my_variable.split("").each_with_index do |char, i|
puts "The character in string '#{my_variable}' at index #{i} is #{char}"
end
# The character in string 'foo' at index 0 is f
# The character in string 'foo' at index 1 is o
# The character in string 'foo' at index 2 is o
# => ["f", "o", "o"]
But not to method / class / module definitions
my_variable = "foo"
def some_method
puts "you can't use the local variable in here, see? #{my_variable}"
end
some_method
# NameError: undefined local variable or method `my_variable'
The variables used for block arguments are (of course) local to the block, but will overshadow previously defined variables, without overwriting them.
overshadowed = "sunlight"
["darkness"].each do |overshadowed|
p overshadowed
end
# darkness
# => ["darkness"]
p overshadowed
# "sunlight"
# => "sunlight"
Class Variables
Class variables have a class wide scope, they can be declared anywhere in the class. A variable will be considered a class variable when prefixed with @@
class Dinosaur
@@classification = "Like a Reptile, but like a bird"
def self.classification
@@classification
end
def classification
@@classification
end
end
dino = Dinosaur.new
dino.classification
# => "Like a Reptile, but like a bird"
Dinosaur.classification
# => "Like a Reptile, but like a bird"
Class variables are shared between related classes and can be overwritten from a child class
class TRex < Dinosaur
@@classification = "Big teeth bird!"
end
TRex.classification
# => "Big teeth bird!"
Dinosaur.classification
# => "Big teeth bird!"
This behaviour is unwanted most of the time and can be circumvented by using class-level instance variables.
Class variables defined inside a module will not overwrite their including classes class variables:
module SomethingStrange
@@classification = "Something Strange"
end
class DuckDinosaur < Dinosaur
include SomethingStrange
end
DuckDinosaur.class_variables
# => [:@@classification]
SomethingStrange.class_variables
# => [:@@classification]
DuckDinosaur.classification
# => "Big teeth bird!"
Global Variables
Global variables have a global scope and hence, can be used everywhere. Their scope is not dependent on where they are defined. A variable will be considered global, when prefixed with a $
sign.
$i_am_global = "omg"
class Dinosaur
def instance_method
p "global vars can be used everywhere. See? #{$i_am_global}, #{$another_global_var}"
end
def self.class_method
$another_global_var = "srsly?"
p "global vars can be used everywhere. See? #{$i_am_global}"
end
end
Dinosaur.class_method
# "global vars can be used everywhere. See? omg"
# => "global vars can be used everywhere. See? omg"
dinosaur = Dinosaur.new
dinosaur.instance_method
# "global vars can be used everywhere. See? omg, srsly?"
# => "global vars can be used everywhere. See? omg, srsly?"
Since a global variable can be defined everywhere and will be visible everywhere, calling an “undefined” global variable will return nil instead of raising an error.
p $undefined_var
# nil
# => nil
Although global variables are easy to use its usage is strongly discouraged in favour of constants.
Instance Variables
Instance variables have an object wide scope, they can be declared anywhere in the object, however an instance variable declared on class level, will only be visible in the class object. A variable will be considered an instance variable when prefixed with @
. Instance variables are used to set and get an objects attributes and will return nil if not defined.
class Dinosaur
@base_sound = "rawrr"
def initialize(sound = nil)
@sound = sound || self.class.base_sound
end
def speak
@sound
end
def try_to_speak
@base_sound
end
def count_and_store_sound_length
@sound.chars.each_with_index do |char, i|
@sound_length = i + 1
p "#{char}: #{sound_length}"
end
end
def sound_length
@sound_length
end
def self.base_sound
@base_sound
end
end
dino_1 = Dinosaur.new
dino_2 = Dinosaur.new "grrr"
Dinosaur.base_sound
# => "rawrr"
dino_2.speak
# => "grrr"
The instance variable declared on class level can not be accessed on object level:
dino_1.try_to_speak
# => nil
However, we used the instance variable @base_sound
to instantiate the sound when no sound is passed to the new method:
dino_1.speak
# => "rawwr"
Instance variables can be declared anywhere in the object, even inside a block:
dino_1.count_and_store_sound_length
# "r: 1"
# "a: 2"
# "w: 3"
# "r: 4"
# "r: 5"
# => ["r", "a", "w", "r", "r"]
dino_1.sound_length
# => 5
Instance variables are not shared between instances of the same class
dino_2.sound_length
# => nil
This can be used to create class level variables, that will not be overwritten by a child-class, since classes are also objects in Ruby.
class DuckDuckDinosaur < Dinosaur
@base_sound = "quack quack"
end
duck_dino = DuckDuckDinosaur.new
duck_dino.speak
# => "quack quack"
DuckDuckDinosaur.base_sound
# => "quack quack"
Dinosaur.base_sound
# => "rawrr"