Conditional Execution
Throughout the second section we are going to focus on adding "tools" to your Ruby Toolbox. There are many common scenarios that arise in the world of programming and web development. There may be times when you want to scan a file and extract out the email addresses, or scan a website to extract all of the hyperlinks. Ruby has many built-in solutions for completing these tasks. In order to become an intermediate developer, you'll need to master these built-in methods provided by Ruby. You'll also need to learn how to structure your programs by controlling the flow of logic. Let's begin by looking at conditional execution and flow control.
Often times we want to run code but only under certain conditions. For example, it's very common to see code that resembles the following, "if the password and username match, log the user in, if not, redirect to login page". Here we have an action, "log the user in" but we only want it to be performed under the condition, "the password and username match". If the condition is not met, we specify an alternative action, which is to redirect to login page. This is an example of conditional execution.
Controlling the flow
Conditional execution is an example of controlling the flow of your application. There are several techniques to control application flow including looping, iteration, and exception handling. These techniques span across programming languages. Each language has subtle differences in their implementation but the concepts are ubiquitous.
If/else
One of the most frequently used structures is the "if" statement. The syntax of the if statement in Ruby looks like this:
if boolean
#code goes here
end
The if statement will test for some conditional, which will either be true or false. If the condition is true, the code will execute. If the condition is false, the code will not execute (it will be skipped). To specify an alternative action we can use the else keyword. By doing this, we specify code to run if the condition is false.
if boolean
#code here executes if boolean is true
else
#code here executes if boolean is false
end
The English equivalent to the example would be as follows, "if the condition is true, execute this code, if false, execute that code." You aren't limited to only two options. If you are testing against more than two options Ruby has an "elsif" clause. Note that the spelling of "elsif" is not the correct English spelling. Testing against three conditions looks like this:
# get a random number from 1 - 10
>> x = Random.new
#<Random:0x007f9bea84c9b0>
>> x = x.rand(1..10)
5
# print a response based on the number
>> if x < 3
>> puts "give this a poor rating"
>> elsif x > 7
>> puts "give this a great rating"
>> else
>> puts "give this a mediocre rating"
>> end
# --> give this a mediocre rating
Let's go through the example piece by piece. Here we have an if statement that compares three conditions. In the example, we are pretending to take input from a user. The input is a random number from 1 - 10, representing a rating of a movie. Based on the users rating we are going to print a string to the screen. To create a new random object we call new on the Ruby class, Random. We can store this new random object in the variable 'x'. Using the rand method (built-in method) we pass in a range as the argument. This is the same as saying to ruby, "give me a random number between 1-10 (inclusive) and store it in the variable 'x', please". Next, we use an if statement to check the value of the random number. If the rating is less than 3, we call it a poor rating. If it is greater than 7 we call it a great rating. Anything else, which only leaves numbers 4, 5, and 6, we call a mediocre rating. Since the value of 'x' happens to be 5 in this case, ruby returns, "give this a mediocre rating". Note: every conditional statement requires one and only one end keyword. The use of elsif ( or multiple elsif's) and else are part of one if statement and thus there only needs to be one end.
Looping with conditions
Loop do
The most basic looping mechanism in Ruby is the "loop do" statement. This executes an infinite loop. For example, the following code will print "hello", continuously and indefinitely:
loop do
print "hello"
end
In order to be useful, the loop do statement needs a conditional break statement. Break statements use the keyword, break, and will cause the current execution to cease. This makes the "loop do" statement rudimentary. Here is an example of a loop do statement, which provides a break:
>> x = 10
>> loop do
>> x += 1
>> puts x
>> break if x == 20
# remember to use ' == ' for equals
11
12
13
14
15
16
17
18
19
20
While
The code in a while statement will execute if the condition is true. It will also continue to execute while the condition is true, forming a loop. It's much more convienent than the "loop do" statement because it implies a break condition.
>> x = 5
>> while x < 10
>> x += 1
>> puts x
>> end
# the output of the code is as follows
6
7
8
9
10
Loops are useful when you need to perform the same action over a given set. In the example, we want to print each number that is between 6 and 10. Note: If we wanted to print out each number between 5 and 10 I would move the increment statement AFTER the puts statement. Instead, the increment statement is at the beginning of the loop. These small nuances make a big difference in the implementation of your code. So, it's important to understand how the ruby interpreter works. As you construct programs it may be a good idea to add comments indicating the expected value of a variable at certain points in the application. Later, you'll learn how to write tests for your programs to confirm these expectations.
For
Another option for looping over a collection is Ruby's "for" statement. The syntax for the "for" statement looks like this:
for variable in collection
#--> code to execute
end
# note the keywords are highlighted
The "for" statement uses keywords for and in. As with other statements, the end keyword is required to indicate the end of the code block. The variable is supplied by the developer. It can be whatever you would like. We use the letter i in the following example.
>> for i in 5..10
>> puts i
>> end
5
6
7
8
9
10
5..10
# remember that '..' is an inclusive range and thus includes the ending value
Here is another example using an array as the collection. Notice the shortcut method in creating the Array.
>> fruits = %w(apple, orange, peach, banana, blueberry)
[
[0] "apple,",
[1] "orange,",
[2] "peach,",
[3] "banana,",
[4] "blueberry"
]
>> fruits.class
Array < Object
>> for fruit in fruits
>> puts fruit
>> end
# Output
apple,
orange,
peach,
banana,
blueberry
The variable 'fruit' was used because it is a descriptive variable that makes sense. I could just have easily used the variable 'i' once again. Whatever variable you choose to use after the keyword for must be the same variable you refer to in the code block (i.e. puts fruit). So, if I were to change the variable to 'i' then I would also need to change the code to "puts i" rather than "puts fruit".
Since creating an array of strings is a common procedure, Ruby provides a quick method using the %w (string)
syntax. The %w
will apply quotes to each string contained in the parenthesis. All you need to do is separate your strings with commas or spaces. The long form alternative would be to create an array literal with strings like so:
fruits = ["apple", "orange", "peach", "banana", "blueberry"]
The shortcut simply provides a way to create an array of strings without having to type quotes around each string. To make sure that my variable was, an array, I used the '.class' method, which returns the current class of the caller. Remember, the caller is the object to the left of the dot ( . ) while the method or message is the word to the right. As you can see, the 'fruits' variable does indeed contain an array. We can iterate over the array with the for statement just like we did with the range, printing out each value as we go.
Ruby borrowed the for/in statement for looping over a collection from Python. It is very common in Python for developers to use for/in to iterate over collections. However, Rubyists rarely make use of the statement. It is much more common to see Rubyists reach for the each method, which is explored in-depth in the chapter Enumerables. Here is a preview of things to come:
>> fruits.each { |fruit| puts fruit }
# Output
apple
orange
peach
banana
blueberry
# between for/in and each, which method do you prefer?
Convenience Conditionals
Ruby has several additional conditionals that are sometimes more convienent either because they use less typing or make more sense grammatically. When the code makes more sense grammatically, it is usually easier to understanding what is going on. It is possible but confusing to use if...else and include a double negative. It creates a sort of backwards logic where the code is executing if it evaluates something isn't not untrue. Programmers with little exposure to Ruby will sometimes use complex logical evaluations when simpler alternatives exist. I suggest subscribing to the adage proposed by Albert Einstein, "Everything should be made as simple as possible, but not simpler". Let's examine some other options available to you when using conditionals.
Unless
The unless statement functions in the opposite way of the while statement. The code in an unless statement will execute if the condition is false. Unless is a conditional meaning the same as if not. It's appropriate in situations where you want to declare some requirement before allowing an action to take place. Here is a snippet from a rails app that executes when a user tries to edit a blog entry:
# check if the currently logged in user is the author of a post
unless current_user == @post.author
redirect_to '/'
end
Case when
The case statement is a powerful and under-utilized conditional. I'll refer to the case statement as "case when", since both words are required keywords. In other languages, for example, JavaScript, the case statement is called a switch statement. In Apple's programming language Swift, it's referred to as a guard statement. The best use for a case statement is when there are multiple options to choose from and each option presents a different outcome. Rather than writing if/else over and over it is more concise and reads better to use a case statement. See the example below:
vehicle = "Jetta"
manufacturer = case vehicle
when "Mustang" then "Ford"
when "Jetta" then "VW"
when "X3" then "BMW"
when "XJ" then "Jaguar"
else "I'm not sure"
end
puts "My " + vehicle + "is made by " + manufacturer
# --> My Jetta is made by VW
There is no limit to the number of "whens" you can use. The use of the word "then" is optional. It's less common to see the word "then" but I've included it for readability. Case when statements are particularly useful when dealing with ranges.
final = 80
result = case final
when 0..59
puts "Sorry, this is a failing grade"
when 60..69
puts "You've passed the final with a grade of 'D' "
when 70..79
puts "You've passed the final with a grade of 'C' "
when 80..89
puts "Congratulations you've passed with final with a grade of 'B'! "
when 90..100
puts "Congratulations you've passed with final with a grade of 'A'! "
else
puts "You have no score, did you skip the final?"
end
puts result
# --> Congratulations you've passed with final with a grade of 'B'!
Ternary Operator
The ternary operator is used frequently by Rubyists. Ruby experts are known for crafting creative and powerful "one-liners". These are solutions to problems that can be written using a single line of code. In coding competitions, points are awarded for speed, creativity, and brevity, among other factors. It's common for Rubyists who have mastered the language to solve mathematical and logical problems with only a single line of code. Other programmers using a different language such as Java, Python, C#, etc. may need 4 lines or more to solve the same problem. This speaks to the beauty of Ruby. On its surface Ruby is a beginner friendly and approachable language but there are underlying nuances that make Ruby incredibly powerful. Here is an example of how to open a file, write to it, and close it all in a single line:
File.open(File.join(Rails.root, 'lib', 'assets', 'file.txt'), 'a+') {|f| f.write("stuff"+"\n" }
The ternary operator is a way of condensing what would otherwise be several lines of code into a single line. It's native to many languages, the syntax is ? :
. In use, the ternary operator will look like this,
test-expression ? if-true-expression : if-false-expression
. If the statement evaluates to true, the true condition will be evaluated. If the statement evaluates to false, the false condition will be evaluated. An example will illustrate this more clearly:
driver_speed = 65
speed_limit = 55
driver_speed > speed_limit ? puts("Turn on siren, pull over driver") : puts("Driver is not speeding")
# --> Turn on siren, pull over driver
Here the parenthesis are required because we are executing multiple statements on a single line. The condition evaluates to true as 65 is greater than 55. As you may have inferred when the condition is true the code to the LEFT of the colon ( : ) gets executed. When the condition is false the code to the RIGHT of the colon gets executed. Ternary operators are convenient shortcuts.
|| Operator
The syntax " || " in computer science means "or". This was reviewed briefly in the section Operators. It provides a useful way of expressing meaning in a concise and readable way. In the situation where we have the following code:
# check if y has a value
if y
# if it does set its value to x
x = y
else
# if not set x to z
x = z
end
We are attempting to set the value of "x". We want to set it to the same value as "y" but (for whatever reason) we aren't sure if "y" is defined at this point in the application. To accomplish our goal we check if "y" has a value, if it does then we set "x" equal to "y". If "y" does not have a value, then we use our fallback value "z"; we set "x" equal to "z". This isn't too complicated and it's only four lines of code without the comments. However, we can shorten this to a single line that reads much simpler:
x = y || z
It should be easy to look at the line above and see what we are trying to do. We're assigning "x" to "y". If that doesn't work we'll set "x" to the same value as "z". This is easier to read and less typing. Always try to accomplish your goal concisely. This almost always use fewer lines when possible.
||= Operator
Recall the section on Reviewing incrementing/decrementing above. Just as we can use +=
and-=
we can take advantage of ||=
. In the following scenario:
unless x
x = y
end
If "x" has a value, leave it alone. If it does not have a value, set "x" to "y". We can accomplish this with the or-equals operator:
x ||= y
This shorthand method is used often. You should get used to thinking and writing with the assignment operators in mind.
Summary
In this chapter we went over conditional code execution and control flow. We learned the following:
conditional statements
- if
- if/else
- if/elsif/else
- unless
- case when
operators
- ternary operator
- || operator
- || = operator
- += operator (increment)
- -= operator (decrement)
Conditional statements such as if, unless, and case require the end keyword.