Pass Ruby 2 keyword arguments without specifying them explicitly - ruby

How I can pass the keyword arguments to a call to another method in my code without specifying each of them.
Consider the following method with the new first-class support for kwargs:
def foo(obj, bar: 'a', baz: 'b')
another_method(bar, baz)
end
Consider the old Ruby options hash syntax:
def foo(obj, options = {})
another_method(options)
end
I want to pass the keyword arguments without specifying each of them explicitly as with the case of the old options hash syntax. Is that even possible?

You can use the double-splat operator:
def a(q: '1', w: '2')
p q
p w
end
def b(**options)
a(options)
end
b
# => 1
# => 2
b(q: '3', w: '4')
# => 3
# => 4

Keyword arguments work similarly to your "old Ruby options hash syntax". A hash's keys will substitute as keywords and their values as arguments. This should work fine:
def foo(obj, bar: 'a', baz: 'b')
another_method(bar, baz)
end
arg_hash = { bar: 'alt bar', baz: 'alt baz' }
foo(Object.new, arg_hash)
Some things to be aware of
If your hash keys are strings, such as { 'bar' => 'alt bar', 'baz' => 'alt baz' }, this won't work. You'll get an ArgumentError: wrong number of arguments (2 for 1) error. This is easy enough to fix in Rails by calling .symbolize_keys on the hash. If you're not in Rails, you'll have to convert the keys manually or reimplement that method.
Also, in Rails, a HashWithIndifferentAccess (such as the params hash in a controller), although it has both strings and symbols for keys, can't be used in this way. If you pass params, it will look at the string keys instead and you'll get the same error as above. I'm not sure why this is the case, but my guess it's because the params hash starts with strings and adds symbols so the symbols are secondary and not the default. Total guess on that though.

Related

Call a Ruby method explicitly using the default for a parameter

