Skip to content

Instantly share code, notes, and snippets.

@TylerRick
Last active March 29, 2020 06:01
Show Gist options
  • Save TylerRick/4990898 to your computer and use it in GitHub Desktop.
Save TylerRick/4990898 to your computer and use it in GitHub Desktop.
DelegateToAll. Like delegate.rb from Ruby's std lib but lets you have multiple target/delegate objects.
# 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<DelegateToAllClass(Array)
def initialize()
super(['a'], ['b'])
end
define_method(:to_s, DelegatorToAll.delegating_block(:to_s))
end
ary = ExtArray.new
p ary.class
ary.push 25
# Question: Why does it produce this ugly to_s output if we don't *explicitly* send to_s? ary=#<ExtArray:0x000000010677b8>
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
$:.unshift File.dirname(__FILE__)
require 'delegate_to_all'
class Tee < DelegateToAllClass(IO)
end
#---------------------------------------------------------------------------------------------------
# Test
if __FILE__ == $0
$stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a"))
$stderr = $stdout
# Test it out
puts '-'*100
puts 'stdout1'
$stderr.puts 'stderr1'
print "stderr2\n"
$stdout.puts 'stdout3'
$stderr.print "stderr4\n"
$stderr.print "stderr2\n"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment