Книга: Metaprogramming Ruby 2
Назад: Open Classes
Дальше: Quiz: Missing Lines

Inside the Object Model

Where you learn surprising facts about objects, classes, and constants.

Your recent experience with Open Classes () hints that there is more to Ruby classes than meets the eye. Much more, actually. Some of the truths about Ruby classes and the object model in general might even come as a bit of a shock when you first uncover them.

There is a lot to learn about the object model, but don’t let all this theory put you off. If you understand the truth about classes and objects, you’ll be well on your way to being a master of metaprogramming. Let’s start with the basics: objects.

What’s in an Object

Imagine running this code:

 
class​ MyClass
 
def​ my_method
 
@v = 1
 
end
 
end
 
 
obj = MyClass.new
 
obj.class ​# => MyClass

Look at the obj object. If you could open the Ruby interpreter and look into obj, what would you see?

Instance Variables

Most importantly, objects contain instance variables. Even though you’re not really supposed to peek at them, you can do that anyway by calling Object#instance_variables. The object from the previous example has just a single instance variable:

 
obj.my_method
 
obj.instance_variables ​# => [:@v]

Unlike in Java or other static languages, in Ruby there is no connection between an object’s class and its instance variables. Instance variables just spring into existence when you assign them a value, so you can have objects of the same class that carry different instance variables. For example, if you hadn’t called obj.my_method, then obj would have no instance variable at all. You can think of the names and values of instance variables as keys and values in a hash. Both the keys and the values can be different for each object.

That’s all there is to know about instance variables. Let’s move on to methods.

Methods

Besides having instance variables, objects also have methods. You can get a list of an object’s methods by calling Object#methods. Most objects (including obj in the previous example) inherit a number of methods from Object, so this list of methods is usually quite long. You can use Array#grep to check that my_method is in obj’s list:

 
obj.methods.grep(/my/) ​# => [:my_method]

If you could pry open the Ruby interpreter and look into obj, you’d notice that this object doesn’t really carry a list of methods. An object contains its instance variables and a reference to a class (because every object belongs to a class, or—in OO speak—is an instance of a class)…but no methods. Where are the methods?

Your pair-programming buddy Bill walks over to the nearest whiteboard and starts scribbling all over it. “Think about it for a minute,” he says, drawing the following diagram. “Objects that share the same class also share the same methods, so the methods must be stored in the class, not the object.”

images/chp1_vars_and_methods.jpg

Figure 1. Instance variables live in objects; methods live in classes.

Before going on, you should be aware of one important distinction about methods. You can rightly say that “obj has a method called my_method,” meaning that you’re able to call obj.my_method(). By contrast, you shouldn’t say that “MyClass has a method named my_method.” That would be confusing, because it would imply that you’re able to call MyClass.my_method() as if it were a class method.

To remove the ambiguity, you should say that my_method is an instance method (not just “a method”) of MyClass, meaning that it’s defined in MyClass, and you actually need an object (or instance) of MyClass to call it. It’s the same method, but when you talk about the class, you call it an instance method, and when you talk about the object, you simply call it a method. Remember this distinction, and you won’t get confused when writing introspective code like this:

 
String.instance_methods == ​"abc"​.methods ​# => true
 
String.methods == ​"abc"​.methods ​# => false

Let’s wrap it all up: an object’s instance variables live in the object itself, and an object’s methods live in the object’s class. That’s why objects of the same class share methods but don’t share instance variables.

That’s all you really have to know about objects, instance variables, and methods. But since we brought classes into the picture, we can also take a closer look at them.

The Truth About Classes

Here is possibly the most important thing you’ll ever learn about the Ruby object model: classes themselves are nothing but objects.

Because a class is an object, everything that applies to objects also applies to classes. Classes, like any object, have their own class, called—you guessed it—Class:

 
"hello"​.class ​# => String
 
String.class ​# => Class

You might be familiar with Class from other object-oriented languages. In languages such as Java, however, an instance of Class is just a read-only description of the class. By contrast, a Class in Ruby is quite literally the class itself, and you can manipulate it like you would manipulate any other object. For example, in Chapter 5, , you’ll see that you can call Class.new to create new classes while your program is running. This flexibility is typical of Ruby’s metaprogramming: while other languages allow you to read class-related information, Ruby allows you to write that information at runtime.

