Section 2. Objects, Classes, and Modules

Like many modern programming languages, Ruby is object oriented. However, Ruby could be considered to be a purer object-oriented language than many. To see what I mean, let’s look closer at that expression 30.minutes.ago from the previous example. You might have naturally assumed that it is a bit of syntactic sugar, something that the Ruby interpreter intercepts and translates into something real like Time.now - 30*60 upon execution. Indeed, that is how it would have to work in languages such as C++, Java, and PHP, where integers, floats, and sometimes strings are treated as primitives below the OOP framework. In Ruby, however, everything is an object—even integers—as the following example demonstrates. This and other examples are executed within irb (the Interactive RuBy interpreter), a program included within the Ruby distribution that you can use to run these examples:

Image

As you can see, we can call methods on the integer directly and traverse up its class hierarchy to the Object class, which is the root class for all other classes in Ruby. This process should be familiar to Java programmers, although we shall soon see this does not have the same importance as it does in Java. Looking further, we can see that because integers are objects in Ruby, they can have methods associated with them:

Image

This example lists all the public methods for integers (with methods inherited from the Object superclass removed for the sake of clarity). This ability to query objects and see their classes and methods is called reflection, and it is part of Ruby’s powerful metaprogramming capabilities that we examine later. But let’s return to the fact that even lowly integers are objects. That means you no longer have to worry about wrapper classes and other awkward interfaces like in other languages and that every variable in the system contains general methods inherited from object. In addition, it means we do not need to rely on syntactic sugar. When you call 30.minutes.ago, it is executed using methods defined in the Integer class against that object "30", and it really just runs like any other method does, with no syntactic sugar involved.

Except that it doesn’t. Try running this code in irb now. You are instead greeted with the following output from the interpreter:

Image

Something is wrong here. Indeed, look at the list of methods for Fixnum. No minutes method is listed. And yet, this code works in Rails; the method is clearly there. Let’s dig a little deeper and look at what Rails’s console reports as methods for the Fixnum class. We can do this by running our method-dumping command within Rails’s console debugger (found at script/console within any Rails project):

Image

Wow! Where did all those extra methods come from? And how did they get within Fixnum? To understand is to see how seriously Ruby takes the principle of flexibility; it allows users to override and extend the functionality of its base classes if needed. And that is what Rails does here. Listing 1 is a simplified excerpt of code within Rails that extends the Numeric superclass of Fixnum.

Listing 1 Example of How Rails Extends Ruby’s Core Fixnum Class

Image

Allowing users to extend base classes is a remarkable example of Ruby’s flexibility. Consider how Rails would have to add helper methods under a more traditional OOP language. In languages such as Java, base classes are precompiled and extended only through inheritance. You can only define a subclass and add methods to it. Under this approach, the Rails source would have to define classes such as RailsNumeric, RailsString, RailsTime, RailsArray, and so on that inherit from Ruby’s core classes. Rails code would be built using such methods, and we would have to be careful to convert to these new classes when we wanted to use the additional methods. For instance, our simple time-comparison example would turn into this:

Image

Although this might be acceptable if you are a Java programmer and have no other choice, writing Ruby is supposed to be creative and immediate; it is just not supposed to look like that. And this is where Ruby’s principle of flexibility really shines. There is no particular reason why even the core classes of the library need to remain absolute and inviolable. This is what Matsumoto meant by the “freedom to choose”: Any user is allowed to tweak and extend Ruby’s core libraries for his or her own ends. It is democracy in action. Not only does this make for happier hackers and simpler code, it improves the language by letting end users discover new ways of doing things not anticipated by Ruby’s creators; indeed, some of Rails’s extensions to the Enumerable module will be incorporated into a future Ruby release for everybody. And it makes the other two principles attainable; by allowing flexibility, Ruby enables code to be concise and consistent.

Before moving on, we should cover one other mechanism for extending functionality in Ruby: the mixin. Like Java, Ruby supports only single inheritance for classes. However, Ruby also allows users to define modules that contain methods, constants, classes, and even other modules. This is normally used for namespacing (it is the technique that eliminates confusion between the classes ActiveRecord::Base and ActionController::Base), but it is also the basis for mixing in code to classes. Mixins enable you to easily add functionality to multiple places without repeating yourself, which reduces cut-and-paste coding. Listing 2 illustrates how this technique conceptually works within Ruby (the actual source code within Ruby looks more complicated). As discussed later, both the Array and Hash classes in Ruby support a wide range of similar functions for iterating over their elements. Instead of writing the same code in multiple places, Ruby defines the functions only once and includes the methods into Hash and Array via mixins. (The include directive in the listing is doing the mixin.) In this particular case, the module Enumerable adds 28 additional functions that work by calling the each method within the class they are included within. This demonstrates an interesting caveat when using mixins: Be sure to check whether methods in the mixin are expecting to call certain attributes or methods within your classes. Each module may have varied requirements, and some may be completely self-contained, but it is up the user to figure that out. As discussed in the next section, Ruby does not precompile or prebind any methods or attributes called within methods, so it has no way of automatically warning you if they are not found. Fortunately, the documentation usually indicates this sort of dependency.

Listing 2 An Example of Module Mixins in Ruby

Image

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset