What condition is my Ruby conditional in? - ruby

The following conditional syntax displays the string 'is true' in irb without using puts
irb(main):001:0> if true
irb(main):002:1> 'is true'
irb(main):003:1> else
irb(main):004:1* 'is false'
irb(main):005:1> end
=> "is true"
...yet when I invoke the same syntax in a script and run it from the command line, it gets ignored. Why?
# Odd behaviour:
puts "Why do only two of the three conditionals print?"
# This doesn't put anything to screen:
if true
'is true_1'
else
'is false'
end
puts "Seriously, why? Or better yet: how?"
# But this does:
if true
puts 'is true_2'
else
puts 'is false'
end
# And this works without "puts":
def truthiness
if 1.send(:==, 1)
'is true_3'
else
'is false'
end
end
puts truthiness
puts "Weird."
When I run this as a script, it displays:
"Why do only two of the three conditionals print?
Seriously, why? Or better yet: how?
is true_2
is true_3
Weird."
FWIW, I am following along with Sandi Metz's talk "Nothing is Something"
https://youtu.be/zc9OvLzS9mU
...and listening to this:
https://youtu.be/AULOC--qUOI
Apologies as I am new to Ruby and trying to wrap my head around how it does what it does.
EDIT:
Useful resources:
http://ruby-doc.org/core-2.3.1/Kernel.html#method-i-puts
https://softwareengineering.stackexchange.com/questions/150824/is-the-puts-function-of-ruby-a-method-of-an-object

The IRB output here is showing the return value of the operation, which is not necessarily what is printed to STDOUT (i.e. the terminal) during execution.
Your script is just throwing the return value away, you would have to do this:
val = if true
'is true_1'
else
'is false'
end
puts val

Related

Why is code after `else` in a multi-line conditional considered valid ruby syntax?

I was surprised to see that (in Ruby 2.7.4) it is possible to add code after the else keyword, as I've never encountered this before. The following code is considered valid by ruby's built-in syntax checker, and runs quite happily:
if false
puts 'noop'
else puts 'why is this possible'; 'not returned'
puts 'and not a syntax error?'
'returned'
end
# # Output
# why is this possible
# and not a syntax error?
# => "returned"
The in-line code after else is interpreted (puts 'why is this possible') , but the result ('not returned') is discarded.
However if I add return to the else line things get really unexpected:
if false
puts 'noop'
else puts 'why is this possible'; return 'inline returned'
puts 'this is now not called'
'not returned'
end
# # Output
# why is this possible
# => "inline returned"
I don't know what this would be called, so I'm unable to google it – I can't find this documented or mentioned anywhere and don't know if it is an intentional language feature (possibly a side-effect of the single-line if/then/else syntax...?).
Can anybody shed any light on why this works, and if there is a valid use-case for it?
It might be more obvious with explicit line breaks. Your first example is equivalent to:
if false
puts 'noop'
else
puts 'why is this possible'
'not returned'
puts 'and not a syntax error?'
'returned'
end
The 'not returned' string literal doesn't do anything on its own. What's left are two puts calls and a return value of 'returned'.
Your second example:
if false
puts 'noop'
else
puts 'why is this possible'
return 'inline returned'
puts 'this is now not called'
'not returned'
end
Here, the return keyword will exit the enclosing method after the first puts call right-away with a return value of 'inline returned'. Hence, the following puts and the implicit return value of 'not returned' are ignored.

Ruby script ignores conditional and breaks method without throwing errors

I have this (I believe) straight and easy method meant to verify if a certain string only includes numbers and isn't empty.
class String
def is_number?
puts "Here it's working, 1"
if self.scan(/\D/).empty? and self != ""
return true
puts "true"
else
return false
puts "false"
end
puts "Here it's working, 2"
end
end
"asd".is_number?
puts "Here it's working, 3"
The result is quite astonishing to me:
The method works until before the conditional. At that point it doesn't go with the "then" nor the "else" options (which, up to today, I never thought to be an option too), and instead breaks the method. Then, it proceeds to the following command. Finally, at the end of the program it sits there without throwing any error.
I honestly don't know how to proceed at this point.
When you used return in a method it will not execute any code after that, if you are expecting true/false to print you should put it above the return statement
def is_number?
puts "Here it's working, 1"
if self.scan(/\D/).empty? and self != ""
puts "true"
return true
else
puts "false"
return false
end
puts "Here it's working, 2"
end
Note:- "Here it's working, 2" statement will never execute as there will be return statement before that.
it doesn't go with the "then" nor the "else" options
No, this is not what happens here, as described in the answer from Salil.
For the future, if you formulate a hypothesis about your code, you should prove or disprove it. Not for us, for yourself. Else how do you know this is actually what is happening?
For example, something like this would reliably verify that the control does indeed enter one of the conditional branches.
if self.scan(/\D/).empty? and self != ""
#return true
#puts "true"
raise "error from if branch"
else
#return false
#puts "false"
raise "error from else branch"
end

How to convert this if condition to unless?

