If you implement a method_missing method to receive methods that receive keyword arguments and you only receive and forward (to send) the method name, *args, and &block, you will receive ArgumentError: wrong number of arguments (given 3, expected 2) when the arguments are forwarded.
This is because the keyword arguments will revert to the backward-compatible handling of what are “keyword arguments” today as the historical “implicit hash”.
In the below code for Original::FileUtils.cp, the args variable will be["test_block.rb", "test_knock.rb", {:preserve=>true}], which will mean *3* arguments will be passed to the method invoked by send:
"test_block.rb""test_knock.rb"{:preserve => true}
module Original
class FileUtils
class << self
def method_missing(m, *args, &block)
::FileUtils.send(m, *args, &block)
end
end
end
end
module Fixed
class FileUtils
class << self
def method_missing(m, *args, **kwargs, &block)
::FileUtils.send(m, *args, **kwargs, &block)
end
end
end
end
original_pathname='test_block.rb'
backup_pathname='test_knock.rb'
begin
Original::FileUtils.cp(original_pathname, backup_pathname, preserve: true)
rescue ArgumentError => e
puts "Original failed with #{e.inspect}"
end
Fixed::FileUtils.cp(original_pathname, backup_pathname, preserve: true)
Instead, what we what is to capture the keyword arguments in its own array to reforward as keyword arguments to via send. In the Fixed:FileUtils method_missing, args is just an array of the positional arguments:
'test_block.rb''test_knock.rb'
…and kwargs is a hash with {:preserve=>true} as the value, which the ** will translate back to proper keyword arguments.