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