1#
2#   forwardable.rb -
3#       $Release Version: 1.1$
4#       $Revision: 38689 $
5#       by Keiju ISHITSUKA(keiju@ishitsuka.com)
6#       original definition by delegator.rb
7#       Revised by Daniel J. Berger with suggestions from Florian Gross.
8#
9#       Documentation by James Edward Gray II and Gavin Sinclair
10
11
12
13# The Forwardable module provides delegation of specified
14# methods to a designated object, using the methods #def_delegator
15# and #def_delegators.
16#
17# For example, say you have a class RecordCollection which
18# contains an array <tt>@records</tt>.  You could provide the lookup method
19# #record_number(), which simply calls #[] on the <tt>@records</tt>
20# array, like this:
21#
22#   class RecordCollection
23#     extend Forwardable
24#     def_delegator :@records, :[], :record_number
25#   end
26#
27# Further, if you wish to provide the methods #size, #<<, and #map,
28# all of which delegate to @records, this is how you can do it:
29#
30#   class RecordCollection
31#     # extend Forwardable, but we did that above
32#     def_delegators :@records, :size, :<<, :map
33#   end
34#   f = Foo.new
35#   f.printf ...
36#   f.gets
37#   f.content_at(1)
38#
39# If the object isn't a Module and Class, You can too extend Forwardable
40# module.
41#
42#   printer = String.new
43#   printer.extend Forwardable              # prepare object for delegation
44#   printer.def_delegator "STDOUT", "puts"  # add delegation for STDOUT.puts()
45#   printer.puts "Howdy!"
46#
47# == Another example
48#
49# We want to rely on what has come before obviously, but with delegation we can
50# take just the methods we need and even rename them as appropriate.  In many
51# cases this is preferable to inheritance, which gives us the entire old
52# interface, even if much of it isn't needed.
53#
54#   class Queue
55#     extend Forwardable
56#
57#     def initialize
58#       @q = [ ]    # prepare delegate object
59#     end
60#
61#     # setup preferred interface, enq() and deq()...
62#     def_delegator :@q, :push, :enq
63#     def_delegator :@q, :shift, :deq
64#
65#     # support some general Array methods that fit Queues well
66#     def_delegators :@q, :clear, :first, :push, :shift, :size
67#   end
68#
69#   q = Queue.new
70#   q.enq 1, 2, 3, 4, 5
71#   q.push 6
72#
73#   q.shift    # => 1
74#   while q.size > 0
75#     puts q.deq
76#   end
77#
78#   q.enq "Ruby", "Perl", "Python"
79#   puts q.first
80#   q.clear
81#   puts q.first
82#
83# This should output:
84#
85#   2
86#   3
87#   4
88#   5
89#   6
90#   Ruby
91#   nil
92#
93# == Notes
94#
95# Be advised, RDoc will not detect delegated methods.
96#
97# +forwardable.rb+ provides single-method delegation via the def_delegator and
98# def_delegators methods. For full-class delegation via DelegateClass, see
99# +delegate.rb+.
100#
101module Forwardable
102  # Version of +forwardable.rb+
103  FORWARDABLE_VERSION = "1.1.0"
104
105  @debug = nil
106  class << self
107    # If true, <tt>__FILE__</tt> will remain in the backtrace in the event an
108    # Exception is raised.
109    attr_accessor :debug
110  end
111
112  # Takes a hash as its argument.  The key is a symbol or an array of
113  # symbols.  These symbols correspond to method names.  The value is
114  # the accessor to which the methods will be delegated.
115  #
116  # :call-seq:
117  #    delegate method => accessor
118  #    delegate [method, method, ...] => accessor
119  #
120  def instance_delegate(hash)
121    hash.each{ |methods, accessor|
122      methods = [methods] unless methods.respond_to?(:each)
123      methods.each{ |method|
124        def_instance_delegator(accessor, method)
125      }
126    }
127  end
128
129  #
130  # Shortcut for defining multiple delegator methods, but with no
131  # provision for using a different name.  The following two code
132  # samples have the same effect:
133  #
134  #   def_delegators :@records, :size, :<<, :map
135  #
136  #   def_delegator :@records, :size
137  #   def_delegator :@records, :<<
138  #   def_delegator :@records, :map
139  #
140  def def_instance_delegators(accessor, *methods)
141    methods.delete("__send__")
142    methods.delete("__id__")
143    for method in methods
144      def_instance_delegator(accessor, method)
145    end
146  end
147
148  # Define +method+ as delegator instance method with an optional
149  # alias name +ali+. Method calls to +ali+ will be delegated to
150  # +accessor.method+.
151  #
152  #   class MyQueue
153  #     extend Forwardable
154  #     attr_reader :queue
155  #     def initialize
156  #       @queue = []
157  #     end
158  #
159  #     def_delegator :@queue, :push, :mypush
160  #   end
161  #
162  #   q = MyQueue.new
163  #   q.mypush 42
164  #   q.queue    #=> [42]
165  #   q.push 23  #=> NoMethodError
166  #
167  def def_instance_delegator(accessor, method, ali = method)
168    line_no = __LINE__; str = %{
169      def #{ali}(*args, &block)
170        begin
171          #{accessor}.__send__(:#{method}, *args, &block)
172        rescue Exception
173          $@.delete_if{|s| %r"#{Regexp.quote(__FILE__)}"o =~ s} unless Forwardable::debug
174          ::Kernel::raise
175        end
176      end
177    }
178    # If it's not a class or module, it's an instance
179    begin
180      module_eval(str, __FILE__, line_no)
181    rescue
182      instance_eval(str, __FILE__, line_no)
183    end
184
185  end
186
187  alias delegate instance_delegate
188  alias def_delegators def_instance_delegators
189  alias def_delegator def_instance_delegator
190end
191
192# SingleForwardable can be used to setup delegation at the object level as well.
193#
194#    printer = String.new
195#    printer.extend SingleForwardable        # prepare object for delegation
196#    printer.def_delegator "STDOUT", "puts"  # add delegation for STDOUT.puts()
197#    printer.puts "Howdy!"
198#
199# Also, SingleForwardable can be used to set up delegation for a Class or Module.
200#
201#   class Implementation
202#     def self.service
203#       puts "serviced!"
204#     end
205#   end
206#   
207#   module Facade
208#     extend SingleForwardable
209#     def_delegator :Implementation, :service
210#   end
211#
212#   Facade.service #=> serviced!
213#   
214# If you want to use both Forwardable and SingleForwardable, you can
215# use methods def_instance_delegator and def_single_delegator, etc.
216module SingleForwardable
217  # Takes a hash as its argument.  The key is a symbol or an array of
218  # symbols.  These symbols correspond to method names.  The value is
219  # the accessor to which the methods will be delegated.
220  #
221  # :call-seq:
222  #    delegate method => accessor
223  #    delegate [method, method, ...] => accessor
224  #
225  def single_delegate(hash)
226    hash.each{ |methods, accessor|
227      methods = [methods] unless methods.respond_to?(:each)
228      methods.each{ |method|
229        def_single_delegator(accessor, method)
230      }
231    }
232  end
233
234  #
235  # Shortcut for defining multiple delegator methods, but with no
236  # provision for using a different name.  The following two code
237  # samples have the same effect:
238  #
239  #   def_delegators :@records, :size, :<<, :map
240  #
241  #   def_delegator :@records, :size
242  #   def_delegator :@records, :<<
243  #   def_delegator :@records, :map
244  #
245  def def_single_delegators(accessor, *methods)
246    methods.delete("__send__")
247    methods.delete("__id__")
248    for method in methods
249      def_single_delegator(accessor, method)
250    end
251  end
252
253  # :call-seq:
254  #   def_single_delegator(accessor, method, new_name=method)
255  #
256  # Defines a method _method_ which delegates to _accessor_ (i.e. it calls
257  # the method of the same name in _accessor_).  If _new_name_ is
258  # provided, it is used as the name for the delegate method.
259  def def_single_delegator(accessor, method, ali = method)
260    str = %{
261      def #{ali}(*args, &block)
262        begin
263          #{accessor}.__send__(:#{method}, *args, &block)
264        rescue Exception
265          $@.delete_if{|s| %r"#{Regexp.quote(__FILE__)}"o =~ s} unless Forwardable::debug
266          ::Kernel::raise
267        end
268      end
269    }
270
271    instance_eval(str, __FILE__, __LINE__)
272  end
273
274  alias delegate single_delegate
275  alias def_delegators def_single_delegators
276  alias def_delegator def_single_delegator
277end
278