1# = delegate -- Support for the Delegation Pattern
2#
3# Documentation by James Edward Gray II and Gavin Sinclair
4
5##
6# This library provides three different ways to delegate method calls to an
7# object.  The easiest to use is SimpleDelegator.  Pass an object to the
8# constructor and all methods supported by the object will be delegated.  This
9# object can be changed later.
10#
11# Going a step further, the top level DelegateClass method allows you to easily
12# setup delegation through class inheritance.  This is considerably more
13# flexible and thus probably the most common use for this library.
14#
15# Finally, if you need full control over the delegation scheme, you can inherit
16# from the abstract class Delegator and customize as needed.  (If you find
17# yourself needing this control, have a look at Forwardable which is also in
18# the standard library.  It may suit your needs better.)
19#
20# SimpleDelegator's implementation serves as a nice example if the use of
21# Delegator:
22#
23#   class SimpleDelegator < Delegator
24#     def initialize(obj)
25#       super                  # pass obj to Delegator constructor, required
26#       @delegate_sd_obj = obj # store obj for future use
27#     end
28#
29#     def __getobj__
30#       @delegate_sd_obj # return object we are delegating to, required
31#     end
32#
33#     def __setobj__(obj)
34#       @delegate_sd_obj = obj # change delegation object,
35#                              # a feature we're providing
36#     end
37#   end
38#
39# == Notes
40#
41# Be advised, RDoc will not detect delegated methods.
42#
43class Delegator < BasicObject
44  kernel = ::Kernel.dup
45  kernel.class_eval do
46    [:to_s,:inspect,:=~,:!~,:===,:<=>,:eql?,:hash].each do |m|
47      undef_method m
48    end
49  end
50  include kernel
51
52  # :stopdoc:
53  def self.const_missing(n)
54    ::Object.const_get(n)
55  end
56  # :startdoc:
57
58  #
59  # Pass in the _obj_ to delegate method calls to.  All methods supported by
60  # _obj_ will be delegated to.
61  #
62  def initialize(obj)
63    __setobj__(obj)
64  end
65
66  #
67  # Handles the magic of delegation through \_\_getobj\_\_.
68  #
69  def method_missing(m, *args, &block)
70    target = self.__getobj__
71    begin
72      target.respond_to?(m) ? target.__send__(m, *args, &block) : super(m, *args, &block)
73    ensure
74      $@.delete_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if $@
75    end
76  end
77
78  #
79  # Checks for a method provided by this the delegate object by forwarding the
80  # call through \_\_getobj\_\_.
81  #
82  def respond_to_missing?(m, include_private)
83    r = self.__getobj__.respond_to?(m, include_private)
84    if r && include_private && !self.__getobj__.respond_to?(m, false)
85      warn "#{caller(3)[0]}: delegator does not forward private method \##{m}"
86      return false
87    end
88    r
89  end
90
91  #
92  # Returns the methods available to this delegate object as the union
93  # of this object's and \_\_getobj\_\_ methods.
94  #
95  def methods(all=true)
96    __getobj__.methods(all) | super
97  end
98
99  #
100  # Returns the methods available to this delegate object as the union
101  # of this object's and \_\_getobj\_\_ public methods.
102  #
103  def public_methods(all=true)
104    __getobj__.public_methods(all) | super
105  end
106
107  #
108  # Returns the methods available to this delegate object as the union
109  # of this object's and \_\_getobj\_\_ protected methods.
110  #
111  def protected_methods(all=true)
112    __getobj__.protected_methods(all) | super
113  end
114
115  # Note: no need to specialize private_methods, since they are not forwarded
116
117  #
118  # Returns true if two objects are considered of equal value.
119  #
120  def ==(obj)
121    return true if obj.equal?(self)
122    self.__getobj__ == obj
123  end
124
125  #
126  # Returns true if two objects are not considered of equal value.
127  #
128  def !=(obj)
129    return false if obj.equal?(self)
130    __getobj__ != obj
131  end
132
133  #
134  # Delegates ! to the \_\_getobj\_\_
135  #
136  def !
137    !__getobj__
138  end
139
140  #
141  # This method must be overridden by subclasses and should return the object
142  # method calls are being delegated to.
143  #
144  def __getobj__
145    raise NotImplementedError, "need to define `__getobj__'"
146  end
147
148  #
149  # This method must be overridden by subclasses and change the object delegate
150  # to _obj_.
151  #
152  def __setobj__(obj)
153    raise NotImplementedError, "need to define `__setobj__'"
154  end
155
156  #
157  # Serialization support for the object returned by \_\_getobj\_\_.
158  #
159  def marshal_dump
160    ivars = instance_variables.reject {|var| /\A@delegate_/ =~ var}
161    [
162      :__v2__,
163      ivars, ivars.map{|var| instance_variable_get(var)},
164      __getobj__
165    ]
166  end
167
168  #
169  # Reinitializes delegation from a serialized object.
170  #
171  def marshal_load(data)
172    version, vars, values, obj = data
173    if version == :__v2__
174      vars.each_with_index{|var, i| instance_variable_set(var, values[i])}
175      __setobj__(obj)
176    else
177      __setobj__(data)
178    end
179  end
180
181  def initialize_clone(obj) # :nodoc:
182    self.__setobj__(obj.__getobj__.clone)
183  end
184  def initialize_dup(obj) # :nodoc:
185    self.__setobj__(obj.__getobj__.dup)
186  end
187  private :initialize_clone, :initialize_dup
188
189  ##
190  # :method: trust
191  # Trust both the object returned by \_\_getobj\_\_ and self.
192  #
193
194  ##
195  # :method: untrust
196  # Untrust both the object returned by \_\_getobj\_\_ and self.
197  #
198
199  ##
200  # :method: taint
201  # Taint both the object returned by \_\_getobj\_\_ and self.
202  #
203
204  ##
205  # :method: untaint
206  # Untaint both the object returned by \_\_getobj\_\_ and self.
207  #
208
209  ##
210  # :method: freeze
211  # Freeze both the object returned by \_\_getobj\_\_ and self.
212  #
213
214  [:trust, :untrust, :taint, :untaint, :freeze].each do |method|
215    define_method method do
216      __getobj__.send(method)
217      super()
218    end
219  end
220
221  @delegator_api = self.public_instance_methods
222  def self.public_api   # :nodoc:
223    @delegator_api
224  end
225end
226
227##
228# A concrete implementation of Delegator, this class provides the means to
229# delegate all supported method calls to the object passed into the constructor
230# and even to change the object being delegated to at a later time with
231# #__setobj__.
232#
233# Here's a simple example that takes advantage of the fact that
234# SimpleDelegator's delegation object can be changed at any time.
235#
236#   class Stats
237#     def initialize
238#       @source = SimpleDelegator.new([])
239#     end
240#
241#     def stats(records)
242#       @source.__setobj__(records)
243#
244#       "Elements:  #{@source.size}\n" +
245#       " Non-Nil:  #{@source.compact.size}\n" +
246#       "  Unique:  #{@source.uniq.size}\n"
247#     end
248#   end
249#
250#   s = Stats.new
251#   puts s.stats(%w{James Edward Gray II})
252#   puts
253#   puts s.stats([1, 2, 3, nil, 4, 5, 1, 2])
254#
255# Prints:
256#
257#   Elements:  4
258#    Non-Nil:  4
259#     Unique:  4
260#
261#   Elements:  8
262#    Non-Nil:  7
263#     Unique:  6
264#
265class SimpleDelegator<Delegator
266  # Returns the current object method calls are being delegated to.
267  def __getobj__
268    @delegate_sd_obj
269  end
270
271  #
272  # Changes the delegate object to _obj_.
273  #
274  # It's important to note that this does *not* cause SimpleDelegator's methods
275  # to change.  Because of this, you probably only want to change delegation
276  # to objects of the same type as the original delegate.
277  #
278  # Here's an example of changing the delegation object.
279  #
280  #   names = SimpleDelegator.new(%w{James Edward Gray II})
281  #   puts names[1]    # => Edward
282  #   names.__setobj__(%w{Gavin Sinclair})
283  #   puts names[1]    # => Sinclair
284  #
285  def __setobj__(obj)
286    raise ArgumentError, "cannot delegate to self" if self.equal?(obj)
287    @delegate_sd_obj = obj
288  end
289end
290
291def Delegator.delegating_block(mid) # :nodoc:
292  lambda do |*args, &block|
293    target = self.__getobj__
294    begin
295      target.__send__(mid, *args, &block)
296    ensure
297      $@.delete_if {|t| /\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:/o =~ t} if $@
298    end
299  end
300end
301
302#
303# The primary interface to this library.  Use to setup delegation when defining
304# your class.
305#
306#   class MyClass < DelegateClass(ClassToDelegateTo) # Step 1
307#     def initialize
308#       super(obj_of_ClassToDelegateTo)              # Step 2
309#     end
310#   end
311#
312# Here's a sample of use from Tempfile which is really a File object with a
313# few special rules about storage location and when the File should be
314# deleted.  That makes for an almost textbook perfect example of how to use
315# delegation.
316#
317#   class Tempfile < DelegateClass(File)
318#     # constant and class member data initialization...
319#
320#     def initialize(basename, tmpdir=Dir::tmpdir)
321#       # build up file path/name in var tmpname...
322#
323#       @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
324#
325#       # ...
326#
327#       super(@tmpfile)
328#
329#       # below this point, all methods of File are supported...
330#     end
331#
332#     # ...
333#   end
334#
335def DelegateClass(superclass)
336  klass = Class.new(Delegator)
337  methods = superclass.instance_methods
338  methods -= ::Delegator.public_api
339  methods -= [:to_s,:inspect,:=~,:!~,:===]
340  klass.module_eval do
341    def __getobj__  # :nodoc:
342      @delegate_dc_obj
343    end
344    def __setobj__(obj)  # :nodoc:
345      raise ArgumentError, "cannot delegate to self" if self.equal?(obj)
346      @delegate_dc_obj = obj
347    end
348    methods.each do |method|
349      define_method(method, Delegator.delegating_block(method))
350    end
351  end
352  klass.define_singleton_method :public_instance_methods do |all=true|
353    super(all) - superclass.protected_instance_methods
354  end
355  klass.define_singleton_method :protected_instance_methods do |all=true|
356    super(all) | superclass.protected_instance_methods
357  end
358  return klass
359end
360
361# :enddoc:
362
363if __FILE__ == $0
364  class ExtArray<DelegateClass(Array)
365    def initialize()
366      super([])
367    end
368  end
369
370  ary = ExtArray.new
371  p ary.class
372  ary.push 25
373  p ary
374  ary.push 42
375  ary.each {|x| p x}
376
377  foo = Object.new
378  def foo.test
379    25
380  end
381  def foo.iter
382    yield self
383  end
384  def foo.error
385    raise 'this is OK'
386  end
387  foo2 = SimpleDelegator.new(foo)
388  p foo2
389  foo2.instance_eval{print "foo\n"}
390  p foo.test == foo2.test       # => true
391  p foo2.iter{[55,true]}        # => true
392  foo2.error                    # raise error!
393end
394