Skip to content

Instantly share code, notes, and snippets.

@forforf
Created March 4, 2011 22:30
Show Gist options
  • Save forforf/855828 to your computer and use it in GitHub Desktop.
Save forforf/855828 to your computer and use it in GitHub Desktop.
A class that can delegate class and instance methods to multiple delegates
#classes to delegate to
#############
class A
class << self; attr_accessor :_me; end
attr_accessor :me
def initialize
@me = :aa
end
def self._i_am
:A
end
def i_am
#sleep 2
:a
end
end
class B
class << self; attr_accessor :_me; end
attr_accessor :me
def initialize
@me = :bb
end
def self._i_am
:B
end
def self._i_am_not
[:A, :C]
end
def i_am
#sleep 1
:b
end
def i_am_not
[:a, :c]
end
end
class C
class << self; attr_accessor :_me; end
attr_accessor :me
def initialize
@me = :cc
end
def self._i_am
:C
end
def self._i_am_not
[:A, :B]
end
def self._next
:D
end
def i_am
:c
end
def i_am_not
[:a, :b]
end
def next
:d
end
end
################
class Forwarder
attr_accessor :proxies, :valid_response_trace, :invalid_response_list
def initialize(proxies)
@proxies = proxies
end
#execute forwarding in an iterative fashion
IterativeBlock = lambda{ |proxies, m, *args, &block|
all_resps = {}
proxies.each do |proxy|
this_resp = {}
begin
#collect valid responses
all_resps[:valid] ||= {}
all_resps[:valid][proxy] = proxy.__send__(m, *args, &block)
rescue NoMethodError
#oops we have some invalid responses, collect those too
all_resps[:invalid] ||= []
all_resps[:invalid] << proxy
end
end
all_resps
}
#If we like speed (with a dash of danger) we can thread the requests rather than iterate
ThreadedBlock= lambda{ |proxies, m, *args, &block|
all_resps = {}
threads = []
proxies.each do |proxy|
threads << Thread.new do
begin
Thread.current[:resp] = proxy.__send__(m, *args, &block)
rescue
Thread.current[:err] = proxy
end
end
threads.each do |t|
t.join
if t[:resp]
#valid response
all_resps[:valid] ||= {}
all_resps[:valid][proxy] = t[:resp]
elsif t[:err]
#oops error
all_resps[:invalid] ||= []
all_resps[:invalid] << t[:err]
else
raise "Proxy returned an invalid responseto its thread."\
"Response thread: #{t} Proxy: #{proxy.inspect}"
end
end
end
all_resps
}
#More speed (with more danger) we can use the first valid response (note the change in t.join)
#How to react to the first response, maybe fibers?
ThreadedBlock= lambda{ |proxies, m, *args, &block|
#send method to all
#respond on first response
#let other methods respond as well (but main program has moved on
#I don't want to kill threads because that will create indertiminate state
}
def forward_to_proxy(call_block, m, *args, &block)
@valid_response_trace = {}
@invalid_response_list = []
proxies = @proxies
if proxies.size > 0
resp = call_block.call(proxies, m, *args, &block)
@valid_response_trace = resp[:valid]
else
raise "No Proxies assigned"
end
@valid_response_trace
end
def forward_iterative(m, *args, &block)
forward_to_proxy(IterativeBlock, m, *args, & block)
end
def forward_threaded(m, *args, &block)
forward_to_proxy(ThreadedBlock, m, *args, & block)
end
end
class MultiProxy
#unload these methods so the proxy object will handle them
[:to_s,:inspect,:=~,:!~,:===].each do |m|
undef_method m
end
#proxied_classes is the container the classes that will be proxied
class << self
#unload these methods so the proxy class will handle them
[:to_s,:inspect,:=~,:!~,:===].each do |m|
undef_method m
end
attr_accessor :class_forwarder, :class_proxy_methods, :all_proxied_class_methods
def proxied_classes=(classes_to_proxy)
@proxy_classes = classes_to_proxy || []
@class_proxy_methods = {}
@proxies = classes_to_proxy
@proxies.each do |proxy|
proxy.methods.each do |proxy_method|
if @class_proxy_methods[proxy_method]
@class_proxy_methods[proxy_method] << proxy
else
@class_proxy_methods[proxy_method] = [proxy]
end
end
end
@all_proxied_class_methods = @class_proxy_methods.keys
@class_forwarder = Forwarder.new(@proxy_classes) if @proxy_classes.size > 0
end
def proxied_classes
@proxy_classes
end
end
MultiProxy.proxied_classes = []
def self.method_missing(m, *args, &block)
if MultiProxy.all_proxied_class_methods.include? m
resp = MultiProxy.class_forwarder.forward_iterative(m, *args, &block)
#resp.values
else
raise NoMethodError, "MultiProxy can't find the class method #{m} in any of its proxies"
end
end
attr_accessor :proxied_objects, :proxied_methods
def initialize(init_params = {})
@proxied_objects = []
@proxied_methods = {}
raise "No Proxied Classes set" unless MultiProxy.proxied_classes.size > 0
MultiProxy.proxied_classes.each do |proxy_class|
proxied_obj = proxy_class.new #(init_params)
proxied_obj.methods.each do |proxy_method|
if @proxied_methods[proxy_method]
@proxied_methods[proxy_method] << proxied_obj
else
@proxied_methods[proxy_method] = [proxied_obj]
end
end
@proxied_objects << proxied_obj
end
@obj_forwarder = Forwarder.new(@proxied_objects)
end
def method_missing(m, *args, &block)
if @proxied_methods.include? m
resp = @obj_forwarder.forward_iterative(m, *args, &block)
#resp.values
else
raise NoMethodError, "MultiProxy object can't find the method #{m} in any of its proxies"
end
end
end
#TESTING
MultiProxy.proxied_classes = [A, B, C]
p MultiProxy.class_proxy_methods
puts "--"
p MultiProxy.to_s
p MultiProxy._i_am_not.values
#p MultiProxy.foo
puts "--"
x = MultiProxy.new
#p x.to_s
p x.i_am_not.values
p x.foo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment