Taken directly from Programming Ruby.
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.
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.
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.
--
DaleBrayden - 19 Oct 2002