4

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?

Phrogz
  • 296,393
  • 112
  • 651
  • 745

2 Answers2

9

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}
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • 3
    This. `arity` has been obscure ever since the introduction of optional parameters with default arguments, became obsolete with the introduction of `parameters` and is now completely useless after the introduction of keyword parameters. – Jörg W Mittag Aug 02 '15 at 09:43
0

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
sawa
  • 165,429
  • 45
  • 277
  • 381
  • There are currently over 20 different methods planned, with varying arguments. I don't want to stub out the superset of all unused arguments. Instead, I want to pass along arguments when appropriate, and not pass them along when the method doesn't need them. With `parameters`, I can now pare down the set of commands to those that match. – Phrogz Aug 02 '15 at 14:08