I am developing a Ruby application where I am dynamically invoking methods based on JSON data. Loosely:
def items
# do something
end
def createItem( name:, data:nil )
# do something that requires a name keyword argument
end
def receive_json(json) # e.g. { "cmd":"createItem", "name":"jim" }
hash = JSON.parse(json)
cmd = hash.delete('cmd')
if respond_to?(cmd)
params = Hash[ hash.map{ |k,v| [k.to_sym, v } ]
method(cmd).arity==0 ? send(cmd) : send(cmd,params)
end
end
As shown above, some methods take no arguments, and some take keyword arguments. Under Ruby 2.1.0 (where I'm developing) the arity
of both methods above is 0
. However, if I send(cmd,params)
always, I get an error for methods that take no parameters.
How can I use send
to correctly pass along the keyword arguments when desired, but omit them when not?
Using parameters
instead of arity
appears to work for my needs:
method(cmd).parameters.empty? ? send(cmd) : send(cmd,opts)
More insight into the richness of the parameters
return values:
def foo; end
method(:foo).parameters
#=> []
def bar(a,b=nil); end
method(:bar).parameters
#=> [[:req, :a], [:opt, :b]]
def jim(a:,b:nil); end
method(:jim).parameters
#=> [[:keyreq, :a], [:key, :b]]
Here's a generic method that picks out only those named values that your method supports, in case you have extra keys in your hash that aren't part of the keyword arguments used by the method:
module Kernel
def dispatch(name,args)
keyargs = method(name).parameters.map do |type,name|
[name,args[name]] if args.include?(name)
end.compact.to_h
keyargs.empty? ? send(name) : send(name,keyargs)
end
end
h = {a:1, b:2, c:3}
def no_params
p :yay
end
def few(a:,b:99)
p a:a, b:b
end
def extra(a:,b:,c:,z:17)
p a:a, b:b, c:c, z:z
end
dispatch(:no_params,h) #=> :yay
dispatch(:few,h) #=> {:a=>1, :b=>2}
dispatch(:extra,h) #=> {:a=>1, :b=>2, :c=>3, :z=>17}