Handling keyword arguments in method_missing in Ruby


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:

  1. "test_block.rb"
  2. "test_knock.rb"
  3. {: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:

  1. 'test_block.rb'
  2. 'test_knock.rb'

…and kwargs is a hash with {:preserve=>true} as the value, which the ** will translate back to proper keyword arguments.


Leave a Reply