Currently, I am able to optimise processing of positional and keyword arguments like the method below. Is there any chance of optmisizing it further?
def my_method(self, *args, **kwargs):
_price = args[0] if len(args) > 0 else kwargs.get("price", getattr(self, "price", 0))
# More arguments
Motto:
To extract positional argument or keyword argument preferring positional argument.
To avail default value for a variable (optionally) through builtin or custom function.
Related
I came across some Ruby code,
I try to understand why the variables have colon at the end of their name inside the declaration of the initialize method.
Is there any reason for the colon?
attr_reader :var1, :var2
def initialize(var1:, var2:)
#var1 = var1
#var2 = var2
end
Those are keyword arguments.
You can use them by name and not position. E.g.
ThatClass.new(var1: 42, var2: "foo")
or
ThatClass.new(var2: "foo", var1: 42)
An article about keyword arguments by thoughtbot
It is called keyword arguments.
Keyword arguments are similar to positional arguments with default
values:
def add_values(first: 1, second: 2)
first + second
end
Arbitrary keyword arguments will be accepted with **:
def gather_arguments(first: nil, **rest)
p first, rest
end
gather_arguments first: 1, second: 2, third: 3
# prints 1 then {:second=>2, :third=>3}
When calling a method with keyword arguments the arguments may appear
in any order. If an unknown keyword argument is sent by the caller an
ArgumentError is raised.
When mixing keyword arguments and positional arguments, all positional
arguments must appear before any keyword arguments.
Hey there
Is it possible to have optional attributes and a block as parameters
for a method call?
Example: I have to call
method(foo, foo: bar, -> { do_something }
and tried it with
def method(foo, *bar, &block)
end
As for my understanding the block always has to be at last position?
After a bit of research I found out the unary(?) * seems to be for
arrays. Since I try to pass a Hash I changed the code to
def method(foo, bar={}, &block)
end
But this doesn't do the trick either. I guess its because he cant
figure out where the bar ends and the block starts.
Any ideas or suggestions? Thank you in advance
Append: Just for the curious why I need this. We have a big json
schema running and have a small DSL that builds the json from the
model definitation. Without going to much into detail we wanted to
implement exportable_scopes.
class FooBar
exportable_scope :some_scope, title: 'Some Scope', -> { rewhere archived: true }
end
On some initializer this is supposed to happens:
def exportable_scope scope, attributes, &block
scope scope block
if attributes.any?
attributes.each do |attribute|
exportable_schema.scopes[scope] = attribute
end
else
exportable_schema.scopes[scope] = {title: scope}
end
end
So this is working fine, I just need a hint for the method
parameters.
Yes, it is possible.
When mixing different kinds of parameters, they have to be included in the method definition in a specific order:
Positional parameters (required and optional) and a single splat parameter, in any order;
Keyword parameters (required and optional), in any order;
Double splat parameter;
Block parameter (prefixed with &);
The order above is somewhat flexible. We could define a method and begin the parameter list with a single splat argument, then a couple of optional positional arguments, and so on. Even though Ruby allows that, it's usually a very bad practice as the code would be hard to read and even harder to debug. It's usually best to use the following order:
Required positional parameters;
Optional positional parameters (with default values);
Single splat parameter;
Keyword parameters (required and optional, their order is irrelevant);
Double splat parameter;
Explicit block parameter (prefixed with &).
Example:
def meditate cushion, meditation="kinhin", *room_items, time: , posture: "kekkafuza", **periods, &b
puts "We are practicing #{meditation}, for #{time} minutes, in the #{posture} posture (ouch, my knees!)."
puts "Room items: #{room_items}"
puts "Periods: #{periods}"
b.call # Run the proc received through the &b parameter
end
meditate("zafu", "zazen", "zabuton", "incense", time: 40, period1: "morning", period2: "afternoon" ) { puts "Hello from inside the block" }
# Output:
We are practicing zazen, for 40 minutes, in the kekkafuza posture (ouch, my knees!).
Room items: ["zabuton", "incense"]
Periods: {:period1=>"morning", :period2=>"afternoon"}
Hello from inside the block
Notice that when calling the method, we have:
Provided the cushion mandatory positional argument;
Overwritten the default value of the meditation optional positional argument;
Passed a couple of extra positional arguments (zabuton and incense) through the *room_items parameter;
Provided the time mandatory keyword argument;
Omitted the posture optional keyword argument;
Passed a couple of extra keyword arguments (period1: "morning", period2: "afternoon") through the **periods parameter;
Passed the block { puts "Hello from inside the block" } through the &b parameter;
Please note the example above servers only to illustrate the possibility of mixing different types of parameters. Building a method like this in real code would be a bad practice. If a method needs that many arguments, it's probably best to split it into smaller methods. If it's absolutely necessary to pass that much data to a single method, we should probably create a class to store the data in a more organized way, then pass an instance of that class to the method as a single argument.
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.
I'm working on some Watir-webdriver tests in Ruby and can't seem to get the following code to work. I want to pass an optional validation argument into the log_activity method.
def log_activity (type, *validation)
#do something
end
I pass the following arguments into the method:
log_activity("license", 1)
I expect validation == 1 to be true, but it is false:
puts validation.empty?
-> false
puts validation
-> 1
if validation == 1
puts "validation!!!!"
else
puts "WTF"
end
-> WTF
What am I doing wrong?
Forgot to mention, I'm using ruby 1.9.3
*validation is an array that includes the second and all arguments afterwards. Given that it is an array, the results you see make sense. You want to check the first element in the *validation array.
Alternatively, if you will only get one optional argument, you can do:
def log_activity (type, validation=nil)
#do something
end
Then validation will be whatever you passed in.
Read "Method Arguments In Ruby" and look at "Optional Arguments". I found it pretty handy.
I am pasting the useful content:
Optional Arguments
If you want to decide at runtime how many – if any – arguments you will supply to a method, Ruby allows you to do so. You need to use a special notation when you define the method, e.g.:
def some_method(*p)
end
You can call the above method with any number of arguments (including none), e.g.:
some_method
or
some_method(25)
or
some_method(25,"hello", 45, 67)
All of those will work. If no arguments are supplied, then p will be an empty array, otherwise, it will be an array that contains the values of all the arguments that were passed in.
when you use *args as the last argument in Ruby, args is an array.
Unfortunately for you, on Ruby 1.8, array.to_s == array.join("")
Try either
if validation == [1]
or
if validation.first == 1
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