Switch Case in Ruby - ruby

I am trying to do a case statement. The code looks like this:
def true?(verbosity)
verb = verbosity.to_s
case verb
when verb.match?('yes')
true
when verb.match?('y')
return true
when verb.match('ja')
true
when verb.match?('j')
true
when verb.to_i(10).eql?(1)
true
else
false
end
end
Regardless of what I write in the case statement, even when the debugger says options[:verbosity] is "yes", the case statement instantly jumps to false and leaves the function. I even added explicit casting to it as string.
How do I have to write the statement to get a valid evaluation?

In this form, when you want to evaluate all case conditions separately, you should omit verb in the beginning, like this:
case
when verb.match?('yes')
true
when verb.match?('ja')
true
# ...
that said, don't you think it would be easier to read and nicer if you used regular expression magic to make this whole method much shorter? I'm thinking of something like this:
def true?(verbosity)
verb = verbosity.to_s
verb.match?(/yes|y|ja|j/i) || verb.to_i.eql?(1)
end

Here's a direct fix:
def true?(verbosity)
verb = verbosity.to_s
# If you wish to reference `verb` below in the branches like this,
# then DON'T make it the subject of the `case` statement:
case
when verb.match?('yes')
true
when verb.match?('y')
return true
when verb.match('ja')
true
when verb.match?('j')
true
when verb.to_i(10).eql?(1)
true
else
false
end
end
Or, here's a cleaner use of a case statement (without changing any behaviour above):
def true?(verbosity)
case verbosity.to_s
when /yes/, /y/, /ja/, '1'
true
else
false
end
end
...But this is doesn't quite do what you want, since it will return true for e.g. "yellow", but false for e.g. "TRUE". So, how about:
def true?(verbosity)
case verbosity.to_s
when /\A(yes|y|ja|1)\z/i
true
else
false
end
end
..But at this point, you may be thinking - why bother with a case statement here at all?? The other answer has already shown how you can take this a step further by removing the case statement; or alternatively you could even do this in 1 line and without a regex:
def true?(verbosity)
%w[yes y ja j 1].include?(verbosity.to_s.downcase)
end
Also, I note that there is some ambiguity in your post about whether this parameter is supposed to be called options[:verbose] or options[:verbosity]. It's unclear whether or not this has manifested as a bug in your complete code, but I felt it's worth mentioning.

Related

ruby: how to combine if and unless

I have following sample code using if and unless
if key.start_with('abc')
do something
end
unless key.start_with('abc')
do that
end
is this the correct way to combine both if and unless or is there any other way?
if key.start_with('abc')
do something
elsif !key.start_with('abc')
do that
else
do this
end
Thanks
You might be conflating terms here. In Ruby, unless is equivalent to if !condition.
If you want to ensure that something either does or doesn't happen based on a single condition, all you need to check is that condition. In your example, it would be:
if key.start_with('abc')
do something
else
do that
end
Either key.startWith('abc') is true or false. There's no reason to check the additional false statement, since it must be false after execution.
If the check if key.start_with('abc') fails, then that means that it does not start with abc, so there's no need to check for that condition. They are direct opposites, so if one is true, the other is false, therefore, a plain if/else is sufficient:
if key.start_with('abc')
do something
else
do that
end
As I stated in my comment, how you set up your if/elsif/else is dependent on how your conditions are related to one another. If they are all mutually exclusive, then it can simply be something like:
if condition1
do first thing
elsif condition2
do 2nd thing
else
do fallback thing
end
However, there are many different ways you might want to check:
if condition1
do a thing
elsif condition2 || condition3
do something if 1 is false, but either either 2 or 3 are true
elsif condition4
do something if 1,2, & 3 are all false
elsif condition5 && condition6
do something only if both 5 and 6 are true (and all above are false)
etc, etc
Checkout the ruby style guide:
https://github.com/bbatsov/ruby-style-guide#no-else-with-unless
I would stay away from unless for most control flow cases as it quickly becomes confusing, except for the following:
a one liner:
do that unless key.start_with('abc')
or a guard clause:
return unless key.start_with('abc')

