Having exhausted the combinations of classes and objects, we can
(thankfully) get back to programming by looking at the nuts and bolts
of class and module definitions.
In languages such as C++ and Java, class definitions are processed at
compile time: the compiler loads up symbol tables, works out how much
storage to allocate, constructs dispatch tables, and does all those other
obscure things we'd rather not think too hard about.
Ruby is different. In Ruby, class and module definitions are executable
code. Although parsed at compile time, the classes and modules are
created at runtime, when the definition is encountered. (The same is
also true of method definitions.) This allows you to structure your
programs far more dynamically than in most conventional languages.
You can make decisions once, when the class is being
defined, rather than each time that objects of the class are
used. The class in the following example decides as it is being
defined what version of a decryption routine to create.
class MediaPlayer
include Tracing if $DEBUGGING
if ::EXPORT_VERSION
def decrypt(stream)
raise "Decryption not available"
end
else
def decrypt(stream)
# ...
end
end
end
|
If class definitions are executable code, this implies that they
execute in the context of some object:
self
must reference
something. Let's find out what it is.
class Test
puts "Type of self = #{self.type}"
puts "Name of self = #{self.name}"
end
|
produces:
Type of self = Class
Name of self = Test
|
This means that a class definition is executed with that class as the
current object. Referring back to the section about metaclasses
on page 238, we can see that this means that methods in
the metaclass and its superclasses will be available during the
execution of the method definition. We can check this out.
class Test
def Test.sayHello
puts "Hello from #{name}"
end
sayHello
end
|
produces:
In this example we define a class method,
Test.sayHello
, and
then call it in the body of the class definition. Within
sayHello
, we call
name
, an instance method of class
Module
. Because
Module
is an ancestor of
Class
, its instance
methods can be called without an explicit receiver within a class definition.
In fact, many of the directives that you use when defining a class
or module, things such as
alias_method
,
attr
, and
public
, are simply methods in class
Module
. This opens up
some interesting possibilities---you can extend the functionality of
class and module definitions by writing Ruby code. Let's look at a
couple of examples.
As a first example, let's look at adding a basic documentation
facility to modules and classes.
This would allow us to associate a
string with modules and classes that we write, a string that is
accessible as the program is running. We'll choose a simple syntax.
class Example
doc "This is a sample documentation string"
# .. rest of class
end
|
We need to make
doc
available to any module or class, so we
need to make it an instance method of class
Module
.
class Module
@@docs = Hash.new(nil)
def doc(str)
@@docs[self.name] = str
end
def Module::doc(aClass)
# If we're passed a class or module, convert to string
# ('<=' for classes checks for same class or subtype)
aClass = aClass.name if aClass.type <= Module
@@docs[aClass] || "No documentation for #{aClass}"
end
end
class Example
doc "This is a sample documentation string"
# .. rest of class
end
module Another
doc <<-edoc
And this is a documentation string
in a module
edoc
# rest of module
end
puts Module::doc(Example)
puts Module::doc("Another")
|
produces:
This is a sample documentation string
And this is a documentation string
in a module
|
The second example is a performance enhancement based on Tadayoshi Funaba's
date
module
(described beginning on page 439). Say we
have a class that represents some underlying quantity (in this case, a
date). The class may have many attributes that present the same
underlying date in different ways: as a Julian day number, as a
string, as a [year, month, day] triple, and so on. Each value
represents the same date and may involve a fairly complex calculation
to derive. We therefore would like to calculate each attribute only
once, when it is first accessed.
The manual way would be to add a test to each accessor:
class ExampleDate
def initialize(dayNumber)
@dayNumber = dayNumber
end
def asDayNumber
@dayNumber
end
def asString
unless @string
# complex calculation
@string = result
end
@string
end
def asYMD
unless @ymd
# another calculation
@ymd = [ y, m, d ]
end
@ymd
end
# ...
end
|
This is a clunky technique---let's see if we can come up with
something sexier.
What we're aiming for is a directive that indicates that the body of a
particular method should be invoked only once. The value returned by
that first call should be cached. Thereafter, calling that same method
should return the cached value without reevaluating the method body
again. This is similar to Eiffel's
once
modifier for routines.
We'd like to be able to write something like:
class ExampleDate
def asDayNumber
@dayNumber
end
def asString
# complex calculation
end
def asYMD
# another calculation
[ y, m, d ]
end
once :asString, :asYMD
end
|
We can use
once
as a directive by writing it as a class method of
ExampleDate
,
but what should it look like internally? The trick
is to have it rewrite the methods whose names it is passed. For each
method, it creates an alias for the original code, then creates a new
method with the same name. This new method does two things. First, it
invokes the original method (using the alias) and stores the resulting
value in an instance variable. Second, it redefines itself, so that on
subsequent calls it simply returns the value of the instance variable
directly. Here's Tadayoshi Funaba's code, slightly reformatted.
def ExampleDate.once(*ids)
for id in ids
module_eval <<-"end_eval"
alias_method :__#{id.to_i}__, #{id.inspect}
def #{id.id2name}(*args, &block)
def self.#{id.id2name}(*args, &block)
@__#{id.to_i}__
end
@__#{id.to_i}__ = __#{id.to_i}__(*args, &block)
end
end_eval
end
end
|
This code uses
module_eval
to execute a block of code in the context of
the calling module (or, in this case, the calling class). The original
method is renamed
__nnn__, where the
nnn part is
the integer representation of the method name's symbol id. The code
uses the same name for the caching instance variable. The bulk
of the code is a method that dynamically redefines itself. Note that
this redefinition uses the fact that methods may contain nested
singleton method definitions, a clever trick.
Understand this code, and you'll be well on the way to true Ruby mastery.
However, we can take it further. Look in the
date
module, and you'll
see method
once
written slightly differently.
class Date
class << self
def once(*ids)
# ...
end
end
# ...
end
|
The interesting thing here is the inner class definition,
``
class << self
''. This defines a class based on the object
self
, and
self
happens to be the class object for
Date
. The result? Every method within the inner class definition
is automatically a class method of
Date
.
The
once
feature is generally
applicable---it should work for any class. If you took
once
and made it a private instance method of class
Module
, it would be
available for use in any Ruby class.