ruby on rails parsing variable to function - ruby-on-rails

Can someone tell me difference between theese two code examples? What changes when putting ":" at the end of variable name?
def initialize(email:)
#email = email
end
def initialize(email)
#email = email
end

email: in the method definition argument means that the method will be expecting a key-value pair as the method arguments.
Sample initialization call:
YourClass.new(email: email_param)
while a simple email in the method definition argument means that the method will assign the passed argument to the variable email.
Sample initialization call:
YourClass.new(email_param)

email: is keyword argument
Keyword arguments follow any positional arguments and are separated by commas like positional arguments
It work this way:
def my_method(arg1:, arg2: "default", arg3:)
puts "arg1: #{arg1}, arg2: #{arg2}, arg3: #{arg3}"
end
my_method(arg3: 3, arg1: 1)
# will print arg1: 1, arg2: default, arg3: 3
my_method(arg2: 2, arg1: 1, arg3: 3)
# will print arg1: 1, arg2: 2, arg3: 3
my_method(arg2: 2)
# will raise ArgumentError (missing keywords: arg1, arg3)
Arguments order is important if these arguments are not keyword
def my_method(arg1, arg2 = "default", arg3)
puts "arg1: #{arg1}, arg2: #{arg2}, arg3: #{arg3}"
end
my_method(1, 3)
# will print arg1: 1, arg2: default, arg3: 3
my_method(1, 2, 3)
# will print arg1: 1, arg2: 2, arg3: 3
my_method(1)
# will raise ArgumentError (wrong number of arguments (given 1, expected 2..3))

Related

Order of parameters in ruby method

I have an already existing method in my concern file, which has following number parameters define. For ex
module MyMethods
def close_open_tasks(param_1, param_2, param_3 = nil)
p param_1
p param_2
p param_3
end
end
But I am trying to add one more new optional parameter at the end param_4 in the above method.
Lets say I am including this module from an api and calling it from there.
When I call the following
close_open_tasks("test1","test2","test4")
"test4" is getting assigned to param_3 arg. How can I make it assign to param_4? Since both param_3 and param_4 are optional parameters its getting trickier with order.
You can use keywords arguments
module MyMethods
def close_open_tasks(param_1:, param_2:, param_3: nil, params_4: nil)
p param_1
p param_2
p param_3
end
end
and call it like this
close_open_tasks(param_1: "test1", param_2: "test2", param_4: "test4")
If your method has more then two positional arguments or there is no obvious order to the arguments you should define them as keyword arguments.
def close_open_tasks(foo, bar:, baz:) # required
def close_open_tasks(foo, bar: 3, baz: nil) # with defaults
If your method should actually take a list of arguments of any length you can use a splat:
def close_open_tasks(*tasks)
tasks.map do
task.close
end
end
close_open_tasks(task1, task2)
The equivilent for keyword arguments is the double splat:
def close_open_tasks(foo, bar: 3, baz: nil, **opts)
Which will provide a opts hash containing the additional keyword args.

How does rails activerecord where clause accepts dynamic parameter?

In ruby web define parameters for a method.
def para_check(para1, para2, para3 .... )
end
How does activerecord .where is defined so that it accepts dynamic parameters ?
I went though few blogs/websites but could not find useful resources.
In Ruby you can prefix the parameter with a splat (*) to define methods that accept any number of positional arguments:
def foo(*x)
x
end
foo(1,2,3) == [1,2,3] # true
This is also known as a variadic function. As you can see this creates a array from the list of arguments.
You can also combine numbered and positional arguments and a splat:
foo(bar, *args)
[bar, args]
end
foo(1, 'a', 'b') == [1, ['a','b']] # true
This makes the method require one argument but allows an infinate number of arguments.
The ActiveRecord::QueryMethods#where method accepts both positional and keyword arguments:
where('foo = :x OR bar = :y', x: 1, x: 2)
Starting with Ruby 2.0 you can do this with:
def foo(*args, **kwargs)
[args, kwargs]
end
foo(1, 2, 3, bar: 'baz') == [[1, 2, 3], { bar: 'baz' }] ## true
Previously you had to use various hacky solutions with array parameters and and optional hash parameter. You can still find these in the Rails source code and in a lot of other code written before Ruby 2.0.
There are a few ways to do this. For starters you could accept a hash or array as parameter.
So if you expect a Hash, that is what where does, you can just write
def para_check(params)
params.each do |param_name, param_value|
# do something with the params
end
end
and then you can write:
para_check(para1: "X", para2: "Y", para5: "Z")
An alternative, in this case maybe, if you need to specify a list/array of parameters, you can also define your method as follows:
def para_check(*params)
params.each do |param_name|
# do something with param_name
end
end
(the '*'-operator is called the splat-operator)
and then you call your method as follows
para_check(:para1, :para2, :para4)