if array.present?
puts "hello"
end
There is no else part to this.
How to write the above if condition using unless.
I'm asking this question because of this lint error:
Use a guard clause instead of wrapping the code inside a conditional expression
Regarding your comment:
I'm asking this question because of this lint error
Use a guard clause instead of wrapping the code inside a conditional expression
This means that instead of:
def foo(array)
if array.present?
puts "hello"
end
end
You are supposed to use:
def foo(array)
return unless array.present?
puts "hello"
end
See https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals
If this is a Rails question (is it?), you can also use blank?:
def foo(array)
return if array.blank?
puts "hello"
end
There's no reason to.
Remember: unless is the inverse of if (or !if if you rather), and is only intended to make your code easier to read.
Using unless with your expression would be incredibly awkward, because you're now moving the actual body of work to an else statement...
unless array.present?
return
else
puts "hello"
end
...which doesn't make your code any easier to read if you had stuck with a negated if:
if !array.present?
return
else
puts "hello"
end
Don't use unless here. You lose readability in exchange for virtually nothing.
One-liner:
puts "hello" unless !array.present?
However, I would recommend:
puts "hello" if array.present?
unless array.present?
return
else
puts "hello"
end
OP requested one-liner modification:
Pseudocode:
something unless condition
Therefore:
puts "hello" unless !array.present?

Strange result in Ruby with "end if"

I expect this code to execute the code block and result in the output "x" and "y", or just to throw a syntax error:
if true
puts "x"
end if
puts "y"
However, the interpreter ignores the if true block and only executes puts "y". If I instead enter the following code:
if true
puts "x"
end if
the interpreter exits with an end-of-input syntax error. Is there a reason why the first snippet is valid code but somehow executing wrong? It would seem to me that there is some error in the parser.
I've confirmed this in Ruby 2.1.2 as well as Ruby 2.1.5.
There are two things playing together here:
The return value of the puts is nil
Ruby is usually clever enough to read the next line if the current command hasn't ended yet.
That means:
if true
puts "x"
end if
puts "y"
is the same than:
if true
puts "x"
end if (puts "y")
Ruby evaluates puts "y" to nil:
if true
puts "x"
end if nil
What leads Ruby to not evaluate the if true block, because if nil acts like if false.
Or in other words: Your example is the same as:
if puts("y") # evaluates to nil (aka is falsey)
if true
puts "x"
end
end
I think what you mean to write is
if true
puts "x"
end
puts "y"
That would produce the output you expect.
Your code is incorrect. The correct code is:
if true
puts "x"
end
puts "y"
Your code tells Ruby to execute the if true ... end block if puts "y" returns true.
puts returns nil, which amounts to false in a condition check, leading to the block not being executed at all.
Your code is effectively saying only to execute the if true block only if puts "y" returns true. Unfortunately, puts returns nil. To end an if statement in Ruby, you simple have to use end. Unlike in Shell Scripting or Visual Basic, there is no specific end statements for different blocks.
Change
if true
puts "x"
end if
puts "y"
to
if true
puts "x"
end
puts "y"
And you'll be golden.

Ruby case statement on a hash?

This is going to sound weird, but I would love to do something like this:
case cool_hash
when cool_hash[:target] == "bullseye" then do_something_awesome
when cool_hash[:target] == "2 pointer" then do_something_less_awesome
when cool_hash[:crazy_option] == true then unleash_the_crazy_stuff
else raise "Hell"
end
Ideally, I wouldn't even need to reference the has again since it's what the case statement is about. If I only wanted to use one option then I would "case cool_hash[:that_option]", but I'd like to use any number of options. Also, I know case statements in Ruby only evaluate the first true conditional block, is there a way to override this to evaluate every block that's true unless there is a break?
You could also use a lambda:
case cool_hash
when -> (h) { h[:key] == 'something' }
puts 'something'
else
puts 'something else'
end
Your code is very close to being valid ruby code. Just remove the variable name on the first line, changing it to be:
case
However, there is no way to override the case statement to evaluate multiple blocks. I think what you want is to use if statements. Instead of a break, you use return to jump out of the method.
def do_stuff(cool_hash)
did_stuff = false
if cool_hash[:target] == "bullseye"
do_something_awesome
did_stuff = true
end
if cool_hash[:target] == "2 pointer"
do_something_less_awesome
return # for example
end
if cool_hash[:crazy_option] == true
unleash_the_crazy_stuff
did_stuff = true
end
raise "hell" unless did_stuff
end
I think, following is the better way to do the stuff you want.
def do_awesome_stuff(cool_hash)
case cool_hash[:target]
when "bullseye"
do_something_awesome
when "2 pointer"
do_something_less_awesome
else
if cool_hash[:crazy_option]
unleash_the_crazy_stuff
else
raise "Hell"
end
end
end
Even in case's else part you can use 'case cool_hash[:crazy_option]' instead of 'if' if there are more conditions. I prefer you to use 'if' in this case because there is only one condition.
in ruby 3.0 you can do the following with pattern matching
# assuming you have these methods, ruby 3 syntax
def do_something_awesome = "something awesome 😎"
def do_something_less_awesome = "something LESS awesome"
def unleash_the_crazy_stuff = "UNLEASH the crazy stuff πŸ€ͺ"
you can do
def do_the_thing(cool_hash)
case cool_hash
in target: "bullseye" then do_something_awesome
in target: "2 pointer" then do_something_less_awesome
in crazy_option: true then unleash_the_crazy_stuff
else raise "Hell"
end
end
will return
do_the_thing(target: "bullseye")
=> "something awesome 😎"
do_the_thing(target: "2 pointer")
=> "something LESS awesome"
do_the_thing(crazy_option: true)
=> "UNLEASH the crazy stuff πŸ€ͺ"
in ruby 2.7 it still works
# need to define the methods differently
def do_something_awesome; "something awesome 😎"; end
def do_something_less_awesome; "something LESS awesome"; end
def unleash_the_crazy_stuff; "UNLEASH the crazy stuff πŸ€ͺ"; end
# and when calling the code above to do the switch statement
# you will get the following warning
warning: Pattern matching is experimental, and the behavior may change
in future versions of Ruby!

Resources