Reputation: 303251
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?
Upvotes: 4
Views: 1648
Reputation: 303251
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}
Upvotes: 9
Reputation: 168101
At first, I thought params
is supposed to become empty when the :cmd
value is "items"
, in which case Jesse Sielaff's answer would be correct. But since you seem to be claiming that it isn't, I think that it is your design flaw. Instead of trying to dispatch in that way, you should rather have those methods just gobble the arguments:
def items(name:nil, data:nil)
...
end
Upvotes: 0