Ruby Keyword Arguments of Method

ruby keyword arguments of method

Ruby doesn't actually have keyword arguments. Rails is exploiting a feature of Ruby which lets you omit the braces around a hash. For example, with find, what we're really calling is:

Person.find(:all, { :conditions => "...", :offset => 10, :limit => 10 } )

But if the hash is the last argument of the method, you can leave out the braces and it will still be treated as a hash:

Person.find(:all, :conditions => "...", :offset => 10, :limit => 10)

You can use this in your own methods:

def explode(options={})
defaults = { :message => "Kabloooie!", :timer => 10, :count => 1 }
options = defaults.merge(options)

options[:count].times do
sleep options[:timer]
puts options[:message]
end
end

And then call it:

explode :message => "Meh.", :count => 3

Or call it without an argument, resulting in all default values being used:

explode

Methods with optional and keyword arguments

If you want this exact signature, you should probably use double-splat for b parameter:

def test(a, c: 1, **b)
[a, b, c]
end
test 1, {hello: :world}
#⇒ [1, {hello: :word}, 1]

There are issues distinguishing named keyword parameters and the hash itself when the latter is passed immediately before named keywords.

Determine arity of method with keyword arguments

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}

How can Ruby functions with named arguments get called with a hash instead?

Just call the method with the hash: foo(hash)

The bigger problem: You didn't use named parameters (or better keyword arguments) but parameters with default values. To use named parameters you must not not use = but :.

def foo(x: 'hi', y:'this is y')
puts x
puts y
end

hash = {x: 'first', y: 'second'}
foo(hash)

How do I write a ruby method combining keyword arguments with hash?

The second works in current MRI and JRuby:

def entries(content_type:, **query)
puts query
end
entries(content_type: 3, baz: 4)
# => {:baz=>4}

The first one can't work because you can't both have keyword arguments and also automatically collect key-value pairs into a hash argument.

EDIT in response to comment:

If you wanted to pass a hash and not collect extra keywords into a hash, then you need to reverse the signature:

def entries(query={}, content_type:)
puts query
end
entries(content_type: 3)
# => {}
entries({ baz: 4 }, content_type: 3)
# => {:baz=>4}

Or, you can splat your hash:

def entries(content_type:, **query)
puts query
end
entries(content_type: 3, **{baz: 4})
# => {:baz=>4}

Or, you can make the second argument also into a keyword:

def entries(content_type:, query: {})
puts query
end
entries(content_type: 3)
# => {}
entries(content_type: 3, query: {baz: 4})
# => {:baz=>4}

Ruby keyword arguments clarification

Its just a keyword argument with an empty hash as the default value:

def initialize(arg: {})
arg
end

irb(main):011:0> initialize().class
=> Hash

Its really strange and unidiomatic though. Before Ruby 2.0 introduced first-class support for keywords you declared a method that takes an optional options hash as:

def initialize(hash = {})

end

This argument had to be at the end of the list of. The name is not significant.

With Ruby 2.0 you can declare a method that takes any number of keywords with a double splat:

def initialize(**other_keyword_args)

end

You can combine it with positional and named keyword arguments as well:

def initialize(a, b = 2, foo:, bar: 2, **other_keyword_args)

end

Using initialize(arg: {}, ...) would make sense if there where more parameters and this one takes a hash but on its own its just strange.

Collecting keyword arguments in Ruby

It's because the first parameter i, is a required parameter (no default value), so, the first value passed to the method (in your first example, this is the hash {a: 7, b: 8}) is stored into it.

Then, since everything else is optional, the remaining values (if any, in this example, there are none) are filled in, as applicable. IE, the second parameter will go to j, unless it is a named parameter, then it goes to kargs (or k). The third parameter, and any remaining--up until the first keyword argument, go into args, and then any keyword args go to kargs

def foo(i, j= 9, *args, k: 11, **kargs)
puts "i: #{i}; args: #{args}; kargs: #{kargs}"
end

foo(a: 7, b: 8)
# i: {:a=>7, :b=>8}; args: []; kargs: {}

Is there a nice way to repackage keyword args in Ruby?

Yes, **args will gather arbitrary keyword arguments as a Hash. Use ** again to flatten the Hash into keyword arguments for bar, Ruby 3 will no longer do this for you.

def foo(**bar_args)
# The ** is necessary in Ruby 3.
bar('var', **bar_args)
end

def bar(var, a:, b:, c:, d:, e:)
puts "#{var} #{a} #{b} #{c} #{d} #{e}"
end

This is appropriate if foo never uses those arguments, it just passes them along to bar. If foo were to use some arguments, those should be defined in foo.

def foo(a:, **bar_args)
puts "#{a} is for a"
bar('var', a: a, **bar_args)
end

def bar(var, a:, b:, c:, d:, e:)
puts "#{var} #{a} #{b} #{c} #{d} #{e}"
end


Related Topics



Leave a reply



Submit