When do Ruby instance variables get set?
The first @hello
in your code is called a class instance variable.
It's an instance variable of the class object which the constant Hello
points to. (and which is an instance of the class Class
.)
Technically, when you're within the class
scope, your self
is set to the object of your current class, and @variables
pertain to your current self
. Boy I suck at explaining these things.
You can get all this and much more clarified to you by watching this collection of $5-each screencasts from The Pragmatic Programmers.
(Or you can ask for clarifications here and I'll try to update.)
Instance variables in ruby may be a bit confusing when first learning Ruby, especially if you are accustomed to another OO language like Java.
You cannot simply declare an instance variable.
One of the most important things to know about instance variables in ruby, apart from the notation with an @ sign prefix, is that they spring into life the first time they are assigned to.
class Hello
def create_some_state
@hello = "hello"
end
end
h = Hello.new
p h.instance_variables
h.create_some_state
p h.instance_variables
# Output
[]
["@hello"]
You can use the method Object#instance_variables
to list all instance variables of an object.
You normally “declare” and initialize all the instance variables in the initialize method. Another way to clearly document which instance variables that should be publicly available is to use the Module methods attr_accessor
(read/write), attr_writer
(write) and attr_reader
(read). These methods will synthesize different accessor methods for the listed instance variable.
class Hello
attr_accessor :hello
end
h = Hello.new
p h.instance_variables
h.hello = "hello"
p h.instance_variables
# Output
[]
["@hello"]
The instance variable still isn’t created until it’s assigned to using the synthesized Hello#hello=
method.
Another important issue, like kch described, is that you need to be aware of the different contexts active when declaring a class. When declaring a class the default receiver (self) in the outermost scope will be the object that represents the class itself. Hence your code will first create a class instance variable when assigning to @hello
on the class level.
Inside methods self will be the object on which the method is invoked, hence you are trying to print the value of an instance variable with the name @hello
in the object, which doesn’t exists (note that it’s perfectly legal to read a non existing instance variable).
there is a clear description in the book "The ruby programming language", read it will be very helpful. I paste it here(from chapter 7.1.16):
An instance variable used inside a class definition but outside an instance method definition is a class instance variable.
class Point
# Initialize our class instance variables in the class definition itself
@n = 0 # How many points have been created
@totalX = 0 # The sum of all X coordinates
@totalY = 0 # The sum of all Y coordinates
def initialize(x,y) # Initialize method
@x,@y = x, y # Sets initial values for instance variables
end
def self.new(x,y) # Class method to create new Point objects
# Use the class instance variables in this class method to collect data
@n += 1 # Keep track of how many Points have been created
@totalX += x # Add these coordinates to the totals
@totalY += y
super # Invoke the real definition of new to create a Point
# More about super later in the chapter
end
# A class method to report the data we collected
def self.report
# Here we use the class instance variables in a class method
puts "Number of points created: #@n"
puts "Average X coordinate: #{@totalX.to_f/@n}"
puts "Average Y coordinate: #{@totalY.to_f/@n}"
end
end
......
Because class instance variables are just instance variables of class objects, we can use attr, attr_reader, and attr_accessor to create accessor methods for them.
class << self
attr_accessor :n, :totalX, :totalY
end
With these accessors defined, we can refer to our raw data as Point.n, Point.totalX, and Point.totalY.
You need to add an initialize
method:
class Hello
def initialize
@hello = "hello"
end
def display
puts @hello
end
end
h = Hello.new
h.display