RubyCat
RubyCat

Reputation: 165

Class method is accessing instance variable

I'd like to understand this code. Why is it returning Hello instead of Howdy! ?

class Speaker
  @message = "Hello!"

  class << self
    @message = "Howdy!"

    def speak
      @message
    end
  end
end

puts Speaker.speak

Upvotes: 1

Views: 70

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110745

Here is your code, except I've defined the class method in the usual way (def self.speak...). As a class method is nothing more than an instance method defined on the class' singleton class, this change is merely a different way of creating the same class method. (If you doubt that, run the code below both ways.) I made that change because I thought it would make my explanation of what is happening clearer. I also added a puts statement.

class Speaker
  @message = "Hello!"

  def self.speak
    puts "self=#{self}"
    @message
  end

  class << self
    @message = "Howdy!"
  end
end

The first line of the class definition creates a class instance variable @message:

Speaker.instance_variables
  #=> [:@message]
Speaker.instance_variable_get(:@message)
  #=> "Hello!"

By constrast,

@message = "Howdy!"

creates an instance variable on Speaker's singleton class:

Speaker.singleton_class.instance_variables
  #=> [:@message]
Speaker.singleton_class.instance_variable_get(:@message)
  #=> "Howdy!"

Now invoke speak on Speaker:

Speaker.speak
  # self=Speaker
  #=> "Hello!" 

As self #=> Speaker, speak is obviously returning the value of the class instance variable.

For speak to return the value of the instance variable defined on Speaker's singleton class we can write the following:

class Speaker
  @message = "Hello!"

  def self.speak
    puts "self=#{self}"
    puts "singleton_class = #{singleton_class}"
    singleton_class.instance_variable_get :@message
  end

  class << self
    @message = "Howdy!"
  end
end

puts Speaker.speak
  # self=Speaker
  # singleton_class = #<Class:Speaker>
  # Howdy!

In the last expression, because self equals Speaker and self is the implied receiver when there is no explicit receiver, "singleton_class is equivalent to Speaker.singleton_class.

Upvotes: 3

Michael Kohl
Michael Kohl

Reputation: 66867

First off, your message @message is not an instance variable, or rather not the type of instance variable you may be thinking about: it's a class-level instance var, so an instance variable of Speaker itself, which as an object is an instance of class Class.

Here's a version of the code that does what you're trying to do with a local variable and a closure:

class Speaker
  @message = "Hello!"

  class << self
    message = "Howdy!"
    define_method(:speak) { message }
  end
end

Speaker.speak
#=> "Howdy!"

And here's some code that illustrates the difference between the class-level instance variable and a "normal" instance variable:

class Speaker
  @message = 'Howdy!'   # class-level instance variable

  def initialize
    @message = 'Hello!' # instance variable of Speaker's instances
  end

  def speak
    @message
  end

  class << self
    def speak
      @message
    end
  end
end

Speaker.speak
#=> "Howdy!"
Speaker.new.speak
#=> "Hello!"

Upvotes: 4

S. Parikh
S. Parikh

Reputation: 1

The reason this code returns 'Hello' is that it is attempting to change an instance variable in a class << self block.

Class methods are for anything that does not deal with an individual instance of that class - instance variables are tied to individual instances of a class, and we can't change instance variables at a class level.

Instead of using an instance variable in the speak method, we should use a class variable (denoted by @@).

As an example, the following code will return 'Howdy!' -

class Speaker
    @@message = "Hello!"

    class << self
        @@message = "Howdy!"

        def speak
           @@message
        end
    end
end

puts Speaker.speak

Upvotes: -1

Related Questions