# DelegateToAll. Like delegate.rb from Ruby's std lib but lets you have multiple target/delegate objects. require 'delegate' class DelegatorToAll < Delegator # Pass in the _obj_ to delegate method calls to. All methods supported by # _obj_ will be delegated to. # def initialize(*targets) __setobj__(targets) end # Handles the magic of delegation through \_\_getobj\_\_. # # If *any* of the targets respond to the message, then we send the message to all targets that do # respond to it. The return value is an array of the return value from each target method that was # invoked. # # Otherwise (no targets respond), we send it to super. # def method_missing(m, *args, &block) targets = self.__getobj__ begin valid_targets = targets.select {|_| _.respond_to?(m) } valid_targets.any? ? valid_targets.map {|_| _.__send__(m, *args, &block) } : super(m, *args, &block) ensure $@.delete_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if $@ end end # TODO: #def respond_to_missing?(m, include_private) # # Returns the methods available to this delegate object as the union # of each target's methods and \_\_getobj\_\_ methods. # def methods __getobj__.inject([]) {|array, obj| array | obj.methods } | super end def public_methods(all=true) __getobj__.inject([]) {|array, obj| array | obj.public_methods(all) } | super end def protected_methods(all=true) __getobj__.inject([]) {|array, obj| array | obj.protected_methods(all) } | super end # # Returns true if two objects are considered of equal value. # def ==(obj) return true if obj.equal?(self) __getobj__.any? {|_| _ == obj } end # # Returns true if two objects are not considered of equal value. # def !=(obj) return false if obj.equal?(self) __getobj__.any? {|_| _ != obj } end def ! #!__getobj__ __getobj__.map {|_| !_ } end # TODO: #def marshal_dump #def marshal_load(data) #def initialize_clone(obj) # :nodoc: #def initialize_dup(obj) # :nodoc: end #=================================================================================================== # This is identical to SimpleDelegator except for its superclass. class SimpleDelegatorToAll < DelegatorToAll # Returns the current object method calls are being delegated to. def __getobj__ @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 DelegatorToAll.delegating_block(message) lambda do |*args, &block| targets = self.__getobj__ begin # We loop through targets and use map to make sure we return the return value from all # targets. targets.map do |target| target.__send__(message, *args, &block) end #.tap {|ret_value| puts %(ret_value=#{(ret_value).inspect}) } ensure $@.delete_if {|t| /\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:/o =~ t} if $@ end end end #=================================================================================================== def DelegateToAllClass(superclass) klass = Class.new(DelegatorToAll) # Delegate all instance methods from superclass to the target objects returned by __getobj__ methods = superclass.instance_methods methods -= ::Delegator.public_api methods -= [:to_s,:inspect,:=~,:!~,:===] klass.module_eval do def __getobj__ # :nodoc: @delegate_dc_obj end def __setobj__(obj) # :nodoc: raise ArgumentError, "cannot delegate to self" if self.equal?(obj) @delegate_dc_obj = obj end #puts %(methods=#{(methods).sort.inspect}) methods.each do |method| define_method(method, DelegatorToAll.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 #=================================================================================================== if __FILE__ == $0 class ExtArray puts %(ary=#{(ary)}) puts %(ary=#{(ary.to_s)}) ary.push 42 puts %(ary=#{(ary.to_s)}) #puts %(ary.methods=#{(ary.methods).sort.inspect}) #------------------------------------------------------------------------------------------------- # SimpleDelegatorToAll test foo = Object.new def foo.test 25 end def foo.iter yield self end def foo.error raise 'this is OK' end foo2 = SimpleDelegatorToAll.new(foo, foo) p foo2 foo2.instance_eval{print "foo\n"} puts %(foo == foo2=#{(foo == foo2).inspect}) # => false puts %(foo2 == foo=#{(foo2 == foo).inspect}) # => true puts %(foo2 != foo=#{(foo2 != foo).inspect}) # => false puts %(foo2.test.include? foo.test=#{(foo2.test.include? foo.test).inspect}) # => true puts %(foo2.iter{[55,true]}=#{(foo2.iter{[55,true]}).inspect}) # => [55,true] #foo2.error # raise error! false_true_delegator = SimpleDelegatorToAll.new(false, true) puts %(!false_true_delegator=#{(!false_true_delegator).inspect}) # [true, false] end