Awhile ago, a student wrote me with a question about an instance variable used right after the class declaration, i.e., inside of a method. Here’s how I summed up this puzzler in code to the group:
class Example # The following line is the issue. It looks like a instance variable has been set to
# a value, but it isn't visible later on.
@var1 = [ 1, 2 ]
p "Just inside 'class Example' but outside method, @var1 = #{@var1.inspect}"
# p "Just inside 'class Example' but outside method, self = #{self}"
def initialize
p "Inside ' Example.initialize', @var1 = #{@var1.inspect}"
# p "Inside 'Example.initialize', self = #{self}"
@var2 = [ 3, 4 ]
end
def dump
p "Inside ' Example.dump', @var1 = #{@var1.inspect}"
p "Inside 'Example.dump', @var2 = #{@var2.inspect}"
# p "Inside 'Example.dump', self = #{self}"
end
end
Example.new.dump
class Example
p "Re-opened class Example, outside method, @var1 = #{@var1.inspect}"
# p "Re-opened class Example, outside method, self = #{self}"
end
If you remove the comments for the statements showing the value of self in various places, you will find that the value of self just inside of the declaration of the class is the name of the class, i.e., Example. Therefore, it would seem, @var1 is an instance of Example. What does this mean?
Ruby is more fully object-oriented that most other languages: Its model is much closer to, say, SmallTalk than it is to Java.
In Ruby, when we declare a class, we are creating a constant (note that capital letter for “Example”) that is a reference to a single object that is an instance of the class… Class. And how can we begrudge this instance its own instance variables? The syntax is a bit confusing, because we are used to variables prefixed by @ to be instance variables of instances of that very class we’re declaring (i.e., instances of Example). But those instance variables are referenced inside methods.
Hal Fulton says: “Variables starting with a single @, defined inside a class, are generally instance variables. However, if they are defined outside any method, they are really class instance variables.” He goes on to say: “Class instance variables cannot be referenced from within instance methods and, in general, are not very useful” (The Ruby Way, pp. 56-57).
Now, let us return to something we talked about briefly in lecture. Remember “extend”? That was weird, wasn’t it? We were able to bring in stuff from a module to a specific instance. Well where the heck does that stuff live? Well, in the module. But it suggests that maybe we could add methods to a specific object, not just to a class. Ruby facilitates this.
Again, Fulton (pp. 61-62):
A singleton class in Ruby is the classlike entity where methods are stored that are per-object rather than per-class. It is arguably not a “true class” because it cannot be instantiated. The following is an example of opening up the singleton class for a string object [listen to what Fulton is saying: Opening up a specific object, not the class] and adding a method:
str = "hello"
class << str # Alternatively:
def hyphenated # def str.hyphenated
self.split('').join('-')
end
end
p str.hyphenated
…Because the method hyphenate exists in no other object or class, it is a singleton method on that object…
But remember that in Ruby, a class is itself an object. Thus we can add a method to the singleton class of a class, and that method will be unique to that object, which happens to be a class. Here is an example:
class MyClass
class << self # Alternatively: def self.hello
def hello # or: def MyClass.hello
puts "Hello from #{self}!"
end
end
end
MyClass.hello
But you will notice that this is simply what we call a class method in Ruby. In other words, a class method is a singleton method on a class. We could also say it’s a singleton method on an object that happens to be a class.
Whew. Let us return now to Fulton’s claim that class instance variables are not very useful. I think they are sometimes. Suppose you wanted to track object creation. You might write something like this:
class Example1
@@number_of_instances = 0
def initialize
@@number_of_instances += 1
print "There have been #{@@number_of_instances} instances of Example1 created.n"
end
def num_instances
@@number_of_instances
end
end
e1a = Example1.new
e1b = Example1.new
Clear enough. But is it right that the instance can see the number of instances of the class? Shouldn’t we be able to keep that number private to the class? Yes we can. We can keep the number of instances in a class instance variable:
class Example2
@number_of_instances = 0
class << self
def bump
@number_of_instances += 1
print "There have been #{@number_of_instances} instances of Example2 created.n"
end
end
def initialize
Example2.bump
end
end
e2a = Example2.new
e2b = Example2.new
Notice here I do have to call the class method bump to get my counter incremented. But at least now the total number of instances is private to the class instance. Is there a way to get rid of the need to call Example2.bump? Yes there is, but you would have to override Class.new. I will leave that as an exercise for the reader. 
Recent Comments