Ruby Conditionals/Case Expression

I'm fairly new to code and I have a quick question on Ruby conditionals, specifically Case Expressions. I have a method in which I want to return a string "odd" if the string length is odd and "even" if the string length is even.
Simple stuff I know and I can get the results using a if/else conditional, however the case expression just returns 'nil'. Any help would be greatly appreciated.
def odd_or_even(string)
case string
when string.length.even? then "even"
when string.length.odd? then "odd"
end
end
odd_or_even("Ruby") # Wanting to return even rather than nil
odd_or_even("Rails") # Wanting to return odd rather than nil
You've written your case statement wrong. It takes two forms, which is unusual compared to other languages. The first form takes an argument, and that argument is compared to all possible cases. The second form is without argument and each case is evaluated independently.
The most minimal fix is this:
def odd_or_even(string)
case
when string.length.even? then "even"
when string.length.odd? then "odd"
end
end
This was because to Ruby your code looked like this when calling with the argument "Ruby":
def odd_or_even(string)
case string
when true then "even"
when false then "odd"
end
end
Your string value does not match true or false so you get nil from a non-matching situation.
You can clean up your code considerably. Consider: Can something be not even and not odd? Not really:
def odd_or_even(string)
string.length.even? ? 'even' : 'odd'
end
case something
when condition1
expression1
when condition2
expression2
else
default_expression
end
is equivalent to
if condition1 === something
expression1
elsif condition2 === something
expression2
else
default_expression
end
Note that case-when internally uses ===, an operator (method) which can be overridden.

Why are these two methods that seem the same operate differently?

My method exist­s_else takes two parameters: base and fallback. If base is nil, it returns fallback. If it's not nil, it returns base. A call to exist­s_else(true, false) should return true.
If I use a standard looking if-statement, true is returned like I thought it would be:
def exist­s_else(bas­e, fallb­ack)
unless base.­nil?
base
else
fallb­ack
end
end
a = true
exists_els­e( a, false­ )
# => true
If I use the inline implementation shown below, it returns false.
def exist­s_else(base, fallback)
base unles­s base.nil­? else fallback
end
a = true
exists_els­e( a, false­ )
# => false
Why does does it return false in the inline implementation?
Your assertion that
base unles­s base.nil­? else fallback
is supposed to be equivalent to the long-form unless statement is not true; in fact, you cannot use else inside a post condition. Ruby is interpreting the code as:
def exist­s_else(base, fallback)
base unles­s base.nil­?
else fallback
end
If you type this (or the version without the newline, as in your question) into IRB, Ruby gives the following warning:
warning: else without rescue is useless
That is to say, Ruby is trying to interpret the else as part of exception handling code, as in
def exists_else(base, fallback)
base unless base.nil
rescue ArgumentError => e
# code to handle ArgumentError here
else
# code to handle other exceptions here
end
You can not use an else statement when you're trying to do it in one line. When an else is necessary you must use the extended version.
Ruby thinks that the else in this case is related to error handling. You have to stick to your initial unless-end method.
I prefer this syntax to evaluate true/false checks:
condition(s) ? true_value : false_value
In your case, it would look like:
def exists_else(base, fallback)
base.nil? ? fallback : base
end
a = true
puts exists_else(a, false) # => true

Is there a simple way to evaluate a value to true/false without using an expression?

