I have several methods with keyword parameters which call other methods with the same parameters. Currently I have to pass each parameter manually. Is there a way I can access all the keyword parameters as a Hash and pass that down directly?
Sample code -
def method1(arg1:, arg2:)
# do something specific to method1
result = executor1(arg1: arg1, arg2: arg2)
# do something with result
end
def method2(arg3:, arg4:, arg5:, arg6:)
# do something specific to method2
result = executor2(arg3: arg3, arg4: arg4, arg5: arg5, arg6: arg6)
# do something with result
end
def method3(arg7:)
# do something specific to method3
result = executor3(arg7: arg7)
# do something with result
end
Can I do something which would change the code to -
def method1(arg1:, arg2:)
# do something specific to method1
args = method1_args_as_a_hash
result = executor1(args)
# do something with result
end
def method2(arg3:, arg4:, arg5:, arg6:)
# do something specific to method2
args = method2_args_as_a_hash
result = executor2(args)
# do something with result
end
def method3(arg7:)
# do something specific to method3
args = method3_args_as_a_hash
result = executor3(args)
# do something with result
end
Context - The number of these keyword arguments has grown quite large in my codebase and passing them as is (or sometimes with slight modifications) to executorX
methods is making the code file too big and difficult to read. I unfortunately cannot change the signature of methodX
methods since I don't have access to every codebase they are used in and also cannot risk breaking any of their consumers. I do have full control over their logic and over executorX
methods. My aim is to refactor this code to reduce the number of lines and improve the readability.
Thanks!
It can be done but is pretty clunky.
You can instrospect on a method with the method
method and get it's signature and then use binding
to get the local variables with the same name:
def foo(bar:, baz:)
params = method(__method__).parameters # [[:keyreq, :bar], [:keyreq, :baz]]
params.each_with_object({}) do |(_, name), hash|
hash[name] = binding.local_variable_get(name)
end
end
irb(main):025:0> foo(bar: 1, baz: 2)
=> {:bar=>1, :baz=>2}
__method__
is a special magic method that contains the name of the current method.
Just beware that extracting that if you want to avoid repeating this all over your code base that you would need to pass the binding object to your other method as well as the name of the method.
module Collector
# @param [Binding] context
# @param [String|Symbol] method_name
# @return [Hash]
def collect_arguments(context, method_name)
params = context.reciever.method(method_name).parameters
params.each_with_object({}) do |(_, name), hash|
hash[name] = context.local_variable_get(name)
end
end
end
class Thing
include Collector
def method1(arg1:, arg2:)
# do something specific to method1
args = collect_arguments(binding, __method__)
result = executor1(**args)
# do something with result
end
end