Reputation: 719
I'm trying to implement lazy method execution in ruby. Let's say I have a class with two methods which should not be executed immediately after call
class Foo
lazy_evaluate :bar, :baz
def bar(string)
puts string
end
def baz(hash)
puts hash.inspect
end
end
f = Foo.new
f.bar('hello world') => nil
f.baz(hello: :world) => nil
f.run_lazy_methods =>
'hello world'
'{:hello=>:world}'
I wan't use this in my gem http://pastie.org/5137463
I'm asking for idea how to implement this behaviour
Upvotes: 2
Views: 218
Reputation: 168269
I found it difficult to make it possible to allow lazy_evaluate
before the corresponding method definitions. My implementation works when you put it after the corresponding definitions.
The preparation part is:
class Foo
def initialize
@queue = []
end
def run_lazy_methods
@queue.each{|proc| proc.call}
end
def self.lazy_evaluate *methods
methods.each do |method|
alias :"old_#{method}" :"#{method}"
define_method method do |*args, &pr|
@queue.push(->{send(:"old_#{method}", *args, &pr)})
end
end
end
end
Then, when you define the methods, and call lazy_evaluate
, they become lazy.
class Foo
def bar(string)
puts string
end
def baz(hash)
puts hash.inspect
end
lazy_evaluate :bar, :baz
end
And you will get the expected results.
f = Foo.new
f.bar('hello world')
f.baz(hello: :world)
f.run_lazy_methods
Upvotes: 0
Reputation: 35318
Use a delegate object, record the methods invoked onto a stack, then replay them on the delegate.
class LazyObject
def initialize(delegate)
@invocations = []
@delegate = delegate
end
def bar(*args, &block)
@invocations << {
method: :bar,
args: args,
block: block
}
end
def baz(*args, &block)
@invocations << {
method: :baz,
args: args,
block: block
}
end
def run_lazy_methods
@invocations.each do |inv|
@delegate.send(
inv[:method],
*inv[:args],
&inv[:block]
)
end
end
end
obj = LazyObject.new(RealObject.new)
obj.bar(hello: :world)
obj.baz("Hello World")
obj.run_lazy_methods
You could write the above better using method_missing
, but I wanted to make it clear ;)
Upvotes: 3