Case expression with no target object: should it be used? - ruby

I know you can use the case statement without a target object, like so:
case
when condition1
do_something1
when condition2
do_something2
else
do_something_else
end
This is equivalent to:
if condition1
do_something1
elsif condition2
do_something2
else
do_something_else
end
Is there any reason the case expression allows being used with no target object? Are there any situations where one would want to use the case expression that way?

It may be used to check multiple expressions. Consider this example:
print "Enter first string: "
some_string = gets.chomp
print "Enter second string: "
some_string1 = gets.chomp
puts case
when some_string.match(/\d/)
'String has numbers'
when some_string1.match(/[a-zA-Z]/)
'String has letters'
else
'String has no numbers or letters'
end
Here, you have to check two different variables. Maybe the experts have some different opinion.

There's actually no difference, an empty case statement won't call === since there's nothing to call. Example:
class CaseExample
def ===(other)
puts "received #{other}"
super(other)
end
end
When called like this:
case
when CaseExample.new()
puts "got here"
end
Will print:
"got here"
While:
case "me"
when CaseExample.new()
puts "got here"
end
It will print:
"received me"
I'd rather go for the if/elsif if there isn't a case object to begin with since the intention is going to be clearer.

Related

Can I recall the "case" in case?

I want to recall the case until user writes a or b. I do not want to use "case"
particularly.
I just want to get input from user but not geting something else. If he writes something else, he should need to write until he writes a or b.
str = gets.chomp.to_s
case str
when "a"
print "nice a"
when "b"
puts "nice b"
else
puts "please do it again"
end
class person
attr_accessor :name , :surname #and other attributes
end
#There will be a method here and it will run when the program is opened.
#The method will create the first object as soon as the program is opened.
#The new object that the user will enter will actually be the 2nd object.
puts "What do you want to do?
add
list
out"
process = gets.chomp.to_s
case process
when "add"
#in here user will add new objects of my class
when "list"
#in here user will show my objects
when "out"
puts "Have a nice day"
else
puts "please do it again"
end
In fact, if you look at it, many actions will be taken as a result of the user entering the correct input. what I want to tell is more detailed in this example. According to the input of the user, there will be actions such as calling methods, adding objects, etc.
I wrote most of the code on my computer. But still I couldn't solve my first problem.
Use Kernel#loop
There are a lot of ways to solve this problem, but let's start with a simple Kernel#loop wrapper around your existing code, as that's probably the easiest path forward for you.
loop do
str = gets.chomp.to_s
case str
when "a"
print "nice a"
when "b"
puts "nice b"
else
puts "please do it again"
# restart your loop when not "a" or "b"
next
end
# exit the loop if else clause wasn't triggered
break
end
Use until Control Expression
The loop construct above is pretty straightforward, but it requires you to think about where you need next and break statements for flow control. My own instinct would be to simply call a block until it's truthy. For example, the core logic could be shortened to:
str = nil; until str =~ /a|b/i do str = gets.chomp end; p str
This is a lot shorter, but it's not particularly user-friendly. To leverage this approach while making the solution more communicative and error-resistant, I'd refactor the original code this way:
# enable single-character input from console
require 'io/console'
# make sure you don't already have a value,
# especially in a REPL like irb
str = nil
until str =~ /a|b/ do
printf "\nLetter (a, b): "
str = STDIN.getch.downcase
end
puts "\nYou entered: #{str}"
While not much shorter than your original code, it handles more edge cases and avoids branching. It also seems less cluttered to me, but that's more a question of style. This approach and its semantic intent also seem more readable to me, but your mileage may legitimately vary.
See Also
IO::Console
Control Expressions
"I just want to do something until something else happens" is when you use some sort of while loop.
You can do this:
while true
str = gets.chomp
break unless str == 'a' || str == 'b'
puts "please do it again"
end
You can also use loop do:
loop do
str = gets.chomp
break unless ['a', 'b'].include?(str)
puts "please do it again"
end
puts "Nice #{str}."
Rubyists tend to prefer loop do over while true. They do pretty much the same thing.
One more thing. There's a simpler way to write out arrays of strings:
loop do
str = gets.chomp
break unless %w(a b).include?(str)
puts "please do it again"
end
puts "Nice #{str}."
It doesn't look a whole lot simpler, but if you have, say, 10 strings, it's definitely quicker to type in when you don't have to use all those quotation marks.
As your intuition was telling you, you don't need to use the case statement at all. Like trying to kill a flea with a sledgehammer. The most concise way to do your check is to check whether the input character is included in an array of the desired characters.

Enter with no input as a valid boolean

I'm writing some very simple code, asking for confirmation on a text input, and
what I want to do is that if the users simply presses "Enter", make it count as a "yes". For example:
define method
puts "enter some text"
#text= gets.chomp
puts "you entered '#{#text}', is it correct?"
correct = gets.chomp
if correct == 'y' || ''
other_method
else
method
end
end
But when I run it on Ruby, I get the "Warning, literal string in condition", and whatever you enter, calls the "other_method". The solution I found is the following:
define method
puts "enter some text"
#text= gets.chomp
puts "you entered '#{#text}', is it correct?"
correct = gets.chomp
if correct == 'y'
other_method
elsif correct == ''
other_method
else
method
end
end
But it's pretty annoying, I'd rather understand why the first one doesn't work, and how can I make it work using the | |
Thank you!
What the error is saying is that you are supplying a string (literal) inside of a conditional statement by itself. When you do if correct == "y" || "" you're actually telling it if correct == "y" OR "" and just supplying the string by itself is not a condition.
To fix this you'd simply supply the condition after the operator as well as before it. Ruby does not assume you want the same thing to happen after the ||.
Like this:
define method
puts "enter some text"
#text= gets.chomp
puts "you entered '#{#text}', is it correct?"
correct = gets.chomp
if correct == 'y' || correct == ''
other_method
else
method
end
end
Hope this helps. Happy coding
The solution here is to use Ruby's very versatile case statement to set up a number of "cases" you want to test:
puts "you entered '#{#text}', is it correct?"
case (gets.chomp)
when 'y', 'yes', ''
method_a
else
method_b
end
This can be extended to use regular expressions for even more versatility:
case (gets.chomp)
when /\A\s*y(?:es)?\s*\z/i
method_a
else
method_b
end
Where now anything like "y" or "yes" or "Yes " will work.
When you have bunch of if statements all testing the same variable, consider using a case statement to simplify your logic.
Here is another option using Regex (Docs):
puts "enter some text"
#text= gets.chomp
puts "you entered '#{#text}', is it correct?"
correct = gets.chomp
if /^y?$/ =~ correct # This will match 'y' and empty string both
other_method
else
method
end

