When do Ruby instance variables get set?

RubySyntaxInstance Variables

Ruby Problem Overview


class Hello
@hello = "hello"
	def display
		puts @hello
	end
end

h = Hello.new
h.display

I created the class above. It doesn't print anything out. I thought the instance variable @hello was set during the class declaration. But when I call the display method the output is 'nil'. What's the correct way to do this?

Ruby Solutions


Solution 1 - Ruby

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).

Solution 2 - Ruby

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

Solution 3 - Ruby

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.)

Solution 4 - Ruby

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.

Solution 5 - Ruby

I'd forgotten that there was a "class instance variable" concept in Ruby. In any case, the OP's problem seemed puzzling, and wasn't really addressed in any of the answers heretofore, except for a hint in kch's answer: it's a problem of scope. (Added on edit: Actually, sris's answer does address this point at the end, but I'll let this answer stand anyway, as I think the example code might be useful for understanding the problem.)

In a Ruby class, a variable name starting with @ can refer to one of two variables: either to an instance variable or to a class instance variable, depending on where in the class it's referred to. This is a fairly subtle gotcha.

An example will clarify the point. Here's a little Ruby test class (all code tested in irb):

class T

  @@class_variable = "BBQ"
  @class_instance_variable_1 = "WTF"
  @class_instance_variable_2 = "LOL"

  def self.class_method
    puts "@@class_variable           == #{@@class_variable           || 'nil'}"
    puts "@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
    puts "@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
    puts "@instance_variable         == #{@instance_variable         || 'nil'}"
  end

  def initialize
    @instance_variable = "omg"
    # The following line does not assign a value to the class instance variable,
    # but actually declares an instance variable withthe same name!
    @class_instance_variable_1 = "wtf"
    puts "@@class_variable           == #{@@class_variable           || 'nil'}"
    # The following two lines do not refer to the class instance variables,
    # but to the instance variables with the same names.
    puts "@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
    puts "@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
    puts "@instance_variable         == #{@instance_variable         || 'nil'}"
  end

  def instance_method
    puts "@@class_variable           == #{@@class_variable           || 'nil'}"
    # The following two lines do not refer to the class instance variables,
    # but to the instance variables with the same names.
    puts "@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
    puts "@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
    puts "@instance_variable         == #{@instance_variable         || 'nil'}"
  end

end

I named the variables according to what I thought they were, though that turns out to not to always be the case:

irb> T.class_method
@@class_variable           == BBQ
@class_instance_variable_1 == WTF    # the value of the class instance variable
@class_instance_variable_2 == LOL    # the value of the class instance variable
@instance_variable         == nil    # does not exist in the class scope
=> nil

irb> t = T.new
@@class_variable           == BBQ
@class_instance_variable_1 == wtf    # the value of the instance variable
@class_instance_variable_2 == nil    # the value of the instance variable
@instance_variable         == omg
=> #<T:0x000000015059f0 @instance_variable="omg", @class_instance_variable_1="wtf">

irb> t.instance_method
@@class_variable           == BBQ
@class_instance_variable_1 == wtf    # the value of the instance variable
@class_instance_variable_2 == nil    # the value of the instance variable
@instance_variable         == omg
=> nil

irb> T.class_method
@@class_variable           == BBQ
@class_instance_variable_1 == WTF    # the value of the class instance variable
@class_instance_variable_2 == LOL    # the value of the class instance variable
@instance_variable         == nil    # does not exist in the class scope
=> nil

The @@class_variable and @instance_variable always behave as you'd expect: the former is defined on the class level, and whether referred to in a class method or in an instance method, it holds value assigned to it at the top. The latter only gets a value in an object of class T, so in a class method, it refers to an unknown variable whose value is nil.

The class method imaginatively named class_method outputs the values of @@class_variable and the two @class_instance_variables as expected, that is, as initialized at the top of the class. However, in the instance methods initialize and instance_method, different variables of the same name are accessed, that is, instance variables, not class instance variables.

You can see that the assignment in the initialize method did not affect the class instance variable @class_instance_variable_1, because the later call of class_method outputs its old value, "WTF". Instead, method initialize declared a new instance variable, one which is also named (misleadingly) @class_instance_variable_1. The value assigned to it, "wtf", is output by methods initialize and instance_method.

The variable @class_instance_variable_2 in the example code is equivalent to variable @hello in the original problem: it's declared and initialized as a class instance variable, but when an instance method refers to a variable of that name, it actually sees an instance variable with the same name -- one which was never declared, so its value is nil.

Solution 6 - Ruby

I'd also recommend looking at class variables which are prefixed with "@@" - here's some sample code to show you how class and instance vars are different:

class Vars
  @@classvar="foo"
  def test
    @instancevar="bar"
  end
  def Vars.show
    puts "classvar: #{@@classvar}"
    puts "instancevar: #{@instancevar}"
  end
  def instance_show
    puts "classvar: #{@@classvar}"
    puts "instancevar: #{@instancevar}"
   
  end
end

# only shows classvar since we don't have an instance created
Vars::show
# create a class instance
vars = Vars.new
# instancevar still doesn't show b/c it hasn't been initialized
vars.instance_show
# initialize instancevar
vars.test
# now instancevar shows up as we expect
vars.instance_show

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionpez_dispenserView Question on Stackoverflow
Solution 1 - RubysrisView Answer on Stackoverflow
Solution 2 - RubyNathan KitchenView Answer on Stackoverflow
Solution 3 - RubykchView Answer on Stackoverflow
Solution 4 - Rubylfx_coolView Answer on Stackoverflow
Solution 5 - RubyTeemu LeistiView Answer on Stackoverflow
Solution 6 - RubySteve MidgleyView Answer on Stackoverflow