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
Where to Put a Before_Filter Shared Between Multiple Controllers
Converting String "2½" (Two and a Half) into 2.5
Paperclip File Not Found Error
How to Delete a Range of Values from an Array
How to Make Xcode Use the Correct Version of Ruby When Running a Script
Pg_Config, Ruby Pg, Postgresql 9.0 Problem After Upgrade, Centos 5
Database Cleaner Not Working in Minitest Rails
Find Number of Bytes a Particular Hash Is Using in Ruby
Getaddrinfo Error with Mechanize
Openssl Trouble with Ruby 1.9.3
How to Integrate 'Premailer' with Rails
Ruby on Rails Map.Root Doesn't Seem to Be Working
Catching Timeout Errors with Ruby Mechanize
Spacing Around Parentheses in Ruby
In Rails, How to Access Response.Body in a Action Before It Returns