summaryrefslogtreecommitdiff
path: root/lib/forwardable.rb
blob: b6344cd4f6fc0b5a1e4ec384c4f6c42be8503c21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# = forwardable - Support for the Delegation Pattern
#
#    $Release Version: 1.1$
#    $Revision$
#    $Date$
#    by Keiju ISHITSUKA(keiju@ishitsuka.com)
#
#    Documentation by James Edward Gray II and Gavin Sinclair
#
# == Introduction
#
# This library allows you delegate method calls to an object, on a method by
# method basis.  You can use Forwardable to setup this delegation at the class
# level, or SingleForwardable to handle it at the object level.
#
# == Notes
#
# Be advised, RDoc will not detect delegated methods.
#
# <b>forwardable.rb provides single-method delegation via the
# def_delegator() and def_delegators() methods.  For full-class
# delegation via DelegateClass(), see delegate.rb.</b>
#
# == Examples
#
# === Forwardable
#
# Forwardable makes building a new class based on existing work, with a proper
# interface, almost trivial.  We want to rely on what has come before obviously,
# but with delegation we can take just the methods we need and even rename them
# as appropriate.  In many cases this is preferable to inheritance, which gives
# us the entire old interface, even if much of it isn't needed.
#
#   class Queue
#     extend Forwardable
#     
#     def initialize
#       @q = [ ]    # prepare delegate object
#     end
#     
#     # setup prefered interface, enq() and deq()...
#     def_delegator :@q, :push, :enq
#     def_delegator :@q, :shift, :deq
#     
#     # support some general Array methods that fit Queues well
#     def_delegators :@q, :clear, :first, :push, :shift, :size
#   end
# 
#   q = Queue.new
#   q.enq 1, 2, 3, 4, 5
#   q.push 6
# 
#   q.shift    # => 1
#   while q.size > 0
#     puts q.deq
#   end
# 
#   q.enq "Ruby", "Perl", "Python"
#   puts q.first
#   q.clear
#   puts q.first
#
# <i>Prints:</i>
#
#   2
#   3
#   4
#   5
#   6
#   Ruby
#   nil
#
# === SingleForwardable
#
#    printer = String.new
#    printer.extend SingleForwardable        # prepare object for delegation
#    printer.def_delegator "STDOUT", "puts"  # add delegation for STDOUT.puts()
#    printer.puts "Howdy!"
#
# <i>Prints:</i>
#
#    Howdy!

#
# The Forwardable module provides delegation of specified
# methods to a designated object, using the methods #def_delegator
# and #def_delegators.
#
# For example, say you have a class RecordCollection which
# contains an array <tt>@records</tt>.  You could provide the lookup method
# #record_number(), which simply calls #[] on the <tt>@records</tt>
# array, like this:
#
#   class RecordCollection
#     extend Forwardable
#     def_delegator :@records, :[], :record_number
#   end
#
# Further, if you wish to provide the methods #size, #<<, and #map,
# all of which delegate to @records, this is how you can do it:
#
#   class RecordCollection
#     # extend Forwardable, but we did that above
#     def_delegators :@records, :size, :<<, :map
#   end
#
# Also see the example at forwardable.rb.
#
module Forwardable

  @debug = nil
  class<<self
    # force Forwardable to show up in stack backtraces of delegated methods
    attr_accessor :debug
  end

  #
  # Shortcut for defining multiple delegator methods, but with no
  # provision for using a different name.  The following two code
  # samples have the same effect:
  #
  #   def_delegators :@records, :size, :<<, :map
  #
  #   def_delegator :@records, :size
  #   def_delegator :@records, :<<
  #   def_delegator :@records, :map
  #
  # See the examples at Forwardable and forwardable.rb.
  #
  def def_instance_delegators(accessor, *methods)
    for method in methods
      def_instance_delegator(accessor, method)
    end
  end

  #
  # Defines a method _method_ which delegates to _obj_ (i.e. it calls
  # the method of the same name in _obj_).  If _new_name_ is
  # provided, it is used as the name for the delegate method.
  #
  # See the examples at Forwardable and forwardable.rb.
  #
  def def_instance_delegator(accessor, method, ali = method)
    accessor = accessor.id2name if accessor.kind_of?(Integer)
    method = method.id2name if method.kind_of?(Integer)
    ali = ali.id2name if ali.kind_of?(Integer)

    module_eval(<<-EOS, "(__FORWARDABLE__)", 1)
      def #{ali}(*args, &block)
	begin
	  #{accessor}.__send__(:#{method}, *args, &block)
	rescue Exception
	  $@.delete_if{|s| /^\\(__FORWARDABLE__\\):/ =~ s} unless Forwardable::debug
	  Kernel::raise
	end
      end
    EOS
  end

  alias def_delegators def_instance_delegators
  alias def_delegator def_instance_delegator
end

#
# The SingleForwardable module provides delegation of specified
# methods to a designated object, using the methods #def_delegator
# and #def_delegators.  This module is similar to Forwardable, but it works on
# objects themselves, instead of their defining classes.
#
# Also see the example at forwardable.rb.
#
module SingleForwardable
  #
  # Shortcut for defining multiple delegator methods, but with no
  # provision for using a different name.  The following two code
  # samples have the same effect:
  #
  #   single_forwardable.def_delegators :@records, :size, :<<, :map
  #
  #   single_forwardable.def_delegator :@records, :size
  #   single_forwardable.def_delegator :@records, :<<
  #   single_forwardable.def_delegator :@records, :map
  #
  # See the example at forwardable.rb.
  #
  def def_singleton_delegators(accessor, *methods)
    for method in methods
      def_singleton_delegator(accessor, method)
    end
  end

  #
  # Defines a method _method_ which delegates to _obj_ (i.e. it calls
  # the method of the same name in _obj_).  If _new_name_ is
  # provided, it is used as the name for the delegate method.
  #
  # See the example at forwardable.rb.
  #
  def def_singleton_delegator(accessor, method, ali = method)
    accessor = accessor.id2name if accessor.kind_of?(Integer)
    method = method.id2name if method.kind_of?(Integer)
    ali = ali.id2name if ali.kind_of?(Integer)

    instance_eval(<<-EOS, "(__FORWARDABLE__)", 1)
       def #{ali}(*args, &block)
	 begin
	   #{accessor}.__send__(:#{method}, *args,&block)
	 rescue Exception
	   $@.delete_if{|s| /^\\(__FORWARDABLE__\\):/ =~ s} unless Forwardable::debug
	   Kernel::raise
	 end
       end
    EOS
  end

  alias def_delegators def_singleton_delegators
  alias def_delegator def_singleton_delegator
end