The Pragmatic Programmers
Programming Ruby

Programming Ruby

 
  Home
  Programming Ruby
  Links to Ruby Information
  Class reference
  Articles on Ruby
      Ruby and the Web
      Distributed Ruby
      Calling Methods Dynamically
      Blocks and Constructors
      Mutual Exclusion
»     Messing Around with Objects
      Embedded Documentation
      OO Regular Expressions
      Test-first in Ruby
  Downloads
  Errata
  Code from the book
  New Features
  Presentation: Intro. to Ruby

Messing around with Objects


Have you ever craved the ability to traverse all objects that are currently alive in your program? We have! Ruby lets you perform this trick with ObjectSpace.each_object . We can use it to do all sorts of neat tricks. For example, to iterate over all objects of type Numeric, you'd write:
  
  a = 102.7
  b = 95.1
  ObjectSpace.each_object(Numeric) {|x| p x }

Produces:

95.1
102.7
2.718281828
3.141592654

Hey, where did those last two numbers come from? We didn't define them in our program. If you look at module Math , you'll see constants for e and pi; since we are examining all living objects in the system, these turn up as well. However, there is a catch. Let's try the same example with different numbers.
  
  a = 102
  b = 95
  ObjectSpace.each_object(Numeric) {|x| p x }

Produces:

2.718281828
3.141592654

Neither of the Fixnum objects we created showed up. That's because ObjectSpace doesn't know about objects with immediate values: Fixnum, true, false, and nil.

Looking Inside Objects

Once you've found an interesting object, you may be tempted to find out just what it can do. Unlike static languages, where a variable's type determines its class, and hence the methods it supports, Ruby supports liberated objects. You really cannot tell exactly what an object can do until you look under its hood (or bonnet, for objects created to the east of the Atlantic). For instance, we can get a list of all the methods that an object will respond to.
  
  r = 1..10 # Create a Range object
  list = r.methods
  list.length    # -> 60
  list[0..3]     # -> ["size", "exclude_end?", "to_s", "length"]

Or, we can check to see if a object supports a particular method.
  
  r.respond_to?("frozen?")  # -> true
  r.respond_to?("hasKey")   # -> false
  "me".respond_to?("==")    # -> true

We can find out our object's class, its unique object id, and test its relationship to other classes.
  
  num = 1
  num.id                    # -> 3
  num.class                 # -> Fixnum
  num.kind_of? Fixnum       # -> true
  num.kind_of? Numeric      # -> true
  num.instance_of? Fixnum   # -> true
  num.instance_of? Numeric  # -> false

Looking at Classes

Knowing about objects is one part of reflection, but to get the whole picture, you also need need to be able to look at classes: the methods and constants that they contain. Looking at the class hierarchy is easy. For any particular class, the method Class#superclass gives you that class' parent. For classes and modules, Module#ancestors lists both superclasses and mixed-in modules.
  
  klass = Fixnum
  begin
    print klass
    klass = klass.superclass
    print " < " if klass
  end while klass
  puts
  p Fixnum.ancestors

Produces:

Fixnum < Integer < Numeric < Object
[Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel]

If you wanted to build a complete class hierarchy, just run that code for every class in the system. We can use ObjectSpace to iterate over all Class objects:
  
  ObjectSpace.each_object(Class) do |aClass|
     # ...
  end

Looking Inside Classes

We can find out a bit more about the methods and constants in a particular object. Instead of just checking to see whether the object responds to a given message, we can ask for methods by access level, we can ask for just singleton methods, and we can have a look at the object's constants.
  
  class Demo
    private
      def privMethod
      end
    protected
      def protMethod
      end
    public
      def pubMethod
      end

    def Demo.classMethod
    end
    
    CONST = 1.23
  end

  Demo.private_instance_methods               # -> ["privMethod"]
  Demo.protected_instance_methods             # -> ["protMethod"]
  Demo.public_instance_methods                # -> ["pubMethod"]
  Demo.singleton_methods                      # -> ["classMethod"]
  Demo.constants - Demo.superclass.constants  # -> ["CONST"]

Module.constants returns all the constants available via a module, including constants from the module's superclasses. We're not interested in those just at the moment, so we'll subtract them from our list. Given a list of method names, we might now be tempted to try calling them. Fortunately, that's easy with Ruby, but it's the subject of another article.


Copyright (c) 2001, The Pragmatic Programmers

 

  Home      PragProg      Ruby      Contact us