Last active
March 29, 2020 06:01
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$:.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