Reputation: 51
I am trying to figure if there's a way of using send for calling a method with dynamic arguments (*args, **kwrags) while the method has only default arguments.
Given the following class:
class Worker
def perform(foo='something')
# do something
end
end
I have a module which needs to check some conditions. If the conditions are not met, the #perform method should run:
module SkippableWorker
def self.included(base)
base.class_eval do
alias_method :skippable_perform, :perform
define_method :perform do |*args, **keyword_args|
return if skip?
self.send(:skippable_perform, *args, **keyword_args)
end
define_method :skip? do
# checks whether to skip or not
end
end
end
end
Once including the module in the Worker class, I encounter a problem in the module's send command:
self.send(:skippable_perform, *args, **keyword_args)
If once wants to call #perform and use the default arguments, the call will be made without any arguments at all:
Worker.new.perform()
But the send command in the module translates the *args and **kwargs to [] and {}. Thus, the call to #perform ends up to be send(:perform, [], {}) which causes ArgumentError for args amount mismatch or for the very least, sends a value one did not mean to send ([] instead of the default 'something').
Any ideas how to overcome this?
Upvotes: 3
Views: 3034
Reputation: 54233
The best explanation I found was here.
**{}
is not parsed in the same way as **kwargs
, even if kwargs
is an empty hash.
One simple solution would be to check if kwargs
is empty before calling skippable_perform
.
The complete code becomes :
module SkippableWorker
def self.included(base)
base.class_eval do
alias_method :skippable_perform, :perform
define_method :perform do |*args, **keyword_args|
return if skip?
if keyword_args.empty?
self.send(:skippable_perform, *args)
else
self.send(:skippable_perform, *args, **keyword_args)
end
end
define_method :skip? do
puts self.class
if rand > 0.5
puts " Skipping"
true
end
end
end
end
end
class Worker
def perform(foo='standard arg')
puts " #{foo}"
end
include SkippableWorker
end
class AnotherWorker
def perform(bar:'standard kwarg')
puts " #{bar}"
end
include SkippableWorker
end
Worker.new.perform
Worker.new.perform
Worker.new.perform('another arg')
Worker.new.perform('another arg')
AnotherWorker.new.perform
AnotherWorker.new.perform
AnotherWorker.new.perform(bar: 'another kwarg')
AnotherWorker.new.perform(bar: 'another kwarg')
Depending on rand
, it could output :
Worker
standard arg
Worker
Skipping
Worker
Skipping
Worker
another arg
AnotherWorker
standard kwarg
AnotherWorker
standard kwarg
AnotherWorker
Skipping
AnotherWorker
another kwarg
Another method would be to check which parameters are expected by skippable_perform
:
method(:skippable_perform).parameters.any?{|type,name| type == :key}
and adapt define_method
depending on the result.
Upvotes: 3