Like any object, classes also have methods. Remember what you learned in ? The methods of an object are also the instance methods of its class. In turn, this means that the methods of a class are the instance methods of Class:

 
# The "false" argument here means: ignore inherited methods
 
Class.instance_methods(false) ​# => [:allocate, :new, :superclass]

You already know about new because you use it all the time to create objects. The allocate method plays a supporting role to new. Chances are, you’ll never need to care about it.

On the other hand, you’ll use the superclass method a lot. This method is related to a concept that you’re probably familiar with: inheritance. A Ruby class inherits from its superclass. Have a look at the following code:

 
Array.superclass ​# => Object
 
Object.superclass ​# => BasicObject
 
BasicObject.superclass ​# => nil

The Array class inherits from Object, which is the same as saying “an array is an object.” Object contains methods that are generally useful for any object—such as to_s, which converts an object to a string. In turn, Object inherits from BasicObject, the root of the Ruby class hierarchy, which contains only a few essential methods. (You will learn more about BasicObject later in the book.)

While talking about superclasses, we can ask ourselves one more question: what is the superclass of Class?

Modules

Take a deep breath and check out the superclass of the Class class itself:

 
Class.superclass ​# => Module

The superclass of Class is Module—which is to say, every class is also a module. To be precise, a class is a module with three additional instance methods (new, allocate, and superclass) that allow you to create objects or arrange classes into hierarchies.

Indeed, classes and modules are so closely related that Ruby could easily get away with a single “thing” that plays both roles. The main reason for having a distinction between modules and classes is clarity: by carefully picking either a class or a module, you can make your code more explicit. Usually, you pick a module when you mean it to be included somewhere, and you pick a class when you mean it to be instantiated or inherited. So, although you can use classes and modules interchangeably in many situations, you’ll probably want to make your intentions clear by using them for different purposes.

Putting It All Together

Bill concludes his lecture with a piece of code and a whiteboard diagram:

 
class​ MyClass; ​end
 
obj1 = MyClass.new
 
obj2 = MyClass.new
images/chp1_classes_1.jpg

Figure 2. Classes are just objects.

“See?” Bill asks, pointing at the previous diagram. “Classes and regular objects live together happily.”

There’s one more interesting detail in the “Classes are objects” theme: like you do with any other object, you hold onto a class with a reference. A variable can reference a class just like any other object:

 
my_class = MyClass

MyClass and my_class are both references to the same instance of Class—the only difference being that my_class is a variable, while MyClass is a constant. To put this differently, just as classes are nothing but objects, class names are nothing but constants. So let’s look more closely at constants.

Constants

Any reference that begins with an uppercase letter, including the names of classes and modules, is a constant. You might be surprised to learn that a Ruby constant is actually very similar to a variable—to the extent that you can change the value of a constant, although you will get a warning from the interpreter. (If you’re in a destructive mood, you can even break Ruby beyond repair by changing the value of the String class name.)

If you can change the value of a constant, how is a constant different from a variable? The one important difference has to do with their scope. The scope of constants follows its own special rules, as you can see in the example that follows.

 
module​ MyModule
 
MyConstant = ​'Outer constant'
 
 
class​ MyClass
 
MyConstant = ​'Inner constant'
 
end
 
end

Bill pulls a napkin from his shirt pocket and sketches out the constants in this code. You can see the result in the following figure.

images/chp1_constants.jpg

All the constants in a program are arranged in a tree similar to a file system, where modules (and classes) are directories and regular constants are files. Like in a file system, you can have multiple files with the same name, as long as they live in different directories. You can even refer to a constant by its path, as you’d do with a file. Let’s see how.

The Paths of Constants

You just learned that constants are nested like directories and files. Also like directories and files, constants are uniquely identified by their paths. Constants’ paths use a double colon as a separator (this is akin to the scope operator in C++):

 
module​ M
 
class​ C
 
X = ​'a constant'
 
end
 
C::X ​# => "a constant"
 
end
 
 
M::C::X ​# => "a constant"

If you’re sitting deep inside the tree of constants, you can provide the absolute path to an outer constant by using a leading double colon as root:

 
Y = ​'a root-level constant'
 
 
module​ M
 
Y = ​'a constant in M'
 
Y ​# => "a constant in M"
 
::Y ​# => "a root-level constant"
 
end

