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.
Related
I have a situation where I'm dealing with couple of methods.I'm confused how to pass parameters and arguments for them. I initially have these two methods let's say -
def method_foo1 classes_prod, classes_corp, run_user, application
set_name_prod = ... // declaring variable
all_classes_prod = ... //declaring variable
method_foo3 set_name_prod, all_classes_prod, run_user , application //calling method 3
end
I'm introducing a second method which also calls method_foo3 from body of it but now it should pass only three arguments.Something like below -
def method_foo2 account_id,application
account_id_prod = .... // declaring variable
method_foo3 account_id_prod, application // //calling method 3
end
Now the main method/ method_3 which method_1 & method_2 are calling -
def method_3 set_name,classes,run_user,application
..... //body of method_3
end
As, we can see the method_3 has only 4 parameters available previously. Now I want to introduce a parameter "account_id" along with the rest. And make sure "classes" , "run_user" & new parameter "account_id" optional as they're not mandatorily being called from method_1 and method_2. How can I do this ?Any help is really appreciated.
I basically want to skip few parameters when they don't get any value and make sure the exact argument goes to exact parameter.
I didn't quite understand what the question was about exactly so I will answer in general, hope this helps
Using a large number of parameters in methods is always a pain. It's very hard to maintain, it's very hard to pass arguments, because it's easy to get confused in them
Also complicating the problem is the fact that there is no method overload in Ruby. You can't have methods with same names but with different parameters number, every new method will override old one
That's the reason, why it is much more convenient to use hashes as parameters
For example
def foo(args)
puts args[:bar]
end
foo(bar: "bar")
# will print
# bar
If after that you decide to use some new parameter, it doesn't matter. It's very easy to add
def foo(args)
puts args[:bar]
puts args[:baz]
end
foo(bar: "bar", baz: "baz")
# will print
# bar
# baz
You can combine hash with kwargs and (or) default kwargs like in Rob Spoor examples
def foo(boo: "boo", **args)
puts args[:bar]
puts args[:baz]
puts boo
end
foo(bar: "bar", baz: "baz")
# will print
# bar
# baz
# boo
foo(bar: "bar", baz: "baz", boo: "baa")
# will print
# bar
# baz
# baa
I also want to note (although it is opinion based) the fact that it is considered good practice to use parentheses when defining a method and when calling it, this significantly improves the readability of the code
As far as I know, Ruby has two distinct ways of letting method parameters be optional:
default values
named arguments
Unfortunately, unlike Python, they don't mix - a parameter is either required, or has a default value, or is named.
For instance:
def method_3 set_name = "", classes = "", run_user = "", application = "", account_id: ""
end
You can call this in many ways though:
method_3 -- all default values
method_3("my set") -- default values for all but set_name
method_3("my set", account_id: "account") -- mixed, set_name and account_id are set
method_3(account_id: "account") -- default values for all but account_id`
I have the following two methods, which I believe should have the same behaviour disregarding their names:
def a=(*params)
params
end
def b(*params)
params
end
But when in fact I use them:
a=(1) # => 1
b(1) # => [1]
(a=1) == b(1) # => false
while interestingly:
(a=1,2) == b(1,2) # => true
Why isn't their behaviour the same?
Edit: forgot to wrap the above in a class / call with self. which accidentally produces the same behaviour but for a different reason. It has been pointed out in the answers.
It has nothing to do with splat. It's the assignment operator. In ruby, the assignment operator returns the value assigned. The return value from the method is ignored.
So a=1 return 1, not [1].
But, as mentioned by #mudasobwa, you're not even calling the method here. But if you were, that's what would happen (ignoring the return value).
class Foo
def a=(*params)
params
end
end
f = Foo.new
f.a = 1 # => 1
f.a = 1,2 # => [1, 2]
To not ignore the return value, call that setter without using assignment operator.
f.send 'a=', 1 # => [1]
The thing is that
a = 1
sets the local variable and does not call your method at all. Try with
def a=(*param)
puts "I AM HERE"
end
var= methods require an explicit receiver. To call your method, call it with an explicit receiver:
self.a = 1
It still won’t return anything but 1, because assignment methods return the value (the same way as initialize called through MyClass.new returns an instance, no matter what.) But you might check that splat works with:
def a=(*param)
puts param.inspect
end
self.a = 1
# [1]
#⇒ 1
I don't understand completely how named parameters in Ruby 2.0 work.
def test(var1, var2, var3)
puts "#{var1} #{var2} #{var3}"
end
test(var3:"var3-new", var1: 1111, var2: 2222) #wrong number of arguments (1 for 3) (ArgumentError)
it's treated like a hash. And it's very funny because to use named parameters in Ruby 2.0 I must set default values for them:
def test(var1: "var1", var2: "var2", var3: "var3")
puts "#{var1} #{var2} #{var3}"
end
test(var3:"var3-new", var1: 1111, var2: 2222) # ok => 1111 2222 var3-new
which very similar to the behaviour which Ruby had before with default parameters' values:
def test(var1="var1", var2="var2", var3="var3")
puts "#{var1} #{var2} #{var3}"
end
test(var3:"var3-new", var1: 1111, var2: 2222) # ok but ... {:var3=>"var3-new", :var1=>1111, :var2=>2222} var2 var3
I know why is that happening and almost how it works.
But I'm just curious, must I use default values for parameters if I use named parameters?
And, can anybody tell me what's the difference between these two then?
def test1(var1="default value123")
#.......
end
def test1(var1:"default value123")
#.......
end
I think that the answer to your updated question can be explained with explicit examples. In the example below you have optional parameters in an explicit order:
def show_name_and_address(name="Someone", address="Somewhere")
puts "#{name}, #{address}"
end
show_name_and_address
#=> 'Someone, Somewhere'
show_name_and_address('Andy')
#=> 'Andy, Somewhere'
The named parameter approach is different. It still allows you to provide defaults but it allows the caller to determine which, if any, of the parameters to provide:
def show_name_and_address(name: "Someone", address: "Somewhere")
puts "#{name}, #{address}"
end
show_name_and_address
#=> 'Someone, Somewhere'
show_name_and_address(name: 'Andy')
#=> 'Andy, Somewhere'
show_name_and_address(address: 'USA')
#=> 'Someone, USA'
While it's true that the two approaches are similar when provided with no parameters, they differ when the user provides parameters to the method. With named parameters the caller can specify which parameter is being provided. Specifically, the last example (providing only the address) is not quite achievable in the first example; you can get similar results ONLY by supplying BOTH parameters to the method. This makes the named parameters approach much more flexible.
The last example you posted is misleading. I disagree that the behavior is similar to the one before. The last example passes the argument hash in as the first optional parameter, which is a different thing!
If you do not want to have a default value, you can use nil.
If you want to read a good writeup, see "Ruby 2 Keyword Arguments".
As of Ruby 2.1.0, you no longer have to set default values for named parameters. If you omit the default value for a parameter, the caller will be required to provide it.
def concatenate(val1: 'default', val2:)
"#{val1} #{val2}"
end
concatenate(val2: 'argument')
#=> "default argument"
concatenate(val1: 'change')
#=> ArgumentError: missing keyword: val2
Given:
def test1(var1="default value123")
var1
end
def test2(var1:"default value123")
var1
end
They'll behave the same way when not passed an argument:
test1
#=> "default value123"
test2
#=> "default value123"
But they'll behave much differently when an argument is passed:
test1("something else")
#=> "something else"
test2("something else")
#=> ArgumentError: wrong number of arguments (1 for 0)
test1(var1: "something else")
#=> {:var1=>"something else"}
test2(var1: "something else")
#=> "something else"
I agree with you that it's weird to require default values as the price for using named parameters, and evidently the Ruby maintainers agree with us! Ruby 2.1 will drop the default value requirement as of 2.1.0-preview1.
This is present in all the other answers, but I want to extract this essence.
There are four kinds of parameter:
Required
Optional
Positional
def PR(a)
def PO(a=1)
Keyword
def KR(a:)
def KO(a:1)
When defining a function, positional arguments are specified before keyword arguments, and required arguments before optional ones.
irb(main):006:0> def argtest(a,b=2,c:,d:4)
irb(main):007:1> p [a,b,c,d]
irb(main):008:1> end
=> :argtest
irb(main):009:0> argtest(1,c: 3)
=> [1, 2, 3, 4]
irb(main):010:0> argtest(1,20,c: 3,d: 40)
=> [1, 20, 3, 40]
EDIT: the required keyword argument (without a default value) is new as of Ruby 2.1.0, as mentioned by others.
Leaving this here because it helped me a lot.
Example
Suppose you have this:
def foo(thing, to_print)
if to_print
puts thing
end
end
# this works
foo("hi", true)
# hi
# => nil
so you try adding the argument names, like so:
foo(thing: "hi", to_print: true)
# foo(thing: "hi", to_print: true)
# ArgumentError: wrong number of arguments (given 1, expected 2)
# from (pry):42:in `foo'
but unfortunately it errors.
Solution
Just add a : to the end of each argument:
def foo2(thing:, to_print:)
if to_print
puts thing
end
end
foo2(thing: "hi", to_print: true)
# hi
# => nil
And it works!
According to "Ruby 2.0.0 by Example" you must have defaults:
In Ruby 2.0.0, keyword arguments must have defaults, or else must be captured by **extra at the end.
def test(a = 1, b: 2, c: 3)
p [a,b,c]
end
test #=> [1,2,3]
test 10 #=> [10,2,3]
test c:30 #=> [1,2,30] <- this is where named parameters become handy.
You can define the default value and the name of the parameter and then call the method the way you would call it if you had hash-based "named" parameters but without the need to define defaults in your method.
You would need this in your method for each "named parameter" if you were using a hash.
b = options_hash[:b] || 2
as in:
def test(a = 1, options_hash)
b = options_hash[:b] || 2
c = options_hash[:c] || 3
p [a,b,c]
end
You can define named parameters like
def test(var1: var1, var2: var2, var3: var3)
puts "#{var1} #{var2} #{var3}"
end
If you don't pass one of the parameters, then Ruby will complain about an undefined local variable or method.
I am trying to set the first argument to a method as being optional, followed by any number of args. For example:
def dothis(value=0, *args)
The issue I am running into is that it doesn't seem like this is actually possible? When I call dothis("hey", "how are you", "good") I was hoping it would set value to default to 0, but instead it is just making value="hey". Is there any way to accomplish this behavior?
This is not possible directly in Ruby
There are plenty of options though, depending on what you are doing with your extended params, and what the method is intended to do.
Obvious choices are
1) Take named params using hash syntax
def dothis params
value = params[:value] || 0
list_of_stuff = params[:list] || []
Ruby has nice calling convention around this, you don't need to provide the hash {} brackets
dothis :list => ["hey", "how are you", "good"]
2) Move value to the end, and take an array for the first param
def dothis list_of_stuff, value=0
Called like this:
dothis ["hey", "how are you", "good"], 17
3) Use a code block to provide the list
dothis value = 0
list_of_stuff = yield
Called like this
dothis { ["hey", "how are you", "good"] }
4) Ruby 2.0 introduced named hash parameters, which handle a lot of option 1, above for you:
def dothis value: 0, list: []
# Local variables value and list already defined
# and defaulted if necessary
Called same way as (1):
dothis :list => ["hey", "how are you", "good"]
This post is a little bit old, but I want to contribute if someone is looking for the best solution for that. Since ruby 2.0, you can do that easily with named arguments defined with a hash. The syntax is easy and more readable.
def do_this(value:0, args:[])
puts "The default value is still #{value}"
puts "-----------Other arguments are ---------------------"
for i in args
puts i
end
end
do_this(args:[ "hey", "how are you", "good"])
You can also do the same thing with the greedy keyword **args as a hash, like this:
#**args is a greedy keyword
def do_that(value: 0, **args)
puts "The default value is still #{value}"
puts '-----------Other arguments are ---------------------'
args.each_value do |arg|
puts arg
end
end
do_that(arg1: "hey", arg2: "how are you", arg3: "good")
You will need to use named parameters to accomplish this:
def dothis(args)
args = {:value => 0}.merge args
end
dothis(:value => 1, :name => :foo, :age => 23)
# => {:value=>1, :name=>:foo, :age=>23}
dothis(:name => :foo, :age => 23)
# => {:value=>0, :name=>:foo, :age=>23}
by using value=0 you are actually assigning 0 to value. just to retain the value, you can either use the above mentioned solutions or just simply use value everytime you call this method def dothis(value, digit=[*args]).
The default arguments are used when the arguments are not provided.
I came across the similar issue and I got over it by using:
def check(value=0, digit= [*args])
puts "#{value}" + "#{digit}"
end
and simply call check like this:
dothis(value, [1,2,3,4])
your value would be default and other values belong to other argument.
Say I have a ruby method:
def blah(foo=17)
...
end
In code I want to invoke blah with a specific argument "blah(a)" or invoke blah using its default argument "blah()" Is there any way to do that without specifying the method name twice? I'm trying to avoid:
if a.nil?
blah()
else
blah(a)
end
Because it makes the code look more complicated than it is. Best I can come up with (didn't test) is:
args=[]
args << a unless a.nil?
a.send :blah, args
I just tried a few ways, and didn't find any, but if you find yourself doing this a lot, I wonder of the benefit of using a default parameter that way. Try this instead:
def blah(foo=nil)
foo ||= 17
puts foo
end
blah()
a = nil
blah(a)
a = 20
blah(a)
This will output:
17
17
20
I don't like this answer, but I guess it works:
blah( *[a].compact )
It's hard to say without knowing what the actual problem being solved is, but I have a feeling something like this would work best:
blah(a || 17)
This statement seems to more clearly express its intent, without leaving the reader to lookup the definition of the blah function in order to work out what the default is.
In ruby everything has a return value. So if also has a return value. The following method works with any number of arguments. The * expands an array so the array elemnts are individual arguments.
blah(*
if a.nil?
[]
else
[a]
end
)
You don't have to indent it this way, but this looked like the indentation to make it clearest.
I was looking for the same. (I think it is almost a bug in Ruby specs)
I think I will solve this in other way ussing the option hash.
def foo(options={})
bar = options.delete(:bar) || :default
puts bar
end
foo
#=> default
foo :bar => :something
#=> something
a = nil
foo :bar => a
#=> default
It is more extensible and is readable for any rubyst.
I vote for
def blah(foo = nil)
foo ||= 17
end
too.
But if you would like to distinguish between the nil-value and
no-argument situations, I suggest using a block like so:
def blah
foo = block_given? ? yield : 17
end
blah #=> 17
blah { nil } #=> nil
blah { 71 } #=> 71