rubykeyword-argumentmethod-dispatch

Determine arity of method with keyword arguments


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?


Solution

  • 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}