Optional argument syntax - ruby

I found this code under this question, which checks if any argument is passed to a method:
def foo(bar = (bar_set = true; :baz))
if bar_set
# optional argument was supplied
end
end
What is the purpose of the ; :baz in this default value, and in what case would I use it?

The idea is that = (bar_set = true; :baz) will be evaluated only if a value is not passed to the bar parameter.
In Ruby, the return value of multiple consecutive expressions is the value of the last expression. Hence = (bar_set = true; :baz) assigns the value true to bar_set, and then sets :baz as the value for bar (because the code in the parentheses will evaluate to :baz, it being the last expression).
If a parameter was passed, bar_set will be nil and the value of bar would be whatever was given.

Related

Check if method's parameter was set by user

Is there a way to check if the parameter of the method was set by the default value set in the method itself or by the user who called the method?
Here's what I want to do in my method, for example
def json_prop(key, value = nil)
json_data = {} if json_data.nil?
return json_data[key] if value.set_by_user? # i.e. User did not set value to nil himself
json_data[key] = value # Set to the value inserted by the user when calling the method
end
There is a standard trick to do this, and it relies on two features of the Ruby programming language:
The default argument value for an optional parameter is a Ruby expression and thus can contain any arbitrary Ruby code and
Local variables defined in the default argument value expression are in scope in the method's body.
Therefore, all we need to do is set a variable in the default argument value expression, and we know: if the variable is set, the default argument value expression was evaluated and thus no argument value was passed:
def json_prop(key, value = (no_value_set_by_user = true; nil))
json_data = {} if json_data.nil?
return json_data[key] if no_value_set_by_user
json_data[key] = value
end
or
def json_prop(key, value = (
no_value_set_by_user = true
nil
))
json_data = {} if json_data.nil?
return json_data[key] if no_value_set_by_user
json_data[key] = value
end

Double splat and default value in method profile

While a method with a default value profile can accept nil (besides a hash):
def f(options = {})
options
end
f(hoge: "AAA", foo: "BBB") #=> {:hoge=>"AAA", :foo=>"BBB"}
f(nil) #=> nil
A method with double splat raises an error with nil:
def f(**options)
options
end
f(hoge: "AAA", foo: "BBB") #=> {:hoge=>"AAA", :foo=>"BBB"}
f(nil) # => wrong number of arguments (1 for 0) (ArgumentError)
When should I use double splat and when should I use = {}?
If the input to the method MUST be options hash, then, use double splat operator **.
Using options = {} only declares the default value to be empty hash, however, it does not necessarily guarantee that caller will pass hash - she may pass non-hash values and nil.
If the function was implemented using double splat (**) - as evident in examples you have provided - then non-hash and nil values will not be accepted and will be reported as error.

`defined?` and `unless` not working as expected

I was expecting the following snippet:
var = "Not Empty" unless defined? var
var # => nil
to return "Not Empty", but I got nil. Any insight into why this is happening?
This is one of the only moments in Ruby I would call actual WTFs.
You have to use
unless defined? var
var = :value
end
With the postfix syntax, the interpreter will internally nil-ify the value so it can reason about the variable, thus making it defined before the check is done:
# Doesn't print anything
unless defined?(foo) and (p(foo) or true)
foo = :value
end
# Prints nil
bar = :value unless defined?(bar) and (p(bar) or true)
Local variables are defined (as nil) at the point they are parsed. Definition of var2 precedes the condition. That makes var2 defined even when if the assignment is not executed. Then, the condition evaluates that var2 is defined, which retains the value nil for var2.

Ruby block's result as an argument

There are many examples how to pass Ruby block as an argument, but these solutions pass the block itself.
I need a solution that takes some variable, executes an inline code block passing this variable as a parameter for the block, and the return value as an argument to the calling method. Something like:
a = 555
b = a.some_method { |value|
#Do some stuff with value
return result
}
or
a = 555
b = some_method(a) { |value|
#Do some stuff with value
return result
}
I could imagine a custom function:
class Object
def some_method(&block)
block.call(self)
end
end
or
def some_method(arg, &block)
block.call(arg)
end
but are there standard means present?
I think, you are looking for instance_eval.
Evaluates a string containing Ruby source code, or the given block, within the context of the receiver (obj). In order to set the context, the variable self is set to obj while the code is executing, giving the code access to obj’s instance variables. In the version of instance_eval that takes a String, the optional second and third parameters supply a filename and starting line number that are used when reporting compilation errors.
a = 55
a.instance_eval do |obj|
# some operation on the object and stored it to the
# variable and then returned it back
result = obj / 5 # last stament, and value of this expression will be
# returned which is 11
end # => 11
This is exactly how #Arup Rakshit commented. Use tap
def compute(x)
x + 1
end
compute(3).tap do |val|
logger.info(val)
end # => 4

Using `defined?` at one-level expansion

The method defined? gives the result for the verbatim expression given as an argument. For example, the result of
defined? foo
is sensitive to whether foo is literally any defined variable/method. It does not make difference whether foo is defined as a string that is a valid (existing) expression:
foo = "Array"
or not:
foo = "NonExistingConstant"
Is it possible to make defined? be sensitive to the given argument expanded one level? That is, for foo = "Array", it should return "constant" and for foo = "NonExistingConstant", it should return nil. If so, how?
Since you need to check only constants:
['Array', 'NonExistentClass'].each do |name|
puts Object.const_defined?(name)
end
# >> true
# >> false

Resources