Where you and Bill tread familiar ground: the Bookworm application and the Ruby object model.
You stumble sleepily into the office, craving your Thursday morning coffee, only to be ambushed by an excited Bill. “Hey!” he says. “Everyone likes the refactorings of Bookworm we did Monday, and the boss wants more. But before we start, let’s go over some theory about class definitions. We’ll begin where we left off Monday: in the Ruby object model.”
You probably think of a class definition as the place where you define methods. In fact, you can put any code you want in a class definition:
| class MyClass |
| puts 'Hello' |
| end |
<= | Hello |
Class definitions also return the value of the last statement, just like methods and blocks do:
| result = class MyClass |
| self |
| end |
| |
| result # => MyClass |
This last example emphasizes a compelling point that you might remember from : in a class (or module) definition, the class itself takes the role of the current object self. Classes and modules are just objects, so why couldn’t a class be self? Keep this point about class definitions and self in mind, because the concept will become useful a bit later.
While we’re on the topic of self, you can learn about a related concept: that of the current class.
As you know, wherever you are in a Ruby program, you always have a current object: self. Likewise, you always have a current class (or module). When you define a method, that method becomes an instance method of the current class.
Although you can get a reference to the current object through self, there’s no equivalent keyword to get a reference to the current class. However, in most situations it’s easy to keep track of the current class just by looking at the code. Here’s how:
At the top level of your program, the current class is Object, the class of main. (That’s why, if you define a method at the top level, that method becomes an instance method of Object.)
In a method, the current class is the class of the current object. (Try defining a method inside another method with def, and you’ll see that the new method is defined on the class of self. This information is probably going to win you some Ruby trivia contest.)
When you open a class with the class keyword (or a module with the module keyword), that class becomes the current class.
This last case is probably the only one that you care about in practice. Indeed, you use it all the time when you open a class with the class keyword, and define methods in the class with def. However, the class keyword has a limitation: it needs the name of a class. Unfortunately, in some situations you may not know the name of the class that you want to open. For example, think of a method that takes a class and adds a new instance method to it:
| def add_method_to(a_class) |
| # TODO: define method m() on a_class |
| end |
How can you open the class if you don’t know its name? You need some way other than the class keyword to change the current class. Enter the class_eval method.
Module#class_eval (also known by its alternate name, module_eval) evaluates a block in the context of an existing class:
| def add_method_to(a_class) |
| a_class.class_eval do |
| def m; 'Hello!'; end |
| end |
| end |
| |
| add_method_to String |
| "abc".m # => "Hello!" |
Module#class_eval is very different from BasicObject#instance_eval, which you learned about earlier in . instance_eval only changes self, while class_eval changes both self and the current class.
(This is not the whole truth: instance_eval also changes the current class, but you’ll have to wait for , to learn how exactly. For now, you can safely ignore the problem and assume that instance_eval only changes self.)
By changing the current class, class_eval effectively reopens the class, just like the class keyword does.
Module#class_eval is actually more flexible than class. You can use class_eval on any variable that references the class, while class requires a constant. Also, class opens a new scope, losing sight of the current bindings, while class_eval has a Flat Scope (). As you learned in , this means you can reference variables from the outer scope in a class_eval block.
Finally, just like instance_eval has a twin method called instance_exec, module_eval/class_eval also has an equivalent module_exec/class_exec method that can pass extra parameters to the block.
Now that you know about both instance_eval and class_eval, you might wonder which of the two you should use. In most cases the answer is easy: you use instance_eval to open an object that is not a class, and class_eval to open a class definition and define methods with def. But what if you want to open an object that happens to be a class (or module) to do something other than using def? Should you use instance_eval or class_eval then?
If all you want is to change self, then both instance_eval and class_eval will do the job nicely. However, you should pick the method that best communicates your intentions. If you’re thinking, “I want to open this object, and I don’t particularly care that it’s a class,” then instance_eval is fine. If you’re thinking, “I want an Open Class () here,” then class_eval is almost certainly a better match.
That was a lot of information about the current class and how to deal with it. Let’s recap the important points that we just went through.
You learned a few things about class definitions:
The Ruby interpreter always keeps a reference to the current class (or module). All methods defined with def become instance methods of the current class.
In a class definition, the current object self and the current class are the same—the class being defined.
If you have a reference to the class, you can open the class with class_eval (or module_eval).
How can this stuff ever be useful in real life? To show you how you can apply this theory about the current class, let’s look at a trick called Class Instance Variables.
The Ruby interpreter assumes that all instance variables belong to the current object self. This is also true in a class definition:
| class MyClass |
| @my_var = 1 |
| end |
In a class definition, the role of self belongs to the class itself, so the instance variable @my_var belongs to the class. Don’t get confused. Instance variables of the class are different from instance variables of that class’s objects, as you can see in the following example:
| class MyClass |
| @my_var = 1 |
| def self.read; @my_var; end |
| def write; @my_var = 2; end |
| def read; @my_var; end |
| end |
| |
| obj = MyClass.new |
| obj.read # => nil |
| obj.write |
| obj.read # => 2 |
| MyClass.read # => 1 |
The previous code defines two instance variables. Both happen to be named @my_var, but they’re defined in different scopes, and they belong to different objects. To see how this works, you have to remember that classes are just objects, and you have to track self through the program. One @my_var is defined with obj as self, so it’s an instance variable of the obj object. The other @my_var is defined with MyClass as self, so it’s an instance variable of the MyClass object—a Spell: .
If you come from Java, you may be tempted to think that Class Instance Variables are similar to Java’s “static fields.” Instead, they’re just regular instance variables that happen to belong to an object of class Class. Because of that, a Class Instance Variable can be accessed only by the class itself—not by an instance or by a subclass.
We’ve touched on many things: the current class, class definitions, self, class_eval, Class Instance Variables.… Now you can go back to Bookworm and put these features together.
The Bookworm source contains very few unit tests, so it’s up to you and Bill to write tests as you refactor. Sometimes this proves to be difficult, as is the case with this class:
| class Loan |
| def initialize(book) |
| @book = book |
| @time = Time.now |
| end |
| |
| def to_s |
| "#{@book.upcase} loaned on #{@time}" |
| end |
| end |
Loan stores the title of a book and the time when it was loaned—that is, the time when the object was created. You’d like to write a unit test for the to_s method, but to write that test, you’d have to know the exact time when the object was created. This is a common problem with code that relies on Time or Date: such code returns a different result every time it runs, so you don’t know what result to test for.
“I think I have a solution to this problem,” Bill announces. “It’s a bit involved, so it will require some attention on your part. Here it is.”
| class Loan |
| def initialize(book) |
| @book = book |
* | @time = Loan.time_class.now |
| end |
| |
* | def self.time_class |
* | @time_class || Time |
* | end |
| |
| def to_s |
| # ... |
Loan.time_class returns a class, and Loan#initialize uses that class to get the current time. The class is stored in a Class Instance Variable () named @time_class. If @time_class is nil, the Nil Guard () in time_class returns the Time class as a default.
In production, Loan always uses the Time class, because @time_class is always nil. By contrast, the unit tests can rely on a fake time class that always returns the same value. The tests can assign a value to the private @time_class variable by using either class_eval or instance_eval. Either of the two methods will do here, because they both change self:
| class FakeTime |
| def self.now; 'Mon Apr 06 12:15:50'; end |
| end |
| |
| require 'test/unit' |
| |
| class TestLoan < Test::Unit::TestCase |
| def test_conversion_to_string |
| Loan.instance_eval { @time_class = FakeTime } |
| loan = Loan.new('War and Peace') |
| assert_equal 'WAR AND PEACE loaned on Mon Apr 06 12:15:50', loan.to_s |
| end |
| end |
Bill is quite proud of his own coding prowess. He says, “I think we deserve a break—after I give you a quiz.”