forked from ruby/ruby
-
Notifications
You must be signed in to change notification settings - Fork 0
/
delegate.rb
405 lines (375 loc) · 10.3 KB
/
delegate.rb
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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# = delegate -- Support for the Delegation Pattern
#
# Documentation by James Edward Gray II and Gavin Sinclair
##
# 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 which is also in
# the standard library. It may suit your needs better.)
#
# SimpleDelegator's implementation serves as a nice example of the use of
# Delegator:
#
# class SimpleDelegator < Delegator
# def __getobj__
# @delegate_sd_obj # return object we are delegating to, required
# end
#
# def __setobj__(obj)
# @delegate_sd_obj = obj # change delegation object,
# # a feature we're providing
# end
# end
#
# == Notes
#
# Be advised, RDoc will not detect delegated methods.
#
class Delegator < BasicObject
kernel = ::Kernel.dup
kernel.class_eval do
alias __raise__ raise
[:to_s,:inspect,:=~,:!~,:===,:<=>,:eql?,:hash].each do |m|
undef_method m
end
private_instance_methods.each do |m|
if /\Ablock_given\?\z|iterator\?\z|\A__.*__\z/ =~ m
next
end
undef_method m
end
end
include kernel
# :stopdoc:
def self.const_missing(n)
::Object.const_get(n)
end
# :startdoc:
##
# :method: raise
# Use __raise__ if your Delegator does not have a object to delegate the
# raise method call.
#
#
# Pass in the _obj_ to delegate method calls to. All methods supported by
# _obj_ will be delegated to.
#
def initialize(obj)
__setobj__(obj)
end
#
# Handles the magic of delegation through \_\_getobj\_\_.
#
def method_missing(m, *args, &block)
r = true
target = self.__getobj__ {r = false}
if r && target.respond_to?(m)
target.__send__(m, *args, &block)
elsif ::Kernel.respond_to?(m, true)
::Kernel.instance_method(m).bind(self).(*args, &block)
else
super(m, *args, &block)
end
end
#
# Checks for a method provided by this the delegate object by forwarding the
# call through \_\_getobj\_\_.
#
def respond_to_missing?(m, include_private)
r = true
target = self.__getobj__ {r = false}
r &&= target.respond_to?(m, include_private)
if r && include_private && !target.respond_to?(m, false)
warn "#{caller(3)[0]}: delegator does not forward private method \##{m}"
return false
end
r
end
#
# Returns the methods available to this delegate object as the union
# of this object's and \_\_getobj\_\_ methods.
#
def methods(all=true)
__getobj__.methods(all) | super
end
#
# Returns the methods available to this delegate object as the union
# of this object's and \_\_getobj\_\_ public methods.
#
def public_methods(all=true)
__getobj__.public_methods(all) | super
end
#
# Returns the methods available to this delegate object as the union
# of this object's and \_\_getobj\_\_ protected methods.
#
def protected_methods(all=true)
__getobj__.protected_methods(all) | super
end
# Note: no need to specialize private_methods, since they are not forwarded
#
# Returns true if two objects are considered of equal value.
#
def ==(obj)
return true if obj.equal?(self)
self.__getobj__ == obj
end
#
# Returns true if two objects are not considered of equal value.
#
def !=(obj)
return false if obj.equal?(self)
__getobj__ != obj
end
#
# Delegates ! to the \_\_getobj\_\_
#
def !
!__getobj__
end
#
# This method must be overridden by subclasses and should return the object
# method calls are being delegated to.
#
def __getobj__
__raise__ ::NotImplementedError, "need to define `__getobj__'"
end
#
# This method must be overridden by subclasses and change the object delegate
# to _obj_.
#
def __setobj__(obj)
__raise__ ::NotImplementedError, "need to define `__setobj__'"
end
#
# Serialization support for the object returned by \_\_getobj\_\_.
#
def marshal_dump
ivars = instance_variables.reject {|var| /\A@delegate_/ =~ var}
[
:__v2__,
ivars, ivars.map{|var| instance_variable_get(var)},
__getobj__
]
end
#
# Reinitializes delegation from a serialized object.
#
def marshal_load(data)
version, vars, values, obj = data
if version == :__v2__
vars.each_with_index{|var, i| instance_variable_set(var, values[i])}
__setobj__(obj)
else
__setobj__(data)
end
end
def initialize_clone(obj) # :nodoc:
self.__setobj__(obj.__getobj__.clone)
end
def initialize_dup(obj) # :nodoc:
self.__setobj__(obj.__getobj__.dup)
end
private :initialize_clone, :initialize_dup
##
# :method: trust
# Trust both the object returned by \_\_getobj\_\_ and self.
#
##
# :method: untrust
# Untrust both the object returned by \_\_getobj\_\_ and self.
#
##
# :method: taint
# Taint both the object returned by \_\_getobj\_\_ and self.
#
##
# :method: untaint
# Untaint both the object returned by \_\_getobj\_\_ and self.
#
##
# :method: freeze
# Freeze both the object returned by \_\_getobj\_\_ and self.
#
[:trust, :untrust, :taint, :untaint, :freeze].each do |method|
define_method method do
__getobj__.send(method)
super()
end
end
@delegator_api = self.public_instance_methods
def self.public_api # :nodoc:
@delegator_api
end
end
##
# A concrete implementation of Delegator, this class provides the means to
# delegate all supported method calls to the object passed into the constructor
# and even to change the object being delegated to at a later time with
# #__setobj__.
#
# class User
# def born_on
# Date.new(1989, 9, 10)
# end
# end
#
# class UserDecorator < SimpleDelegator
# def birth_year
# born_on.year
# end
# end
#
# decorated_user = UserDecorator.new(User.new)
# decorated_user.birth_year #=> 1989
# decorated_user.__getobj__ #=> #<User: ...>
#
# A SimpleDelegator instance can take advantage of the fact that SimpleDelegator
# is a subclass of +Delegator+ to call <tt>super</tt> to have methods called on
# the object being delegated to.
#
# class SuperArray < SimpleDelegator
# def [](*args)
# super + 1
# end
# end
#
# SuperArray.new([1])[0] #=> 2
#
# 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
#
class SimpleDelegator<Delegator
# Returns the current object method calls are being delegated to.
def __getobj__
unless defined?(@delegate_sd_obj)
return yield if block_given?
__raise__ ::ArgumentError, "not delegated"
end
@delegate_sd_obj
end
#
# Changes the delegate object to _obj_.
#
# It's important to note that this does *not* cause SimpleDelegator's methods
# to change. Because of this, you probably only want to change delegation
# to objects of the same type as the original delegate.
#
# Here's an example of changing the delegation object.
#
# names = SimpleDelegator.new(%w{James Edward Gray II})
# puts names[1] # => Edward
# names.__setobj__(%w{Gavin Sinclair})
# puts names[1] # => Sinclair
#
def __setobj__(obj)
__raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj)
@delegate_sd_obj = obj
end
end
def Delegator.delegating_block(mid) # :nodoc:
lambda do |*args, &block|
target = self.__getobj__
target.__send__(mid, *args, &block)
end
end
#
# 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
#
# Here's a sample of use from Tempfile which is really a File object with a
# few special rules about storage location and 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
#
def DelegateClass(superclass)
klass = Class.new(Delegator)
methods = superclass.instance_methods
methods -= ::Delegator.public_api
methods -= [:to_s,:inspect,:=~,:!~,:===]
klass.module_eval do
def __getobj__ # :nodoc:
unless defined?(@delegate_dc_obj)
return yield if block_given?
__raise__ ::ArgumentError, "not delegated"
end
@delegate_dc_obj
end
def __setobj__(obj) # :nodoc:
__raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj)
@delegate_dc_obj = obj
end
methods.each do |method|
define_method(method, Delegator.delegating_block(method))
end
end
klass.define_singleton_method :public_instance_methods do |all=true|
super(all) - superclass.protected_instance_methods
end
klass.define_singleton_method :protected_instance_methods do |all=true|
super(all) | superclass.protected_instance_methods
end
return klass
end