I have a curiosity with variable definition. I know about variable definition, it's just to understand how it works. The idea is to set myvar to 0 if it is not defined. I don't want to use false because false could be returned by false or nil.
def foo
myvar=1000
myvar = ((defined? myvar)==nil) ? 0 : myvar
puts myvar
end
foo # => 1000
but
def foo
# myvar=1000
myvar = ((defined? myvar)==nil) ? 0 : myvar
puts myvar
end
foo # => nil
but
def foo
mybool = ((defined? myvar)==nil)
myvar = mybool ? 0 : myvar
puts myvar
end
foo # => 0 it rocks !
It's like if the boolean test was affected to myvar before the final test was evaluated.
((defined? myvar)==nil) gives 2 possibilities defined? myvar could be nil or the right side of test nil could be nil (it's nil).
To get rid of the nil part of this test, I tried this code :
def foo
mylocalvariable = 2 # it gives 'local-variable' and not nil
puts ((defined? myvar) == (defined? mylocalvariable ))
myvar = ((defined? myvar)!=(defined? mylocalvariable )) ? 0 : myvar
puts myvar
end
foo # => nil
It seems the first part of the test was affected to myvar, but assignment operators come after () or after comparison operator. Do you have an idea? I don't want an other code but just why it works like that.
Thank you.
You might not want other code, but you need some because you're going the long way around to accomplish something that is simple. If you want to set an undefined variable to 0 use:
myvar ||= 0
How does it work? ||= is a conditional assignment operator, that basically looks at the variable being assigned to on the left, and, if it's nil or false, assigns the value on the right to it. It's a trick found in many languages.
Ternary (?:) statements are useful for small if/then/else statements, but aren't really the right choice for what you want to do. Instead, if you want to use something besides ||=, use:
myvar = 0 unless myvar
Again, all of these will reassign myvar if it happens to be a false value, not just if it's nil. That's a potential problem, but, as developers, we're supposed to be in control and should have a good idea what type of value a variable is holding at any moment so it shouldn't be that big of a problem.
Hi this kind of operator is ternary operator.
Example:
#x = 1
#y = 2
#x>2 ? puts "YES" : puts "NO"
You can also check if it for an ActiveRecord
Example:
#x = User.find(:all, :conditions => ["id = ?", 1])
#x.nil? ? puts "Null" : puts "Not Null"
I know why. When you code myvar= and you test defined?myvar, ruby consider myvar defined. If you remove myvar= , myvar is considered undefined in the test.
The left part is read before the right part.
Related
Today I came across an interesting piece of code. It's more like a scientific question about the ruby parser.
We know everything in ruby is an object and every expression evaluates at least to nil.
But how is the following "assignment" parsed:
somevar
NameError: undefined local variable or method 'somevar' for main:Object
somevar = "test" if false
=> nil
somevar
=> nil
You see the variable is undefined until it's used in the assignment. But the assignment is not happening because of the condition. Or is it happening because the condition evaluates to nil? I tried something which would break in this case, but it just works:
a = {}
a[1/0]
ZeroDivisionError: divided by 0
a[1/0] = "test" if false
=> nil
So is this meant to work the way it is? Or does it make sense to test the variable (defined?(somevar)) before accessing, in case a future version of ruby will break this behaviour? As example by saving the assigned pointer to this variable.
My currently used ruby version is 3.0.2.
This is expected behavior in Ruby. Quote from the Ruby docs:
The local variable is created when the parser encounters the assignment, not when the assignment occurs:
a = 0 if false # does not assign to a
p local_variables # prints [:a]
p a # prints nil
If you do = "test" if false it evaluates to nil => no assignment needed. But by calling somevar = ... you told the interpreter to declare the name somevar. The nil aren't the same (if that makes sense).
The [] operator however doesn't declare a variable (only accesses) but since if false isn't true there is no assignment so the whole left side isnt evaluated.
Consider:
a = [1,2,3]
a[1] = "test" if false
a
=> [1,2,3]
a[1] is neither nil nor test.
Not sure what you expect or how future Ruby will break this?
In Ruby, I am trying to write a line that uses a variable if it has been set, otherwise default to some value:
myvar = # assign it to ENV['MY_VAR'], otherwise assign it to 'foobar'
I could write this code like this:
if ENV['MY_VAR'].is_set? #whatever the function is to check if has been set
myvar = ENV['MY_VAR']
else
myvar = 'foobar'
end
But this is rather verbose, and I'm trying to write it in the most concise way possible. How can I do this?
myvar = ENV['MY_VAR'] || 'foobar'
N.B. This is slightly incorrect (if the hash can contain the value nil) but since ENV contains just strings it is probably good enough.
The most reliable way for a general Hash is to ask if it has the key:
myvar = h.has_key?('MY_VAR') ? h['MY_VAR'] : 'default'
If you don't care about nil or false values (i.e. you want to treat them the same as "not there"), then undur_gongor's approach is good (this should also be fine when h is ENV):
myvar = h['MY_VAR'] || 'foobar'
And if you want to allow nil to be in your Hash but pretend it isn't there (i.e. a nil value is the same as "not there") while allowing a false in your Hash:
myvar = h['MY_VAR'].nil? ? 'foobar' : h['MY_VAR']
In the end it really depends on your precise intent and you should choose the approach that matches your intent. The choice between if/else/end and ? : is, of course, a matter of taste and "concise" doesn't mean "least number of characters" so feel free to use a ternary or if block as desired.
hash.fetch(key) { default_value }
Will return the value if it exists, and return default_value if the key doesn't exist.
This works best for me:
ENV.fetch('VAR_NAME',"5445")
myvar = ENV.fetch('MY_VAR') { 'foobar' }
'foobar' being the default if ENV['MY_VAR'] is unset.
Although it's not relevant in the specific example you gave since you're really asking about hash keys, not variables, Ruby does give a way to check variable definition. Use the defined? keyword (it's not a method, but a keyword since it needs special handling by the interpreter), like so:
a = 1
defined? a
#=> "local-variable"
#a = 2
defined? #a
#=> "instance-variable"
##a = 3
defined? ##a
#=> "class-variable"
defined? blahblahblah
#=> nil
Hence you could do:
var = defined?(var) ? var : "default value here"
As far as I know, that's the only way other than an ugly begin/rescue/end block to define a variable in the way that you ask without risking a NameError. As I said, this doesn't apply to hashes since:
hash = {?a => 2, ?b => 3}
defined? hash[?c]
#=> "method"
i.e. you're checking that the method [] is defined rather than the key/value pair you're using it to access.
Another possible alternative, which will work even if ENV['MY_VAR'] turnsout to be a false value
myvar = ENV['MY_VAR'].presence || 'foobar'
The Demand gem which I wrote allows this to be extremely concise and DRY:
myvar = demand(ENV['MY_VAR'], 'foobar')
This will use ENV['MY_VAR'] only if it is present. That is, it will discard it just if it's nil, empty or a whitespace-only string, giving the default instead.
If a valid value for ENV['MY_VAR'] is falsy (such as false), or an invalid value is truthy (such as ""), then solutions like using || would not work.
I am new guy to Ruby, post the answer I found at 2021, maybe useful for someone.
check if env key exists:
include?(name) → true or false
has_key?(name) → true or false
member?(name) → true or false
key?(name) → true or false
get env with default value:
ENV.fetch(name, :default_val)
ref: https://docs.ruby-lang.org/en/master/ENV.html
myvar = ENV['MY_VAR'].is_set? ? ENV['MY_VAR'] : 'foobar'
This way you keep the .is_set? method.
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.
I am using Ruby on Rails 3 and I have this situation:
In a controller I have
# first statement
if a == true
flag = true
end
# second statement
if flag == true
// code1
else
// code2
end
If a is false, what happens in the if statement without having initialized the flag variable? That is, is the flag variable "always"/"in any case" set to NOT TRUE?
Is it a safe approach?
You can try this sort of thing out in irb:
>> if false
>> foo = "bar"
>> end
=> nil
>> foo
=> nil
So you can see that even though the if condition is false, the foo variable is introduced (it is nil, which will be treated the same as false in your if, so you are safe). I suspect that this is because if statements don't introduce a new scope. See:
>> if true
>> bar = "baz"
>> end
=> "baz"
>> bar
=> "baz"
One final thought: If A is really just a boolean, you could set flag = A and avoid the if altogether.
An unassigned variable is equal to nil but constants work a little differently. With a variable anything other than nil or false will evaluate to true. With a constant you need to use definded?(CONSTANT) so your code would look like this:
# first statement
flag = A if defined?(A)
# second statement
if flag
puts "Code 1"
else
puts "Code 2"
end
# Or if you don't need the flag variable
if defined?(A) && A
puts "Code 1"
else
puts "Code 2"
end
Output:
Code 2
Code 2
How can I check whether a variable is defined in Ruby? Is there an isset-type method available?
Use the defined? keyword (documentation). It will return a String with the kind of the item, or nil if it doesn’t exist.
>> a = 1
=> 1
>> defined? a
=> "local-variable"
>> defined? b
=> nil
>> defined? nil
=> "nil"
>> defined? String
=> "constant"
>> defined? 1
=> "expression"
As skalee commented: "It is worth noting that variable which is set to nil is initialized."
>> n = nil
>> defined? n
=> "local-variable"
This is useful if you want to do nothing if it does exist but create it if it doesn't exist.
def get_var
#var ||= SomeClass.new()
end
This only creates the new instance once. After that it just keeps returning the var.
The correct syntax for the above statement is:
if (defined?(var)).nil? # will now return true or false
print "var is not defined\n".color(:red)
else
print "var is defined\n".color(:green)
end
substituting (var) with your variable. This syntax will return a true/false value for evaluation in the if statement.
defined?(your_var) will work. Depending on what you're doing you can also do something like your_var.nil?
Try "unless" instead of "if"
a = "apple"
# Note that b is not declared
c = nil
unless defined? a
puts "a is not defined"
end
unless defined? b
puts "b is not defined"
end
unless defined? c
puts "c is not defined"
end
WARNING Re: A Common Ruby Pattern
the defined? method is the answer. See the accepted answer above.
But watch out... consider this common ruby pattern:
def method1
#x ||= method2
end
def method2
nil
end
method2 always returns nil.
The first time you call method1, the #x variable is not set - therefore method2 will be run. and
method2 will set #x to nil.
But what happens the second time you call method1?
Remember #x has already been set to nil. But method2 will still be run again!! If method2 is a costly undertaking this might not be something that you want.
Let the defined? method come to the rescue:
def method1
return #x if defined? #x
#x = method2
end
As with most things, the devil is in the implementation details.
Use defined? YourVariable
Keep it simple silly .. ;)
Here is some code, nothing rocket science but it works well enough
require 'rubygems'
require 'rainbow'
if defined?(var).nil? # .nil? is optional but might make for clearer intent.
print "var is not defined\n".color(:red)
else
print "car is defined\n".color(:green)
end
Clearly, the colouring code is not necessary, just a nice visualation in this toy example.
You can try:
unless defined?(var)
#ruby code goes here
end
=> true
Because it returns a boolean.
As many other examples show you don't actually need a boolean from a method to make logical choices in ruby. It would be a poor form to coerce everything to a boolean unless you actually need a boolean.
But if you absolutely need a boolean. Use !! (bang bang) or "falsy falsy reveals the truth".
› irb
>> a = nil
=> nil
>> defined?(a)
=> "local-variable"
>> defined?(b)
=> nil
>> !!defined?(a)
=> true
>> !!defined?(b)
=> false
Why it doesn't usually pay to coerce:
>> (!!defined?(a) ? "var is defined".colorize(:green) : "var is not defined".colorize(:red)) == (defined?(a) ? "var is defined".colorize(:green) : "var is not defined".colorize(:red))
=> true
Here's an example where it matters because it relies on the implicit coercion of the boolean value to its string representation.
>> puts "var is defined? #{!!defined?(a)} vs #{defined?(a)}"
var is defined? true vs local-variable
=> nil
It should be mentioned that using defined to check if a specific field is set in a hash might behave unexpected:
var = {}
if defined? var['unknown']
puts 'this is unexpected'
end
# will output "this is unexpected"
The syntax is correct here, but defined? var['unknown'] will be evaluated to the string "method", so the if block will be executed
edit: The correct notation for checking if a key exists in a hash would be:
if var.key?('unknown')
Please note the distinction between "defined" and "assigned".
$ ruby -e 'def f; if 1>2; x=99; end;p x, defined? x; end;f'
nil
"local-variable"
x is defined even though it is never assigned!
defined? is great, but if you are in a Rails environment you can also use try, especially in cases where you want to check a dynamic variable name:
foo = 1
my_foo = "foo"
my_bar = "bar"
try(:foo) # => 1
try(:bar) # => nil
try(my_foo) # => 1
try(my_bar) # => nil
Also, you can check if it's defined while in a string via interpolation, if you code:
puts "Is array1 defined and what type is it? #{defined?(#array1)}"
The system will tell you the type if it is defined.
If it is not defined it will just return a warning saying the variable is not initialized.
Hope this helps! :)
Leaving an incredibly simple example in case it helps.
When variable doesn't exist:
if defined? a then "hi" end
# => nil
When variable does exist:
a = 2
if defined? a then "hi" end
# => "hi"