Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions

  




 

 

Ruby Programming
Previous Page Home Next Page

Class and Module Definitions

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:
Hello from Test

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.
Ruby Programming
Previous Page Home Next Page

 
 
  Published under the terms of the Open Publication License Design by Interspire