module Tap
module Support
# BatchableClass encapsulates class methods related to Batchable.
module BatchableClass
# Merges the batches for the specified objects. All objects
# sharing the individual object batches will be affected, even
# if they are not listed explicitly as an input.
#
# t1 = Tap::Task.new
# t2 = Tap::Task.new
# t3 = t2.initialize_batch_obj
#
# Tap::Task.batch(t1, t2)
# t3.batch # => [t1,t2,t3]
#
# Returns the new batch.
def batch(*batchables)
merged = []
batches = batchables.collect {|batchable| batchable.batch }.uniq
batches.each do |batch|
merged.concat(batch)
batch.clear
end
merged.uniq!
batches.each {|batch| batch.concat(merged) }
merged
end
protected
# Redefines the specified method(s) as batched methods. The existing method
# is renamed as unbatched_method and method redefined to
# call unbatched_method on each object in the batch.
#
# def process(one, two)
# ...
# end
# batch_function(:process)
#
# Is equivalent to:
#
# def unbatched_process(one, two)
# ...
# end
#
# def process(one, two)
# batch.each do |t|
# t.unbatched_process(one, two)
# end
# self
# end
#
# The batched method will accept/pass as many arguments as are defined for
# the unbatched method. Splats are supported, and blocks are supported too
# by passing a block to batch_function:
#
# def process(arg, *args, &block)
# ...
# end
# batch_function(:process) {}
#
# Is equivalent to:
#
# def unbatched_process(arg, *args, &block)
# ...
# end
#
# def process(*args, &block)
# batch.each do |t|
# t.unbatched_process(*args, &block)
# end
# self
# end
#
# Obviously there are limitations to batch_function, most notably batching
# functions with default values. In these cases, batch functionality
# must be implemented manually.
def batch_function(*methods)
methods.each do |method_name|
unbatched_method = "unbatched_#{method_name}"
if method_defined?(unbatched_method)
raise "unbatched method already defined: #{unbatched_method}"
end
arity = instance_method(method_name).arity
args = case
when arity < 0 then "*args"
else Array.new(arity) {|index| "arg#{index}" }.join(", ")
end
args += ", &block" if block_given?
class_eval %Q{
alias #{unbatched_method} #{method_name}
def #{method_name}(#{args})
batch.each do |t|
t.#{unbatched_method}(#{args})
end
self
end
}
end
end
end
end
end