Where it’s your turn to teach Bill a few tricks.
It’s late morning, and you and Bill are deep in the flow. You’re zipping through the Bookworm source, deleting a useless line here, changing a confusing name there, and generally polishing the code…until you bump into a particularly troublesome bit of refactoring.
The Paragraph class wraps a string and then delegates all calls to the wrapped string—all of them, that is, except for one method, Paragraph#title?, which returns true if a Paragraph is all uppercase.
| class Paragraph |
| def initialize(text) |
| @text = text |
| end |
| |
| def title?; @text.upcase == @text; end |
| def reverse; @text.reverse; end |
| def upcase; @text.upcase; end |
| # ... |
Paragraph objects are created in a single place in the Bookworm source code. Also, Paragraph#title? is called only once in the whole application, from a method named index:
| def index(paragraph) |
| add_to_index(paragraph) if paragraph.title? |
| end |
Bill frowns. “The stupid Paragraph class really doesn’t hold its own weight. We could scrap it entirely and just use regular Strings, if it weren’t for the title? method.”
“Why don’t we Monkeypatch () the String class and add the title? method right there?” you offer. “I’m not convinced,” Bill says. “A method with that name would make sense only on strings that represent a paragraph, not on each and every string.”
While Bill is pondering the idea of patching the String class with a Refinement (), you decide to Google for a solution.
As it turns out, Ruby allows you to add a method to a single object. For example, here’s how you can add title? to a specific string:
| str = "just a regular string" |
| |
| def str.title? |
| self.upcase == self |
| end |
| |
| str.title? # => false |
| str.methods.grep(/title?/) # => [:title?] |
| str.singleton_methods # => [:title?] |
The previous code adds a method named title? to str. No other object gets the method—not even other Strings. A method like this one, which is specific to a single object, is called a Spell: . You can define a Singleton Method with either the syntax above or the Object#define_singleton_method method.
Thanks to Singleton Methods, you can now fix your problem with the Bookworm source. You can send any old String to index if you enhance that String with a title? Singleton Method:
| paragraph = "any string can be a paragraph" |
| |
| def paragraph.title? |
| self.upcase == self |
| end |
| |
| index(paragraph) |
Now you can use plain strings in Bookworm and delete the Paragraph class.
Bill is awestruck by your solution. “I knew about Singleton Methods, but I never realized you could use them this way.”
“Wait a minute,” you reply. “You knew about them? What did you think they were useful for?”
“Singleton Methods aren’t just useful for enhancing a specific object, like you just did.” Bill replies. “They’re also the basis for one of Ruby’s most common features. What if I told you that you’ve been using Singleton Methods all along, without ever knowing it?”
Remember what you learned in ? Classes are just objects, and class names are just constants. If you remember this concept, then you’ll see that calling a method on a class is the same as calling a method on an object:
| an_object.a_method |
| AClass.a_class_method |
See? The first line calls a method on an object referenced by a variable, and the second line calls a method on an object (that also happens to be a class) referenced by a constant. It’s the same syntax.
But, wait—there’s more. Remember how Bill told you that you’ve been using Singleton Methods () all along? That’s really what class methods are: they’re Singleton Methods of a class. In fact, if you compare the definition of a Singleton Method and the definition of a class method, you’ll see that they’re the same:
| def obj.a_singleton_method; end |
| def MyClass.another_class_method; end |
So, the syntax for defining a Singleton Method with def is always the same:
| def object.method |
| # Method body here |
| end |
In the definition shown previously, object can be an object reference, a constant class name, or self. The syntax might look different in the three cases, but in truth the underlying mechanism is always the same. Nice design, don’t you think?
You’re not quite finished with class methods yet. There’s a very useful and common spell that relies on class methods exclusively, and it deserves its own discussion.
Look at this example, coming straight from the core of Ruby.
Ruby objects don’t have attributes. If you want something that looks like an attribute, you have to define two Mimic Methods (), a reader and a writer:
| class MyClass |
| def my_attribute=(value) |
| @my_attribute = value |
| end |
| |
| |
| def my_attribute |
| @my_attribute |
| end |
| end |
| |
| obj = MyClass.new |
| obj.my_attribute = 'x' |
| obj.my_attribute # => "x" |
Writing methods like these (also called accessors) gets boring quickly. As an alternative, you can generate accessors by using one of the methods in the Module#attr_* family. Module#attr_reader generates the reader, Module#attr_writer generates the writer, and Module#attr_accessor generates both:
| class MyClass |
| attr_accessor :my_attribute |
| end |
All the attr_* methods are defined on class Module, so you can use them whenever self is a module or a class. A method such as attr_accessor is called a Spell: . Class Macros look like keywords, but they’re just regular class methods that are meant to be used in a class definition.
“Now that you know about Class Macros,” Bill says, “I think I know a place in Bookworm’s source code where we can make good use of them.”
The Book class in the Bookworm source code has methods named GetTitle, title2, and LEND_TO_USER. By Ruby’s conventions, these methods should be named title, subtitle, and lend_to, respectively. However, there are other projects that use the Book class, and you have no control over these projects. If you just rename the methods, you will break the callers.
Bill has an idea to fix this situation: you can rename the methods if you invent a Class Macro () that deprecates the old names:
| class Book |
| def title # ... |
| |
| def subtitle # ... |
| |
| def lend_to(user) |
| puts "Lending to #{user}" |
| # ... |
| end |
| |
| |
| def self.deprecate(old_method, new_method) |
| define_method(old_method) do |*args, &block| |
| warn "Warning: #{old_method}() is deprecated. Use #{new_method}()." |
| send(new_method, *args, &block) |
| end |
| end |
| |
| deprecate :GetTitle, :title |
| deprecate :LEND_TO_USER, :lend_to |
| deprecate :title2, :subtitle |
| end |
The deprecate method takes the old name and the new name of a method and defines a Dynamic Method () that catches calls to the old name. The Dynamic Method forwards the calls to the renamed method—but first it prints a warning on the console to notify the callers that the old name has been deprecated:
| b = Book.new |
| b.LEND_TO_USER("Bill") |
<= | Warning: LEND_TO_USER() is deprecated. Use lend_to(). |
| Lending to Bill |
That was an ingenious way to use a Class Macro. However, if you really want to understand Class Macros, as well as Singleton Methods in general, you have to fit one last missing piece in the Ruby object model.