ruby: how to combine if and unless - ruby

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')

Related

Elegant way to tell if something is false?

Seeing that .nil? is so useful and makes code so readable, I tried .false? and was surprised it didn't exist.
Question
What is the most elegant / preferred / self-documenting / idiomatic way to check if something is false in ruby, without using any user-defined methods?
Example
A possible use case replacing; the s.false? in this:
def false?
self == false
end
s ||= "hello" # is the same as
s = "hello" if (s.nil? || s.false?)
Well, usually you just compare to false only if it is actually important whether the value is really false as in
if s == false
do_something
end
However, most of the time, people actually check for a value to be truthy or falsy as you often don't (need to) care for the strict difference between false or nil.
Here, you thus merely check whether a value is nil or false (that is: it's falsy) or if it is anything else (that is: it's truthy). This is encouraged by language idioms such as the checks done by if and unless as well as the common boolean operators such as || or &&.
do_something if s # called if s is anything else but false or nil
do_something unless s # called if s is either false or nil
Especially when accepting / expecting boolean values, a nil value is often handled as if it were false because of these Ruby language idioms.
What is the most elegant / preferred / self-documenting / idiomatic
way to check if something is false in ruby, without using any
user-defined methods?
I think s == false is your answer plain and simple.

Switch Case in 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.

Is overriding operators with #prepend dangerous?

I am building a gem that makes an ActiveRecord immutable. The details do not matter, but basically, one thing this allows is for user.country == :CAN to return true if the country's code attribute match.
Of course, this means that :CAN == user.country will never work unless I override the == operator on Symbol, which I am wondering if this is a dangerous thing to do, maybe because of the added overhead, since this operator is used extensively everywhere.
Is there a better/smarter way to do exactly that? #coerce is not an option because Symbol already knows how to ==.
Symbol.prepend(Module.new do
def ==(other)
if other.respond_to?(:immutable?) && other.immutable?
other.==(self)
else
super
end
end
end)
If it changes anything, my main issue also has to do with the === operator, since I wanted to enable the following:
case user.country
when :CAN then ...
when :USA then ...
else ...
end

No Difference Between Boolean Operators AND and OR in Case Conditional in Ruby

I can't see a practical difference between the boolean operators AND and OR in a Ruby case conditional.
For example, I want to get the user to input the sentence:
sudo make me a sandwich
And the case conditional starts as follows:
case user_selection
when /sudo/ && /sandwich/
However, if the user enters:
make me a sandwich
The condition will be a satisfied.
My way around it in this instance is to re-order the conditions:
case user_selection
when /sandwich/ && /sudo/
But that pre-supposes that every time a user thinks to use "sudo" they will include the string "sandwich" in their response. However, this is functionally no different from this:
case user_selection
when /sudo/
I looked up boolean operators for Ruby conditionals, but have not found a satisfactory answer.
You cannot use && in a case block like that, because when /sudo/ && /sandwich/ evaluates to just when /sandwich/.
Instead you will need to use one regexp that looks for both words. A simple example might be:
case user_selection
when /sudo.*sandwich/
Just for reference.
If you absolutely need a logical AND in your case statement, you could use a lambda :
def answer(order)
case order
when ->(x) { x =~ /sudo/ && x =~ /sandwich/ }
puts 'Okay'
else
puts 'Do it yourself!'
end
end
answer 'make me a sandwich'
#=> Do it yourself!
answer 'sudo sing a song'
#=> Do it yourself!
answer 'sudo make me a sandwich'
#=> Okay
If you can compact the boolean logic to a single check (e.g. with a Regex), you probably should, as in #spickermann's answer.

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.

Resources