delegate.rb

Path: lib/delegate.rb
Last Update: Fri Jul 03 13:24:46 +0000 2009

delegate — Support for the Delegation Pattern

Documentation by James Edward Gray II and Gavin Sinclair

Introduction

This library provides three different ways to delegate method calls to an object. The easiest to use is SimpleDelegator. Pass an object to the constructor and all methods supported by the object will be delegated. This object can be changed later.

Going a step further, the top level DelegateClass method allows you to easily setup delegation through class inheritance. This is considerably more flexible and thus probably the most common use for this library.

Finally, if you need full control over the delegation scheme, you can inherit from the abstract class Delegator and customize as needed. (If you find yourself needing this control, have a look at forwardable, also in the standard library. It may suit your needs better.)

Notes

Be advised, RDoc will not detect delegated methods.

delegate.rb provides full-class delegation via the DelegateClass() method. For single-method delegation via def_delegator(), see forwardable.rb.

Examples

SimpleDelegator

Here‘s a simple example that takes advantage of the fact that SimpleDelegator‘s delegation object can be changed at any time.

  class Stats
    def initialize
      @source = SimpleDelegator.new([])
    end

    def stats( records )
      @source.__setobj__(records)

      "Elements:  #{@source.size}\n" +
      " Non-Nil:  #{@source.compact.size}\n" +
      "  Unique:  #{@source.uniq.size}\n"
    end
  end

  s = Stats.new
  puts s.stats(%w{James Edward Gray II})
  puts
  puts s.stats([1, 2, 3, nil, 4, 5, 1, 2])

Prints:

  Elements:  4
   Non-Nil:  4
    Unique:  4

  Elements:  8
   Non-Nil:  7
    Unique:  6

DelegateClass()

Here‘s a sample of use from tempfile.rb.

A Tempfile object is really just a File object with a few special rules about storage location and/or when the File should be deleted. That makes for an almost textbook perfect example of how to use delegation.

  class Tempfile < DelegateClass(File)
    # constant and class member data initialization...

    def initialize(basename, tmpdir=Dir::tmpdir)
      # build up file path/name in var tmpname...

      @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)

      # ...

      super(@tmpfile)

      # below this point, all methods of File are supported...
    end

    # ...
  end

Delegator

SimpleDelegator‘s implementation serves as a nice example here.

   class SimpleDelegator < Delegator
     def initialize(obj)
       super             # pass obj to Delegator constructor, required
       @_sd_obj = obj    # store obj for future use
     end

     def __getobj__
       @_sd_obj          # return object we are delegating to, required
     end

     def __setobj__(obj)
       @_sd_obj = obj    # change delegation object, a feature we're providing
     end

     # ...
   end

Methods

Public Instance methods

The primary interface to this library. Use to setup delegation when defining your class.

  class MyClass < DelegateClass( ClassToDelegateTo )    # Step 1
    def initialize
      super(obj_of_ClassToDelegateTo)                   # Step 2
    end
  end

[Source]

     # File lib/delegate.rb, line 259
259: def DelegateClass(superclass)
260:   klass = Class.new
261:   methods = superclass.public_instance_methods(true)
262:   methods -= ::Kernel.public_instance_methods(false)
263:   methods |= ["to_s","to_a","inspect","==","=~","==="]
264:   klass.module_eval {
265:     def initialize(obj)  # :nodoc:
266:       @_dc_obj = obj
267:     end
268:     def method_missing(m, *args)  # :nodoc:
269:       unless @_dc_obj.respond_to?(m)
270:         super(m, *args)
271:       end
272:       @_dc_obj.__send__(m, *args)
273:     end
274:     def respond_to?(m, include_private = false)  # :nodoc:
275:       return true if super
276:       return @_dc_obj.respond_to?(m, include_private)
277:     end
278:     def __getobj__  # :nodoc:
279:       @_dc_obj
280:     end
281:     def __setobj__(obj)  # :nodoc:
282:       raise ArgumentError, "cannot delegate to self" if self.equal?(obj)
283:       @_dc_obj = obj
284:     end
285:     def clone  # :nodoc:
286:       new = super
287:       new.__setobj__(__getobj__.clone)
288:       new
289:     end
290:     def dup  # :nodoc:
291:       new = super
292:       new.__setobj__(__getobj__.clone)
293:       new
294:     end
295:   }
296:   for method in methods
297:     begin
298:       klass.module_eval "def \#{method}(*args, &block)\nbegin\n@_dc_obj.__send__(:\#{method}, *args, &block)\nensure\n$@.delete_if{|s| ::Delegator::IgnoreBacktracePat =~ s} if $@\nend\nend\n", __FILE__, __LINE__+1
299:     rescue SyntaxError
300:       raise NameError, "invalid identifier %s" % method, caller(3)
301:     end
302:   end
303:   return klass
304: end

[Validate]