The Module class also provides an instance method and a class method that, confusingly, are both called constants. Module#constants returns all constants in the current scope, like your file system’s ls command (or dir command, if you’re running Windows). Module.constants returns all the top-level constants in the current program, including class names:

 
M.constants ​# => [:C, :Y]
 
Module.constants.include? :Object ​# => true
 
Module.constants.include? :Module ​# => true

Finally, if you need the current path, check out Module.nesting:

 
module​ M
 
class​ C
 
module​ M2
 
Module.nesting ​# => [M::C::M2, M::C, M]
 
end
 
end
 
end

The similarities between Ruby constants and files go even further: you can use modules to organize your constants, the same way that you use directories to organize your files. Let’s look at an example.

The Rake Example

The earliest versions of Rake, the popular Ruby build system, defined classes with obvious names, such as Task and FileTask. These names had a good chance of clashing with other class names from different libraries. To prevent clashes, Rake switched to defining those classes inside a Rake module:

 
module​ Rake
 
class​ Task
 
# ...

Now the full name of the Task class is Rake::Task, which is unlikely to clash with someone else’s name. A module such as Rake, which only exists to be a container of constants, is called a Spell: .

This switch to Namespaces had a problem: if someone had an old Rake build file lying around—one that still referenced the earlier, non-Namespaced class names—that file wouldn’t work with an upgraded version of Rake. For this reason, Rake maintained compatibility with older build files for a while. It did so by providing a command-line option named classic-namespace that loaded an additional source file. This source file assigned the new, safer constant names to the old, unsafe ones:

 
Task = Rake::Task
 
FileTask = Rake::FileTask
 
FileCreationTask = Rake::FileCreationTask
 
# ...

When this file was loaded, both Task and Rake::Task ended up referencing the same instance of Class, so a build file could use either constant to refer to the class. A few versions afterwards, Rake assumed that all users had migrated their build file, and it removed the option.

Enough digression on constants. Let’s go back to objects and classes, and wrap up what you’ve just learned.

Objects and Classes Wrap-Up

What’s an object? It’s a bunch of instance variables, plus a link to a class. The object’s methods don’t live in the object—they live in the object’s class, where they’re called the instance methods of the class.

What’s a class? It’s an object (an instance of Class), plus a list of instance methods and a link to a superclass. Class is a subclass of Module, so a class is also a module. If this is confusing, look back at Figure 2, .

These are instance methods of the Class class. Like any object, a class has its own methods, such as new. Also like any object, classes must be accessed through references. You already have a constant reference to each class: the class’s name.

“That’s pretty much all there is to know about objects and classes,” Bill says. “If you can understand this, you’re well on your way to understanding metaprogramming. Now, let’s turn back to the code.”

Using Namespaces

It takes only a short while for you to get a chance to apply your newfound knowledge about classes. Sifting through the Bookworm source code, you stumble upon a class that represents a snippet of text out of a book:

 
class​ TEXT
 
# ...

Ruby class names are conventionally Pascal cased: words are concatenated with the first letter of each capitalized: ThisTextIsPascalCased, so you rename the class Text:

 
class​ Text

You change the name of the class everywhere it’s used, you run the unit tests, and—surprise!—the tests fail with a cryptic error message:

<= 
TypeError: Text is not a class

“D’oh! Of course it is,” you exclaim. Bill is as puzzled as you are, so it takes the two of you some time to find the cause of the problem. As it turns out, the Bookworm application requires an old version of the popular Action Mailer library. Action Mailer, in turn, uses a text-formatting library that defines a module named—you guessed it—Text:

 
module​ Text

That’s where the problem lies: because Text is already the name of a module, Ruby complains that it can’t also be the name of a class at the same time.

In a sense, you were lucky that this name clash was readily apparent. If Action Mailer’s Text had been a class, you might have never noticed that this name already existed. Instead, you’d have inadvertently Monkeypatched () the existing Text class. At that point, only your unit tests would have protected you from potential bugs.

Fixing the clash between your Text class and Action Mailer’s Text module is as easy as wrapping your class in a Namespace ():

 
module​ Bookworm
 
class​ Text

You and Bill also change all references to Text into references to Bookworm::Text. It’s unlikely that an external library defines a class named Bookworm::Text, so you should be safe from clashes now.

That was a lot of learning in a single sitting. You deserve a break and a cup of coffee—and a little quiz.

Назад: Open Classes
Дальше: Quiz: Missing Lines