An unfortunately large number of methods are written in the following form:
def my_method(foo = {}, bar = {})
# Do stuff with foo and bar
end
While I appreciate not having to write my_method({}, {}) everywhere I reference the method, using something other than the default for the second parameter makes me use something other than the default for the first parameter too - my_method({}, foo: 'bar').
Is there a way to tell Ruby to use the default for a parameter when other, later parameters need to use something other than the default? I'm hoping for something in the form of my_method(__default__, foo: 'bar'), but would welcome any other solutions that address the core of this problem.
This would be particularly useful when APIs undergo minor (but significant) changes. It can lead to hard to find bugs occasionally:
# Original method
def foo(value = 'true', options = {})
# ...
end
# Defaults are updated slightly in a new version of the library
def foo(value = true, options = {})
# ...
end
# My code might break in an unexpected way now
foo('true', bar: 'baz')
This is the problem which keyword arguments (new to ruby 2) were made to solve (provided that you control the method definition).
def foo(a: {}, b: {})
"a: #{a}, b: #{b}"
end
foo # => "a: {}, b: {}"
foo(a: 1, b: 2) # => "a: 1, b: 2"
foo(a: 3) # => "a: 3, b: {}"
foo(b: 4) # => "a: {}, b: 4"
You could set defaults to nil then handle the actual defaulting of values within the body of the method. ie.,
def my_method(first=nil, second=nil)
first_value = first || 1
second_value = second || 2
end
This allows you to pass 'nil' when you want that value to be its default. For example,
my_method(nil, 'second')
Now 'first_value' is 1 and second_value is 'second'.
edit: though 'nil' is really non-descriptive of the action of making the method use its default value. Consider:
def my_method(first=:default, second=:default)
first_value = (first == :default ? 1 : first)
second_value = (second== :default ? 2 : second)
end
Then you can do:
my_method(:default, 'second')
(but really Sergio's answer is the best =) )
You can just refactor the code to something like this, so it gets assigned to the default value only if the named parameter isn't provided a value.
def my_method(foo, bar)
foo ||= {}; bar ||= {};
#=> Do something with foo and bar now.
end
What ||= operator does is, it assigns the value on the right to the variable on the left if the variable isn't initialized or has nil value.
You can now call it like this
my_method(nil, "I'm bar");
If by any chance, you want to pass nil as a value, then this will fail. Sergio Tulentsev's answer is the way to go. I'd have suggested the same had I known it.

Set optional parameter put the others with a default value at nil

I face a weirb problem with optionals parameters in ruby.
This is my code :
def foo options={:test => true}
puts options[:test]
end
foo # => puts true
foo :lol => 42 # => puts nil
I can not figure out why the second call puts nil.
Is seems that putting an other parameter set :test to nil.
Thanks.
It happens because if it is a default parameter, passing a hash parameter will completely overwrite it (ie. it sets options = {:lol => 42}), so the options[:test] key no longer exists.
To give particular hash keys default values, try:
def foo options={}
options = {:test => true}.merge options
puts options[:test]
end
In this case, we merge a hash with default values for certain keys ({:test => true}), with another hash (containing the key=>values in the argument). If a key occurs in both hash objects, the value in the hash passed to the merge function will take precedence.

ruby keyword arguments of method

How can I declare a method with keyword arguments just like rails do. some examples may be
Person.find(:all, :conditions => "...").
How can I use symbols to create methods similar to the above?
I am very new to ruby. Thanks in advance!
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
Since Ruby 2.0, ruby does have keyword arguments.
def my_method(arg1, name: 'defaultName', number: 0)
puts arg1, name, number
end
I agree with accepted answer given by Samir Talwar and christopherwright. The only potential downside is that you get no warnings if you use an incorrect keyword symbol as an argument or when looking up an option, it just ends up ignored. If that's something you're concerned about, the gem hash_keyword_args addresses it. The idiom would be
def explode(opts={})
opts = opts.keyword_args(:message => "Kabloooie!", :timer => 10, :count => 1)
opts.count.times do
sleep opts.timer
puts opts.message
end
end
Notice the use of accessor methods so you'll get a NoMethodError if you mistype a keyword. And the calling behavior is:
explode(:message => "Okay") # works
explode(:msg => "Oops") # raises ArgumentError
The gem also provides a few other features you might or might not care about, such as being able to indicate that a keyword is required. I've been using it happily for a while now.
(Disclaimer: I'm the author of the gem.)
You just need to define a method where one of the parameters is a hash. It's actually pretty simple.
def method(arg1, params)
name = params[:name]
number = params[:number]
And then call it like:
method(arg1, :name => 'Eric', :number => 2)
Two notes:
In Ruby, you don't need to surround the parameters hash in {} when you call the method in most cases, unless you have something complicated going on like passing multiple hashes. In that case, make sure you surround those parameters with {}
Ruby is dynamically typed, so you don't need to say that params is a hash when you define the method.
Ruby 2.0 introduced real keyword arguments, and Ruby 2.1 added required keyword arguments.
There's a nice article up at https://chriszetter.com/blog/2012/11/02/keyword-arguments-in-ruby-2-dot-0/ on this, I've borrowed the examples from there:
Ruby 2.0+:
def exclaim(text, exclamation: '!', number: 7)
text + exclamation * number
end
exclaim('hello', number: 4) #=> 'hello!!!!'
# equivalent:
exclaim('hello', {:number => 4}) #=> 'hello!!!!'
Ruby 2.1+:
def exclaim(text, exclamation: '!', number:)
text + exclamation * number
end
exclaim('Yo', number: 5) # => 'Yo!!!!!'
exclaim('Yo') # raises: ArgumentError: missing keyword: number
Since Ruby is typed dynamically, just do :
def my_method(arg1, arg2)
#things
end
example:
my_method(:test, {:somehash => "yay"})
or
my_method :test, :somehash => "yay"
or
my_method(:test, :somehash => "yay")

Ruby multiple named arguments

I'm very new to ruby and I'm trying to write a web application using the rails framework. Through reading I've seen methods being called like this:
some_method "first argument", :other_arg => "value1", :other_arg2 => "value2"
Where you can pass an unlimited number of arguments.
How do you create a method in ruby that can be used in this way?
Thanks for the help.
That works because Ruby assumes the values are a Hash if you call the method that way.
Here is how you would define one:
def my_method( value, hash = {})
# value is requred
# hash can really contain any number of key/value pairs
end
And you could call it like this:
my_method('nice', {:first => true, :second => false})
Or
my_method('nice', :first => true, :second => false )
This is actually just a method that has a hash as an argument, below is a code example.
def funcUsingHash(input)
input.each { |k,v|
puts "%s=%s" % [k, v]
}
end
funcUsingHash :a => 1, :b => 2, :c => 3
Find out more about hashes here http://www-users.math.umd.edu/~dcarrera/ruby/0.3/chp_03/hashes.html
Maybe that *args can help you?
def meh(a, *args)
puts a
args.each {|x| y x}
end
Result of this method is
irb(main):005:0> meh(1,2,3,4)
1
--- 2
--- 3
--- 4
=> [2, 3, 4]
But i prefer this method in my scripts.
You can make the last argument be an optional hash to achieve that:
def some_method(x, options = {})
# access options[:other_arg], etc.
end
However, in Ruby 2.0.0, it is generally better to use a new feature called keyword arguments:
def some_method(x, other_arg: "value1", other_arg2: "value2")
# access other_arg, etc.
end
The advantages of using the new syntax instead of using a hash are:
It is less typing to access the optional arguments (e.g. other_arg instead of options[:other_arg]).
It is easy to specify a default value for the optional arguments.
Ruby will automatically detect if an invalid argument name was used by the caller and throw an exception.
One disadvantage of the new syntax is that you cannot (as far as I know) easily send all of the keyword arguments to some other method, because you don't have a hash object that represents them.
Thankfully, the syntax for calling these two types of methods is the same, so you can change from one to the other without breaking good code.

hash['key'] to hash.key in Ruby

I have a a hash
foo = {'bar'=>'baz'}
I would like to call foo.bar #=> 'baz'
My motivation is rewriting an activerecord query into a raw sql query (using Model#find_by_sql). This returns a hash with the SELECT clause values as keys. However, my existing code relies on object.method dot notation. I'd like to do minimal code rewrite. Thanks.
Edit: it appears Lua has this feature:
point = { x = 10, y = 20 } -- Create new table
print(point["x"]) -- Prints 10
print(point.x) -- Has exactly the same meaning as line above
>> require 'ostruct'
=> []
>> foo = {'bar'=>'baz'}
=> {"bar"=>"baz"}
>> foo_obj = OpenStruct.new foo
=> #<OpenStruct bar="baz">
>> foo_obj.bar
=> "baz"
>>
What you're looking for is called OpenStruct. It's part of the standard library.
A good solution:
class Hash
def method_missing(method, *opts)
m = method.to_s
if self.has_key?(m)
return self[m]
elsif self.has_key?(m.to_sym)
return self[m.to_sym]
end
super
end
end
Note: this implementation has only one known bug:
x = { 'test' => 'aValue', :test => 'bar'}
x.test # => 'aValue'
If you prefer symbol lookups rather than string lookups, then swap the two 'if' condition
Rather than copy all the stuff out of the hash, you can just add some behaviour to Hash to do lookups.
If you add this defintion, you extend Hash to handle all unknown methods as hash lookups:
class Hash
def method_missing(n)
self[n.to_s]
end
end
Bear in mind that this means that you won't ever see errors if you call the wrong method on hash - you'll just get whatever the corresponding hash lookup would return.
You can vastly reduce the debugging problems this can cause by only putting the method onto a specific hash - or as many hashes as you need:
a={'foo'=>5, 'goo'=>6}
def a.method_missing(n)
self[n.to_s]
end
The other observation is that when method_missing gets called by the system, it gives you a Symbol argument. My code converted it into a String. If your hash keys aren't strings this code will never return those values - if you key by symbols instead of strings, simply substitute n for n.to_s above.
There are a few gems for this. There's my recent gem, hash_dot, and a few other gems with similar names I discovered as I released mine on RubyGems, including dot_hash.
HashDot allows dot notation syntax, while still addressing concerns about NoMethodErrors addressed by #avdi. It is faster, and more traversable than an object created with OpenStruct.
require 'hash_dot'
a = {b: {c: {d: 1}}}.to_dot
a.b.c.d => 1
require 'open_struct'
os = OpenStruct.new(a)
os.b => {c: {d: 1}}
os.b.c.d => NoMethodError
It also maintains expected behavior when non-methods are called.
a.non_method => NoMethodError
Please feel free to submit improvements or bugs to HashDot.

Resources