Reaching the end of the Ruby Class Jungle

Posted by JuanGongora on August 19, 2016

I know it’s hard to believe… But there’s still a lot more ground to cover on our journey into Ruby’s classes. They are after all very beautiful and elaborate in their nature: such things should take time to appreciate and understand.

That means pick up your walking stick, pat the dust off your Indiana Jones hat, and let’s depart where we left off…

In my last blog Diving deeper into Ruby’s Class pool, I explained how a class’s innards are filled with instance variables, that keep track of the state of instantiated objects: as well as instance methods, which contain the variables that are associated with the instance of a class.

Class Variables

Okay, so I want to introduce another fellow to the group… It’s name? Class variables! These variables begin with two @@ symbols (not to be confused with instance variables which start with only one @) and are always set at the top tier of your class body. One major difference between a class variable and an instance variable is the inheritance of their values: one allows them to be automatically shared to other classes under the same family hierarchy, while the other doesn’t. What does that mean? Well let’s write an example below:

class Alien
  @@count = 13
  
  def get_share
    @share = 9
  end
  
  def get_count
    @@count
  end

end

This class called Alien contains the class variable @@count which is set to the default value of 13. Right under it is an instance method with the instance variable @share at the default of 9.

The get_count accessor method is used to retrieve the value of the class variable @@count, just like get_share does for the instance variable @share.

class Robot < Alien

  def get_share
    @share = 10
  end

  @@count = 14

end

Here we create a child class (Robot) of the parent class Alien. This is done by simply adding a < right after the class name, followed by the name of the parent class: class Robot < Alien. With Robot becoming a member of the Alien class, it automatically inherits whatever features were inside of it. But we want to change those values to see the difference in inheritance from an instance variable to a class variable. Hence @share = 10 and @@count = 14.

Let’s test it out then by making new instances of the Alien and Robot classes, and using their built methods for our terminal to interpret their changing values:

a = Alien.new
b = Robot.new
c = Alien.new

p a.get_share
p b.get_share
p c.get_share

p a.get_count
p b.get_count
p c.get_count

Okay terminal time…

9
10
9
14
14
14

We can see now that the value of @@count was altered by Robot: and then it shared that same value throughout the family classes. Although @share seems to have retained its original instance value from Alien, even though we changed that value in the Robot class. That’s because the instance variable from Alien does not recognize the altered state of @share from Robot, it only recognizes its own.

To dive deeper into why that’s so, we have to remember that a class isn’t the same object as any of its instances, and no two instances under the same class are going to be alike. Therefore it’s impossible, by definition, for a class to share instance variables with its instances.

But because of a class variables’ cross communication with its class family, you are capable of outright altering its overall value at any point in time: even if the new value was set in a child class. This means that whatever new value you choose will also be the very same for all related sub classes, including even the parent class:

class Alien
  @@count = 13

  def get_count
    @@count
  end

end

class Robot < Alien
  @@count = 8
end

class Human < Alien
  @@count = 3
end

a = Alien.new
p a.get_count

b = Robot.new
p b.get_count

c = Human.new
p c.get_count

Terminal says…

3
3
3

As you can see from above, the value of @@count was converted to 3: which was set from the last @@count alteration in class Human onto all the other family classes.

Class Methods

Something to note about class variables is that they do have a specific gap in regards to visibility. That is, when it comes to a class and its instances, a class variable is private to that class alone. So if you want to make them accessible to the outside world, you can continue using instance methods like we already have been doing: or you can use what’s called a class method:

class Alien
  @@count = 13

  def self.get_count
    @@count
  end

  def get_count_instance
    @@count
  end
	
end

Above, self.get_count is a class method – that is, it belongs to the Alien class rather than to an instance of the Alien class. Let’s continue on the above sample to further explain this:

p Alien.get_count
p Alien.get_count_instance

What do you think the terminal will print out?

13
(eval):14: undefined method `get_count_instance' for Alien:Class (NoMethodError)

Hmm it looks like get_count_instance wasn’t able to print out the value of @@count like self.get_count did. In order to do so we have to do this:

a = Alien.new
p a.get_count_instance

Which is what we had been doing earlier in order to obtain the class variable @@count.

That explains how we can invoke the class method from Alien itself rather than having to invoke it from a new Alien object.

If we wanted to have an* instance method* that was called like a class method (i.e. not from an instantiated object but from the class itself) we’d have to tweak it like so:

class Alien
  @@count = 13
  @share = 9

  def self.get_count
    @@count
  end

  class << self
    def get_share
      @share
    end
  end

end

This class called Alien contains the class variable @@count which is set to the default value of 13. Right under it is an instance variable @share with the default of 9.

The class method self.get_count is used to retrieve the value of the class variable @@count.

Here’s the change:

class << self opens up self’s singleton class, so that methods can be redefined for the current self object, this is used in order to define and call the method get_share with the class Alien itself.

p Alien.get_count
p Alien.get_share

Okay terminal time…

13
9

Awesome sauce! It gave us the value of the instance in the same calling format as our class method. But there’s a catch:

class Robot < Alien
end

p Robot.get_count
p Robot.get_share

Terminal says:

13
nil

This result brings us back to the original nature of an instance variable(nonlinear inheritance). So at first get_share worked with Alien, and was equivalent to calling like a class method. But the way it does it is a little more subtle.

The secret is that self, in this context (class << self) actually refers to the object Alien(remember that classes are also objects). But this Alien that it refers to is a unique, anonymous subclass of the original class Alien. This subclass is the singleton class. So here get_share creates a new method called into Alien’s singleton class, which becomes accessible by the normal method call Alien.get_share. But as soon as you make a child class of Alien, in this case Robot, it will not be able to recognize the inheritance of that get_share method: simply because it is a method of Alien’s singleton class, not its primary class Alien.

Phew! I know it can be confusing at first…

(If you need a little more run through on this subject, you can check this great article out for more examples…)

Now to sum things up with all this class method business… a class method is defined directly on the class object: in that singleton-method style. A singleton method defined on a class object is commonly referred to as a class method of the class that it was defined in. The idea of a class method is that you send a message to the object that’s the class rather than to one of the class’s instances. So… the message of a class method goes to the class, not to a particular instance of that class.

Attribute Accessors

I want to segue our way back to a topic that was covered in my last blog: which is accessor methods. These methods help us obtain the variables of a class object, as well as assign new values to pre-existing variables. Below is the code snippet that was used in Diving deeper into Ruby’s Class pool:

class Sheriff
  def initialize(dog)
    @dog = dog
  end

  def get_dog
    @dog
  end
  
  def set_dog=(dog)
    @dog = dog
  end
	
end

Above we’ve got an object initializer for the instance variable @dog as well as a getter(get_dog) and setter(set_dog) method for the variable @dog.

There’s something we can do to trim these though. Ruby provides a unique way to let us write getter and setter methods without having to stick a 'set' or 'get' in front of a word like 'dog'. Let’s revise the code below:

class Sheriff
  attr_reader :dog
  attr_writer :dog
	
  def initialize(dog)
    @dog = dog
  end
	
end

Here we introduce the famous attribute methods. our get_dog method was converted to attr_reader(attribute reader) and our set_dog=(dog) method changed to attr_writer(attribute writer). With those single lines, we shortened our methods and kept their operations the same!

You probably also noticed how the attribute method calls without an explicit receiver: so there’s no left-hand object and no dot before attr written here. Thats because when there isn’t an explicit receiver, the messages instead go to self, which is the default object. So the object receiving the attr_reader message is the actual class object Sheriff.

The elements that start with colons(:dog) are symbols. Symbols are the names of strings, instance variables, methods, classes, etc. A symbol is basically used to just represent some kind of state. So if there is an instance variable called @dog, then there automatically will be a symbol called :dog.

That’s what we have going on for both our attribute methods, they are linking themselves to work with the initialized instance variable @dog.

Let’s watch them at work:

m = Sheriff.new("monk")
p m.dog
m.dog=("fish")
p m.dog

Terminal:

"monk"
"fish"

Cool beans, it works just like our old accessor methods. But there’s one more thing we can do to shorten our code even more. Ruby has a single method, attr_accessor(attribute accessor), which is the combination of attr_reader plus attr_writer: all wrapped up into one! Let’s use this method to shorten our code below:

class Sheriff
  attr_accessor :dog
	
  def initialize(dog)
    @dog = dog
  end
	
end

And it works just the same! You can define attribute accessors in order to get directly at the variables within the instance.

The super of Superclasses

As we push through the canopy of the Ruby Class jungle, we’ve discovered a couple of different ways class inheritance shares its family methods and/or variables. But as I’m sure you’ve noticed, some of the operations we’ve used for sharing class elements have had restrictions when making value or method alterations .

Ruby graciously offers a unique keyword called super, which relates itself to the superclass(or parent class) in the inheritance chain.

This beauty allows us to trigger methods from parent classes right onto child classes for functionality sharing. As well as being capable of making child classes further expand their inherited methods. Let’s work on this below to sample what I mean:

class Sheriff
  attr_accessor :dog

  def initialize(dog)
    @dog = dog
  end

  def bounty
    puts "Sheriff #{@dog} is on a hunt for your bounty!"
  end

end

This is where we are starting. It should look familiar to you from what we have already been working on. The only new addition is the method bounty.

class Deputy < Sheriff
  attr_accessor :cat
  def initialize(dog, cat)
  super(dog)
    @cat = cat
  end
end

Alright, now we have a child of Sheriff to fiddle around with. First thing’s first, we have included the super keyword under the initializer of Deputy’s instance method. This method has two elements: dog and cat. The argument dog is a link to the @dog instance variable, which is being called by super(dog). This calling from super, will send the argument dog for initialization in the parent(super) class Sheriff.

Thanks to that ability, we are able to connect the initialization method in Sheriff right into Deputy without impurity of the parent’s method state. We have then added our own extra initialized instance variable into the mix: @cat. This shows us how we were able to create additional features on top of Sheriff’s @dog initializer, without having to break any of the parent’s original method code.

Let’s add another super that works with Sheriff’s bounty class to see how it works on a non initialized method:

class Deputy < Sheriff
  attr_accessor :cat
  def initialize(dog, cat)
  super(dog)
    @cat = cat
  end

  def bounty
    puts "I'm starting from the bounty method in class Deputy."
    super
    puts "I'm back from the super call to class Sheriff's bounty method."
    puts "Deputy #{cat} is not amused by our example..."
  end

end

When you call super with no arguments, Ruby sends a message to the current object’s parent(Sheriff), asking it to invoke a method of the same name as the current method(bounty). It then passes to super whatever parameters were executed in the parent, right back to the current method. Let’s test it out:

a = Deputy.new("Hal", "Squid")
p a.bounty

Terminal:

I'm starting from the bounty method in class Deputy.
Sheriff Hal is on a hunt for your bounty!
I'm back from the super call to class Sheriff's bounty method.
Deputy Squid is not amused by our example...
nil

We can see from above that the method begins implementing its code from its own class (Deputy) until it reaches super. Once it got there, super instantly begins jumping up through the class hierarchy: that is until it finds another method called bounty. bounty is then found in the class Sheriff, so the method executes the code inside the parent class, and then when completed, jumps back to the method bounty from class Deputy to finish executing the rest of the remaining code.

Looking for the ancestor in charge of the Ziggurat

We’ve gone a long way in search of our inheritance. Finding various forms that clue us in on the inner mysteries of Ruby’s Class hierarchy. We’re now at the heart of the jungle, where we see a massive ziggurat spike itself up and up to the highest clouds where even the birds can’t reach.

As we walk towards the structure we start to make out what looks like a sealed tomb entrance. There’s an inscrition on the wall… It says:

“Ye who wish to enter must re-cap what was taught: otherwise, the passage to the halls of Module will forever remain sealed.”

I guess we better listen to it…

  • Variables which begin with two @@ characters are called class variables. They are private to a class and its instances, but when accessed through accessor methods you have the convenience of not having to write self.class from an instance object. You also get automatic sharing throughout the class hierarchy.
  • Class methods differ from instance methods by their method definition: which places the class name and a period in front of the method’s name. Class methods are for things that don’t operate on the individual instance of an object, or for cases where you don’t have the instance available to you. It’s primary use is for updating all users of the class to a specific condition.
  • From the sample class Alien written earlier above, remember that at the outer level of a class definition, and inside the class methods, self is the class object Alien whereas in the instance methods, self is the instance of Alien that’s calling the method.
  • With attribute accessors, Ruby provides us with an easy way to access an object’s variables. Anattribute accessor is the property of an object whose value can be read and/or written through the object itself.
  • Ruby’s an interpreted language, so it keeps its Symbol Table handy at all times. You can find out what’s on it at any given moment by calling Symbol.all_symbols.

Whooa!!

A massive grinding sound begins to echo all around us, scaring all the wild birds to swarm out of the tree thickets and onto the skies. As the rumble begins to slow down we see clouds of dust start to flow from the cracks of the tomb entrance…

Bang!! The granite wall blocking the entrance falls straight forward! Good thing we weren’t standing that close to it. We can’t see anything inside though, it’s too dark…

Let’s camp out at the entrance for the night, before starting into the ziggurat. We need to rest our feet before going into the first hall of this ancient structure… Modules I believe it was called?