Reputation: 165
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
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
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
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