Methods with optional and keyword arguments - ruby

def test(a, b = {}, c: 1)
[a, b, c]
end
test 1, {hello: :world}
I would expect the output of this to be:
[1, {hello: :word}, 1]
But instead I got:
ArgumentError: unknown keyword: hello
I've read some pages like https://makandracards.com/makandra/36011-ruby-do-not-mix-optional-and-keyword-arguments
that suggest to dont mix with optional arguments a keywords argument.
Is possible to make this work?

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.

Related

How do I find the value of the key that I set with argument in Ruby?

I am currently working on a project with Ruby.
I don't know how to find the set of the value and key that has the same key as I gave with argument.
what I have tried is something like this below.
but it doesn't work.
def find_target(type)
target = type_list.find{|k,v| k == type}
target
end
def type_list
type = {
a: 1,
b: 2,
c: 3,
d: 4
}
type
end
but instead of giving an argument of variable, I gave a string as an argument, and it worked.
def find_target(a)
target = type_list.find{|k,v| k == a}
target
end
Edited
What I really want find_target to do is returning a matched value.
For example, when an argument is a, then it returns 1.
How can I solve this?
I would love you if you could help me.
Thank you .
I think one thing tripping you up is that your type_list hash is keyed with symbols (rather than strings or the value of a variable). The syntax you're using:
{a: 1}
is just shorthand for this:
{:a => 1}
Which means "A Hash with one key: the symbol :a with the value 1". That's distinct from:
{'a' => 1} # Keyed with the string 'a'
and this:
a = 'something'
b = {a => 1} # Keyed with value of 'a' at the time of creating, ie: {'something' => 1}. Note that if you change the value of a, the hash key won't change.
What do you expect as your return value from find_target(:a)? The find method on a Hash will return an Enumerator - mostly equivalent to a two-element Array, with the key and the value: {a: 1}.find{|k,v|k == :a} will return [:a, 1]. Is that what you want?
If you just want to have the value 1 returned, then you're really doing a hash lookup, and you don't need any extra methods at all. A common way to do this would be to define type_list as a constant, and then just refer to it by key:
TYPE_LIST = {
a: 1,
b: 2,
c: 3,
d: 4
}
#Then to find the type:
TYPE_LIST[:a] # Returns '1'
You might want to use a find_type method to handle the case where the key doesn't match a type: a plain Hash lookup will return nil, which you might not want.
Hope this helps put you on the right path, but I'm happy to expand this answer if needed!

Destructure a Hash in block arguments in Ruby 2.7

This:
[{a: 1, b: 2}, {a: 3, b: 4}].each do |a:, b:| p a end
Raises the following warning in Ruby 2.7
warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
I understand that each is passing a hash to the block, and the block now accepts |a:, b:| as named arguments but, is there any way to correctly destructure the hash in this context?
I'm uncertain, but I think perhaps the idea is to use pattern matching for hash destructuring? For example:
{a: 1, b: 2}.tap do |args|
args in {a: a, b: b} # !!!
p a
end
Currently by default however, this will display a warning (which can be disabled):
Pattern matching is experimental, and the behavior may change in future versions of Ruby!
If you already know that you have two keys in each Hash as per your example, why not this?
[{a: 1, b: 2}, {a: 3, b: 4}].each do |h|
a, b = h.values
p a
end
In Ruby 3 you can use the rightward assignment operator =>:
{a: 1, b: 2}.tap do |args|
args => { a:, b: }
p a
end

How to prevent auto expansion of hash arguments?

Why does Ruby 2.3.1 auto-expand my hash argument when I don't provide an optional named argument (:to) and how do I prevent that?
def qsend(*args, to: nil, **opts)
puts "args: #{args}, opts: #{opts}"
end
qsend({ a: 1, b: 2, c: 3 })
qsend({ a: 1, b: 2, c: 3, d: 4 }, to: "me")
Output:
args: [], opts: {:a=>1, :b=>2, :c=>3}
args: [{:a=>1, :b=>2, :c=>3, :d=>4}], opts: {}
When I remove args from the argument list, my hash is not dropped into opts in the 2nd call, but an error is raised: "wrong number of arguments (given 1, expected 0) (ArgumentError)". And when I remove opts from the argument list, my hash is not dropped into args in the 1st call - an error is being thrown again, this time: "unknown keywords: a, b, c (ArgumentError)". Using args = {} instead of *args makes no difference.
The following call works (hash in 1st call will be dropped into args), but is there no better way?
qsend({ a: 1, b: 2, c: 3 }, {})
I would prefer to solve the problem in the method definition.
edit: method renamed, output fixed.

is it possible to use hash keys as a first level variables inside ruby method?

I'll try to explain myself a little bit more
# Lets say I have this hash
options = {a: 1, b: 2}
# and here I'm calling the method
some_method(options)
def some_method(options)
# now instead of using options[:a] I'd like to simply use a.
options.delete_nesting_and_create_vars
a + b # :a + :b also good.
thanks!
Is it possible using Ruby2 splat parameters:
options = {a: 1, b: 2}
def some_method1(a:, b:)
a + b
end
or:
def some_method2(**options)
options[:a] + options[:b]
end
some_method1 **options
#⇒ 3
some_method2 **options
#⇒ 3
If your options are fixed, like only :a and :b are the only keys, you can write the method like this:
def some_method(a:, b:)
a + b
end
options = {a: 1, b: 2}
some_method(options) #=> 3

Does Rubyist call Argument Lists and Array both Array?

I'm simply curious about how the terms are used, so I have a question.
First, let me quote where the terms are used.
quote from Active Record document:
Active Record Query Interface — Ruby on Rails Guides
2 Conditions
Conditions can either be specified as a string, array, or hash.
2.2 Array Conditions
Now what if that number could vary, say as an argument from somewhere? The find
would then take the form:
Client.where("orders_count = ?", params[:orders])
I was confused
Client.where("orders_count = ?", params[:orders])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I was confused the document. Does the document refer to ^^^ part as Array?. I think ruby Array is [ ].
I found other site call this Argument Lists.
Programming Ruby: The Pragmatic Programmer's Guide
In Ruby It does what is called?
def f(a, b)
end
f(1, 2)
^^^^^^
Array or List?
Array and List?
How do you distinguish Array and List in Ruby?
Client.where("orders_count = ?", params[:orders]) ... this is Array?
No, this is not an array. These are two arguments.
How do you distinguish Array and List in Ruby?
Argument list refers to a method's arguments, it's not a class.
You can provide an argument list when defining a method:
def foo(a, b)
p a: a, b: b
end
foo is the method name and a, b is the argument list.
When calling a method, the passed arguments may also be called argument list:
foo 1, 2 # prints {:a=>1, :b=>2}
1, 2 is the argument list.
Converting between array and argument list
You can convert an array into a argument list by using *:
foo *[1, 2] # prints {:a=>1, :b=>2}
You can also convert an argument list to an array by prefixing an argument with * in the method definition:
def bar(*args)
p args
end
This allows the method to take a variable number of arguments:
bar 1, 2 # prints [1, 2]
In Ruby an argument list can be handled as both - an array or single arguments - depending on the assignment:
a, b, c = 1, 2, 3
a #=> 1
b #=> 2
c #=> 3
But:
array = 1, 2, 3
array #=> [1, 2, 3]

Resources