Ruby case statement on a hash? - ruby

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!

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.

include works with if but not else

I doing a Ruby botcamp. I'm supposed to write code that replaces all user input of 's' with 'th' so it reads like Daffy Duck is speaking. If I enter an s it will be replaced with th. That works! But If I don't enter an 's' it's supposed to print that none were included in my elsif statemnt. Instead I'm getting the error 'undefined method `include?' for nil:NilClass'. Other than that error, the interpretor is telling me the code is good.
print "Input a string: "
user_input=gets.chomp.downcase!
if user_input.include?"s"
user_input.gsub!(/s/, "th")
puts "Your string is #{user_input}!"
elsif
puts "There are no s's in your string!"
end
Any ideas on what I need to change?
You need to be careful with built-in ruby methods that end with an exclamation point (!). A lot of them will return nil if no changes were made:
'test'.downcase! # => nil
'Test'.downcase! # => "test"
Since you are assigning the result to a variable, there's no need to use the exclamation point method, since those modify in-place, you can just use the normal downcase method.
'test'.downcase # => "test"
You also later on have an elsif with no condition, that should probably just be an else. It's actually executing the first line of the "body" of the elsif as the conditional:
if false
puts "a"
elsif
puts "b" # recall that `puts` returns `nil`
puts "c"
else
puts "d"
end
This results in
b
d
being output

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?

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)}"

How to return true/false in a block in ruby

I'm doing something like this:
myarray.delete_if{ |x|
#some code
case x
when "something"
return true
when "something else"
return false
end
The "return" statement seems wrong, and I can't figure out the right syntax, I understand the simplistic form of: myarray.delete_if{ |x| x == y }, but not when my desire to return true/false is more procedural as in the case statement example.
Just remove return. In Ruby, the last value evaluated is used as return value.
myarray = ["something", "something else", "something"]
myarray.delete_if { |x|
#some code
case x
when "something"
true
when "something else"
false
end
}
myarray # => ["something else"]
You can use next if you want to be explicit.
You do not need to particularly condition the false cases. They can be nil by default if you do not condition them.
myarray.delete_if do |x|
...
case x
when "something" then true
end
end
or even better:
myarray.delete_if do |x|
...
"something" === x
end
I do not know what you have in the ... part, but if you just want to remove a certain element from an array, you can do:
myarray.delete("something")
and if you want to get back the receiver, then:
myarray.tap{|a| a.delete("something")}

Resources