How can I make script work in Ruby?

I am new to Ruby.
I need to make this script work:
puts "Do you like cats?"
ask = gets
def ask(n)
if ask == yes
return "I do too"
end
if ask == no
return "Dogs are better"
end
end
puts "#{ask(n)}"
Error message is :
pracif.rb:15:in <main>': undefined local variable or methodn' for
main: Object (NameError)
Here's a script that would work for you :
puts "Do you like cats?"
answer = gets
def ask(n)
if n == 'yes'
return "I do too"
end
if n == 'no'
return "Dogs are better"
end
end
puts ask(answer.downcase.chomp)
Explaination
As the error said you were trying to pass in a variable n which was not defined
Secondly you have a method name ask same as variable name. I've renamed the variable to answer instead
Thirdly, enclose yes and no in quotes
And finally, since you are using gets a \n gets appended like yes\n so none of your conditions would match. So i've used chomp to remove \n. And also used downcase to make input case insensitive.
EDIT
As mentioned by #Jordan in the comments, there is no reason to use string interpolation for the puts statement. So it's enough to call the method directly.
There are a bunch of issues with your code. Try something more like:
def reply(response)
return 'I do too' if response == 'yes'
return 'Dogs are better' if response == 'no'
'Invalid response!'
end
puts 'Do you like cats?'
response = gets().chomp()
puts reply(response)
Pay attention to the variable names. If you keep them descriptive, it is easier to spot mistakes.
Your script has no n local variable defined that you are passing to your ask(n) method at the end.
Rename your ask variable that your script gets from user to answer for example and pass it to your ask method at the end like so:
Updated code to fix other problem I did not see in the first run.
puts "Do you like cats?"
answer = gets.chomp
def ask(n)
(n == 'yes') ? "I do too" : "Dogs are better"
end
puts "#{ask(answer)}"

Looping through a case statement in Ruby?

I am trying to determine the best way to loop through a case statement until a user provides a certain input (in this case, exit).
So far, my code works with a while loop, but it seems a little redundant when I have input = gets.chomp over and over.
Here's a bit of abbreviated code:
input = gets.chomp
while input.downcase != 'exit'
case input.downcase
when 'help'
puts "Available commands are..."
input = gets.chomp
#more when statements go here...
else
puts "That is not a valid command. Type 'HELP' for available commands."
input = gets.chomp
end
end
puts "Goodbye!"
I'd write it like:
loop do
input = gets.chomp.downcase
case input
when 'help'
puts "Available commands are..."
# more when statements go here...
when 'exit'
break
else
puts "That is not a valid command. Type 'HELP' for available commands."
end
end
puts "Goodbye!"
A loop is designed for this sort of case, where you just want to loop cleanly, and then eventually break out on some condition.
For ultimate clarity in what the code is doing, I'd put the exit immediately after reading the input, instead of being embedded in the case statements. It's a minor thing, but is useful to remember if you're coding and others have to help maintain it:
loop do
input = gets.chomp.downcase
break if input == 'exit'
case input
when 'help'
puts "Available commands are..."
# more when statements go here...
else
puts "That is not a valid command. Type 'HELP' for available commands."
end
end
puts "Goodbye!"
Why don't you change the while to:
while (input = gets.chomp.downcase) != 'exit'
Note that this also means that instead of using case input.downcase, you can use case input, as it has already been made lowercase.
edit: my roots in C betray me...
As mentioned in the comments, this is not a particularly "ruby-esque" solution. It also causes a stack trace when gets returns nil. You might prefer to split it into two lines:
while (input = gets)
input = input.chomp.downcase
break if input == 'exit'
case input
# when statements
else
puts "That is not a valid command. Type 'HELP' for available commands."
end
end
I separated the "exit" from the case criteria for a couple of reasons:
It was part of the loop logic in the question, so it's (arguably) more readable to keep it separate from the other cases.
I didn't realise that break behaves differently in Ruby case statements to other languages that I am more familiar with, so I didn't think it would do what you wanted.
You could equally well do this:
while (input = gets)
case input.chomp.downcase
when 'exit'
break
# other when statements
else
puts "That is not a valid command. Type 'HELP' for available commands."
end
end
edit: I am delighted to hear that like Perl, Ruby also has the $_ variable, to which the value of gets will be assigned:
while gets
case $_.chomp.downcase
when 'exit'
break
# other when statements
else
puts "That is not a valid command. Type 'HELP' for available commands."
end
end
You can even get rid of input by exiting the loop with break instead of checking the result in the while condition
while true
case gets.chomp.downcase
when 'exit'
break
when 'help'
# stuff
else
# other stuff
end
end

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