Precisei criar uma classe de proxy que armazenaria em cache os resultados do método de um objeto porque não pude alterar sua implementação.
Ele tinha muitas operações caras que seriam feitas para cada chamada de método, e alguns desses métodos pesados seriam chamados internamente, e é por isso que precisei vir com uma solução “intrusiva”.
É importante observar que você provavelmente deseja evitar o uso de algo assim, provavelmente sempre preferirá projetar seu código melhor do que usar este hack . Além disso, é desnecessário dizer que isso causará problemas no caso de seus métodos não serem referencialmente transparentes ou produzirem efeitos colaterais.
class MethodCacheProxy
instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }
attr_reader :target, :cache, :invasive
def initialize(target, invasive = false)
@target = target
@cache = {}
@invasive = invasive
intercept_instance_methods if invasive
end
def method_missing(name, *args, &block)
cache_for(name, *args, &block)
end
def cache_for(name, *args, &block)
unless block_given?
cache[cache_key_for(name, args)] ||= target.send(name, *args)
else
target.send(name, *args, &block)
end
end
def cache_key_for(name, args)
"#{name}_#{args.hash}"
end
private
def intercept_instance_methods
instance_methods.each do |name|
override_method(name)
end
end
def instance_methods
target.class.instance_methods(false)
end
def instance_method(name)
target.class.instance_method(name)
end
def override_method(name)
cacher = self
method = instance_method(name)
target.define_singleton_method(name) do |*args, &block|
unless block.present?
cache_key = cacher.cache_key_for(name, args)
cacher.cache[cache_key] ||= method.bind(self).(*args, &block)
else
method.bind(self).(*args, &block)
end
end
end
end