9.3 Behaviour

Object Behaviour

Ruby uses message passing to invoke specific behaviours. When a Ruby object receives a message, it checks to see if a method of the same name exists. If there is one, that method is invoked with the appropriate parameters. If the method doesn't exist, it calls the method_missing method and passes the message to it. We cover method_missing in the RubyMonk Metaprogramming: Method missing lesson.

How does Ruby know whether a method 'exists' in an object or not? Take a look at these method invocations:

Example Code:

Output Window

The methods object_id, class were not defined by the class Foo. It is however present in every object created from Foo. In fact, these methods are present in every object in the Ruby object space. How is this possible? Where do these methods magically appear from? We will answer these questions and understand Ruby's object model better in the course of this chapter.

The first thing to note is that every object in Ruby by default inherits from the class Object. The two snippets of code below are identical:

class Foo
end

class Foo < Object
end

class Foo < Object means that Foo inherits behaviour from Object. In practice, that means that every instance instantiated by Foo will include all the instance methods defined by Object. Since by default every Ruby object inherits from Object, all instance methods defined by Object are available to every object in Ruby. The methods object_id and class are thus available everywhere.

Here is an interesting fact: even the class definition object Foo is an object and hence begets the behaviour exposed by the Object class. We can use the is_a? method to check whether this is true.

Example Code:

Output Window

The is_a? method tells you whether the object contains behaviour of the given class. As you can see, both the class definition object Foo and an instance created from Foo share Object's behaviour.

Superclass

The superclass method lets us inspect the inheritance hierarchy of a class definition. We've already seen inheritance in action in the chapter Classes: Inheritance. Now let us see what happens under the hood when you use inheritance in Ruby.

Let us start with an example of the superclass method.

Example Code:

Output Window

Here, class definition Foo inherits from class definition Bar. What does that mean? As you can see, Foo.new has the shout method which is defined in Bar, the parent class of Foo.

"The class definition X inherits behaviour from another class definition Y" is another way of saying that every instance of X will also include methods defined by Y. This also means that Y is X's superclass.

A superclass doesn't have much to do with the class method we looked at earlier. While the class method primarliy deals with the identity of instances of class, the superclass and its relatives deal with their behaviour.

Example Code:

Output Window

As we can see, Object is Foo's superclass. Thus all methods defined in Object is available to Foo as well - this is how object_id, respond_to and the rest of the standard object methods are available to all objects in Ruby.

We'd talked about the inheritance tree of a class definition before. Let us use superclass to inspect the inheritance tree of a simple class:

Example Code:

Output Window

What we have printed here are the ancestor classes of Foo. The inheritance hierarchy shows that Foo's parent is Object and Object's parent is BasicObject. BasicObject is the terminal object and does not have any more parents.

Now can you implement a method superclasses inside Object that returns this class hierarchy information? The method has to return an array that lists the superclasses of the current object.

Hint

This problem can be solved using recursion or iteration.

Output Window

You would have noticed that we re-opened the standard Ruby class Object and added a method to it. It is possible to do this with any Ruby class. Using this approach you can add your own behaviours to even standard objects. This is called monkey-patching. Monkey-patching however is not generally encouraged and should be used only as a last-resort.

So far we've seen how the inheritance hierarchy of a class affects the behaviour of the class. In Ruby, the ability to modify behaviour of object is not restricted to inheritance alone. Mixing in modules is an oft-used way to reuse behaviour across classes. In the next section, we'll look into the ancestors which is similar to the method we just implemented, but also includes the list of the modules mixed into the class.

Ancestors

Lets look at a simple example:

Example Code:

Output Window

For the time being ignore all the namespaced modules in the output. Then we are left with Foo, Bar, Object, Kernel and BasicObject. The ancestors method simply returns an array that starts with the class or module definition on which it was invoked. It then contains the superclasses and mixed in modules of the object.

Kernel is a module that is mixed into the Object class. Kernel contains methods like puts, p, rand, loop etc.

The rest of the namespaced modules are the various other modules that are mixed into Object when your code is run on RubyMonk.

BasicObject

The Object class is the ancestor of almost all objects in Ruby. Object however inherits from BasicObject. BasicObject has almost no methods of its own. Object on the other hand defines its own methods and includes the Kernel mixin as well.

BasicObject comes last in the inheritance hierarchy of any Ruby object:

Example Code:

Output Window

BasicObject is useful, albeit rarely, when you need to build a new minimal object that does not include the methods defined by Object and Kernel.

Congratulations, guest!


% of the book completed

or

This lesson is Copyright © 2011-2014 by Jasim A Basheer