Ruby Method Double Splat vs Hash

When dealing with a method that takes a known number of arguments plus an options hash, is there any difference/advantage of capturing the options in a double splat?
consider
def method_a(a, **options)
puts a
puts options
end
vs
def method_b(a, options = {})
puts a
puts options
end
Are the two equivalent? I think method_b is more readable but still I see a lot of code going with method_a.
Is there a reason to use double splat for options when the regular (non options) arguments are captured without a splat?
Well it depends what you mean by "known number of arguments", specifically for the situation when you have keyword arguments plus any number of other keyword args, for example:
def foo(i, keyword_1: "default", **other_keywords)
end
I can call this as
foo(6, keyword_1: "asd", other: "keyword")
And {other: "keyword"} will be contained in other_keywords while keyword_1 can be accessed directly as a local variable.
Without the ** splat operator this behavior is more cumbersome to implement, something like this:
def foo(i, opts={})
keyword_1 = opts.delete(:keyword_1) || "default"
# now `opts` is the same as `other_keywords`
end
Another difference is the fact that the ** version captures rest keyword arguments. Keyword arguments are represented by symbols, resulting in the following behaviour:
def a(**options)
options
end
def b(options = {})
options
end
a(a: 1) #=> {:a=>1}
a('a' => 1) #=> ArgumentError (wrong number of arguments (given 1, expected 0))
b(a: 1) #=> {:a=>1}
b('a' => 1) #=> {"a"=>1}
def c(hash_options = {}, **keyword_options)
[hash_options, keyword_options]
end
# symbols are extracted and used as rest keyword arguments
c('a' => 1, b: 2, 'c' => 3, d: 4) #=> [{"a"=>1, "c"=>3}, {:b=>2, :d=>4}]

Ruby Argument Error with named parameter

Consider the following Ruby Code:
def f1(p1, p2: nil, p3: nil)
puts "Parameter 1: #{p1}"
puts "Parameter 2: #{p2}"
puts "Parameter 3: #{p3}"
end
def f2(p1, p2: nil, p3: nil)
f1(p1, p2, p3)
end
# This will throw error ArgumentError: wrong number of arguments (3 for 1)
f2("Hi")
In the above code, my problem is I need to list the parameters in both f1 and f2 (user requirement). At the same time I should enable the named parameters for the user (because the 3 parameters can become 30 parameters with most of them having a default value)
Has anyone come across this issue?
You used named parameters, you have to specify them explicitly.
...
def f2(p1, p2: nil, p3: nil)
f1(p1, p2: p2, p3: p3) # specify p2 and p3
end
f2("Hi")
When you call f1 you have to specify explicitly p2 and p3 if you want to define them
Check the official documentation or some third-party resources about this
def foo(arg1 = 1, arg2:, arg3: nil)
puts "#{arg1} #{arg2} #{arg3}"
end
foo(arg2: 'arg2')
# Will display
# 1 arg2
foo('arg1_defined', arg2: 'arg2')
# WIll display
# arg1_defined arg2
Note: Also named arguments do not have to follow order, you can place them at any order you want, but after other arguments
foo(arg3: 'arg3_with_value', arg2: 'arg2_val')
# 1 arg2_val arg3_with_value
foo(arg3: 'arg3_val', 10)
# SyntaxError: unexpected ')', expecting =>
# they have to be after not-named arguments

what happens when *args is passed to yield in ruby

what happens when *args passed to yield in ruby, in capture_helper.rb of rails I saw a statement where *args is passed to yield statement, what actually happens when we do so.
buffer = with_output_buffer { value = yield(*args) }
where first parameter is builder object and second parameter is the block passed
With the * operator (splat operator) prefixing a variable (which must be an array or hash), the values of the array are extracted:
ary = [1, 2, 3]
def foo(a, b, c)
a + b + c
end
foo(ary)
# => ArgumentError: wrong number of arguments (given 1, expected 3)
foo(*ary)
# 6
It's just the same with yield, except that the values are passed to the block:
def bar
ary2 = [5, 6]
yield(*ary2)
end
bar do |x, y|
puts x + y
end
# 11
Splats have many uses in ruby. When you use a splat in a method call it turns an array into a list of arguments.
def test(*args)
args
end
Consider these examples:
a = [1,2,3]
test(a)
# => [[1, 2, 3]]
test(1,2,3)
# => [1, 2, 3]
test(*a)
# => [1, 2, 3]
In the first example the array is treated as the first argument so the result is an array in an array. While *[1,2,3] de-structures the array so that we get the desired result.
This makes it extremely useful for calling methods that take a variable number of arguments. yield works just like any other method in this regard, as using a splat will de-structure the array in args and call the passed block with a list of arguments.

Resources