I have a piece of sample code:
def m(args = nil, **kwargv)
puts kwargv
a = args or {}
puts a
kwargv['a'] = a
puts kwargv
end
When I invoke:
m(args = {'c':'d'})
m(args: {'c': 'd'})
m(args = {xxx}, {})
only in the last one will args be interpreted as args; in the first and the second ones, the dict will be used as kwargv.
Is there a more elegant way to specify args as a dict?
This should work:
> m({'c' => 'd'})
# {}
# {"c"=>"d"}
# {"a"=>{"c"=>"d"}}
# => nil
The logic behind this is that because the first argument is optional (defaulting to nil), and the last argument is a keyword-argument, passing a hash with symbol keys (m({ :c => 'd'})) will be interpreted as the keyword hash. Any other type of argument will be passed as the first parameter. If you really need to pass a keyword hash as the first parameter, you need to explicitly pass the second parameter as well:
> m({ :c => 'd' }, {})
# {}
# {:c=>"d"}
# {"a"=>{:c=>"d"}}
On a side note, calling the method while naming the args parameter (m(args=something)) is superfluous, and actually does something different than you think (assigns the something to a local variable named args, then passing it to the method). Use m(something) instead.
Related
I want to have named parameters to a method so the API is clear to the caller, but the implementation of the method needs the named parameters in a hash. So I have this:
def my_method(required_param, named_param_1: nil, named_param_2: nil)
named_params = {
named_param_1: named_param_1,
named_param_2: named_param_2
}
# do something with the named params
end
This works, but I have to do this in quite a few places, and I would rather have some helper that dynamically gets the named parameters into a hash. I haven't been able to find a way to do this. Any thoughts on how to accomplish this?
def my_method(required_param, named_param_1: nil, named_param_2: nil)
named_params = method(__method__).parameters.each_with_object({}) do |p,h|
h[p[1]] = eval(p[1].to_s) if p[0] == :key
end
p named_params # {:named_param_1=>"hello", :named_param_2=>"world"}
# do something with the named params
end
my_method( 'foo', named_param_1: 'hello', named_param_2: 'world' )
Ruby 2.0 provides no built-in way to get a Hash from keyword arguments.
You must choose between:
Using keyword arguments (as you currently are) and creating a Hash from those values separately, OR...
Using a Hash argument with default values, such as the Ruby v1.9.3 strategies described here.
You could do write your method as below :
def my_method(required_param, named_param_1: nil, named_param_2: nil)
named_params = Hash[method(__method__).parameters.select do |first,last|
[last,eval("#{last}")] if first == :key
end]
end
my_method(12, named_param_1: 11,named_param_2: 12)
# => {:named_param_1=>11, :named_param_2=>12}
Let's say we have this code:
def something(*someargs)
return *someargs.join(",")
end
Now, I found you can reference *someargs just like any other variable anywhere in the method definition. But I tried this...returning *someargs as a string, separated with a comma. Yet, when I call this method:
a = something(4, 5)
p a.class # => Array
p a #> ["4,5"]
why does something(4,5) still returns an array? If I do something like this:
[4, 5].join(",")
the result will be a string not in an array. So my question would be, how do I make the "something" method return an actual string which contains all the arguments as a string. And it's weird because if I do *someargs.class, the result is "Array", yet it doesn't behave like a typical array...
Try below :
def something(*someargs)
return someargs.join(",")
end
a = something(4, 5)
p a.class # => String
p a # => "4,5"
One example to explain your case:
a = *"12,11"
p a # => ["12,11"]
So when you did return *someargs.join(","), someargs.join(",") created the string as "4,5".But now you are using splat operator(*) again on the evaluated string "4,5" with the assignment operation like a = *"4,5". So finally you are getting ["4,5"].
Read more scenarios with splat operators here - Splat Operator in Ruby
Hope that helps.
An object prepended with a splat *... is not an object. You cannot reference such thing, nor can you pass it as an argument to a method because there is no such thing. However, if you have a method that can take multiple arguments, for example puts, then you can do something like:
puts *["foo", "bar"]
In this case, there is no such thing as *["foo", "bar"]. The splat operator is expanding it into multiple arguments. It is equivalent to:
puts "foo", "bar"
Regarding why someargs remains to be an array after someargs.join(","). That is because join is not a destructive method. It does not do anything to the receiver. Furthermore, an object cannot change its class by a destructive method. The only way to change the reference of someargs from an array to a string is to reassign it.
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.
When using an idiom such as:
def func(*args)
# some code
end
What is the meaning of *args? Googling this specific question was pretty hard, and I couldn't find anything.
It seems all the arguments actually appear in args[0] so I find myself writing defensive code such as:
my_var = args[0].delete(:var_name) if args[0]
But I'm sure there's a better way I'm missing out on.
The * is the splat (or asterisk) operator. In the context of a method, it specifies a variable length argument list. In your case, all arguments passed to func will be putting into an array called args. You could also specify specific arguments before a variable-length argument like so:
def func2(arg1, arg2, *other_args)
# ...
end
Let's say we call this method:
func2(1, 2, 3, 4, 5)
If you inspect arg1, arg2 and other_args within func2 now, you will get the following results:
def func2(arg1, arg2, *other_args)
p arg1.inspect # => 1
p arg2.inspect # => 2
p other_args.inspect # => [3, 4, 5]
end
In your case, you seem to be passing a hash as an argument to your func, in which case, args[0] will contain the hash, as you are observing.
Resources:
Variable Length Argument List, Asterisk Operator
What is the * operator doing
Update based on OP's comments
If you want to pass a Hash as an argument, you should not use the splat operator. Ruby lets you omit brackets, including those that specify a Hash (with a caveat, keep reading), in your method calls. Therefore:
my_func arg1, arg2, :html_arg => value, :html_arg2 => value2
is equivalent to
my_func(arg1, arg2, {:html_arg => value, :html_arg2 => value2})
When Ruby sees the => operator in your argument list, it knows to take the argument as a Hash, even without the explicit {...} notation (note that this only applies if the hash argument is the last one!).
If you want to collect this hash, you don't have to do anything special (though you probably will want to specify an empty hash as the default value in your method definition):
def my_func(arg1, arg2, html_args = {})
# ...
end
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.