Is there a simple way in Ruby to get a true/false value from something without explicitly evaluating it to true or false
e.g. how would one more succinctly express
class User
def completed_initialization?
initialization_completed == 1 ? true : false
end
end
is there some way to do something along the lines of
initialization_completed.true?
There's obviously not much in it but since I'm in the zen garden of Ruby I might as well embrace it
EDIT (I've updated the example)
This question was extremely badly phrased as was very gently pointed out by #Sergio Tulentsev. The original example (below) does of course evaluate directly to a boolean. I'm still struggling to find an example of what I mean however Sergio's double-negative was in fact exactly what I was looking for.
Original example
class User
def top_responder
responses.count > 10 ? true : false
end
end
> operator already returns boolean value. So it can be just
def top_responder
responses.count > 10
end
To convert arbitrary values to booleans, I offer you this little double-negation trick.
t = 'foo'
!!t # => true
t = 1
!!t # => true
t = 0
!!t # => true
t = nil
!!t # => false
The first negation "casts" value to boolean and inverts it. That is, it will return true for nil / false and false for everything else. We need another negation to make it produce "normal" values.

trouble with this case statement Ruby

Can someone help me understand how to write this case statement properly its not working and as a NOOB I have no idea how to fix it:
def hide_link?(link, mailing)
case link
when 'edit' && ['sent', 'sending', 'archived'].include?(mailing.status)
return true
when 'send_schedule' && ['sent', 'sending', 'archived'].include?(mailing.status)
return true
when 'archive' && ['archived'].include?(mailing.status)
puts "I should be in here"
return true
else 'dashboard' && ['sending', 'draft'].include?(mailing.status)
return true
end
end
Basically I want to return true when the link matches certain criteria.
I believe that if link doesn't match these criterias the method should return false. Thus:
def hide_link?(link, mailing)
case link
when 'edit'
['sent', 'sending', 'archived'].include?(mailing.status)
when 'send_schedule'
['sent', 'sending', 'archived'].include?(mailing.status)
when 'archive'
puts "I should be in here"
['archived'].include?(mailing.status)
when 'dashboard'
['sending', 'draft'].include?(mailing.status)
else
false
end
end
The construction [...].include?(mailing.status) has result true or false which will be returned as a result of hide_link? method.
Remove return.
link = "fred"
case link
when "fred"
true
else
false
end
case will return the value itself which will then be passed to the method.
Refactor of megas's version:
def hide_link?(link, mailing)
statuses_to_hide = case link
when 'edit', 'send_schedule'
%w{sent sending archived}
when 'archive'
%w{archived}
when 'dashboard'
%w{sending draft}
else
[]
end
statuses_to_hide.include?(mailing.status)
end
The conditions in the case statement all follow the same form, which suggest that there is an opportunity to eliminate some repetition, and to separate policy from implementation. The policy is the set of conditions under which the link should be hidden:
WHEN_TO_HIDE_LINK = [
['edit', %w(sent sending archived)],
['send_schedule', %w(sent sending archived)],
['archive', %w(archived)],
['dashboard', %w(sending draft)],
]
The implementation is the code that applies the policy:
def hide_link?(link, mailing)
WHEN_TO_HIDE_LINK.any? do |link_value, mailing_statuses|
link_value == link && mailing_statuses.include?(mailing.status)
end
end
Explanations below the fold.
%w
%w is a way to specify a list of strings without typing all those quotes and commas. This:
%w(sent sending archived)
is equivalent to this:
['sent', 'sending', 'archived']
any?
Enumerable#any? passes each element of the array to the block (the bit between the do and the end). If the block ever returns truthy, then the result of any? is true; otherwise, the value of any? is false.
array decomposition
Did you notice that although each element of WHEN_TO_HIDE_LINK is an array, the block passed to any? does not take an array? You might expect that you'd have to do this:
WHEN_TO_HIDE_LINK.any? do |when_to_hide|
link_value = when_to_hide[0]
mailing_statuses = when_to_hide[1]
...
but Ruby will decompose array into parts for you. Here's one way to do it:
WHEN_TO_HIDE_LINK.any? do |when_to_hide|
link_value, mailing_statuses = when_to_hide
...
When there is an array on the right side of the = and comma-separated variables on the left, Ruby decomposes the array into its elements and assigns them to the variables separately.
But Ruby can make things even easier:
WHEN_TO_HIDE_LINK.any? do |link_value, mailing_statuses|
...
This is equivalent to either of the preceding two fragments.

Resources