Learn Enough Ruby to Be Dangerous | Learn Enough to Be Dangerous
You have to make a choice. Choose...wisely.

Get occasional notifications about things like product discounts, blog posts, and new or updated tutorials. Unsubscribe at any time.

Quick Checkout
or Pay by Credit Card
Error processing your payment
  • You didn't choose whether or not to be added to the mailing list
Confirm
$0.00

Payments and credit card details are securely managed and protected by Learn Enough's payment processor, Stripe. More information on their site:

CART
Total
$0.00

Your Cart is Empty

$30
$300
$300
$XY
$XY
1234
Get Single Tutorial
MORE INFO

Learn Enough Ruby to Be Dangerous is available as an ebook, an offline video series, and as a structured, self-paced online course. The course includes full online access to the book content, streaming videos, progress tracking, exercises, and community exercise answers.

All Access Subscription
MORE INFO

The Learn Enough All Access Subscription includes the entire Learn Enough introductory sequence and the full Ruby on Rails Tutorial. More than 2500 pages of book content and 53 hours of video that teach you to code from total beginner up to professional-grade web development.

Sign up for the course and get access to the full tutorial and streaming screencasts!

2.4 Attributes, booleans, and control flow

Everything in Ruby, including strings, is an object. This means that we can get useful information about strings and do useful things with them using the same dot notation used in many object-oriented languages (e.g., JavaScript, as seen in Learn Enough JavaScript to Be Dangerous).

We’ll start by accessing a string attribute, which is a piece of data attached to an object. In particular, in the console we can use the length attribute to find the number of characters in a string:

$ irb
>> "badger".length    # Accessing the "length" property of a string
=> 6
>> "".length          # The empty string has zero length.
=> 0

The length attribute is especially useful in comparisons, such as checking the length of a string to see how it compares to a particular value (note that the REPL supports “up arrow” to retrieve previous lines, just like the command-line terminal):

>> "badger".length > 3
=> true
>> "badger".length > 6
=> false
>> "badger".length >= 6
=> true
>> "badger".length < 10
=> true
>> "badger".length == 6
=> true

The last line uses the equality comparison operator ==, which Ruby shares with many other languages. (Note that, like JavaScript, Ruby supports ===, and indeed has several comparison operators in general, but == works in almost all cases of relevance.)

The return values in the comparisons above, which are always either true or false, are known as boolean values, after mathematician and logician George Boole (Figure 2.4).7

images/figures/boole
Figure 2.4: True or false? This is a picture of George Boole.

Boolean values are especially useful for control flow, which lets us take actions based on the result of a comparison (Listing 2.5).

Listing 2.5: Control flow with if.
>> password = "foo"
=> "foo"
>> if (password.length < 6)
>>   "Password is too short."
>> end
=> "Password is too short."

Note in Listing 2.5 that the comparison after if is in parentheses, and the if statement is terminated by the end keyword. The latter is required, but in Ruby (unlike many other languages) the parentheses are optional, and it’s common to leave them off (Listing 2.6).

Listing 2.6: Control flow with if and no parentheses.
>> if password.length < 6
>>   "Password is too short."
>> end
=> "Password is too short."

Listing 2.5 and Listing 2.6 also follow a consistent indentation convention, which is irrelevant to Ruby but is important for human readers of the code (Box 2.2).

Box 2.2. Code formatting

The code samples in this tutorial, including those in the REPL, are designed to show how to format Ruby in a way that maximizes readability and code comprehension. The programs executing Ruby programs, whether irb or Ruby itself, don’t care about these aspects of the code, but human developers do.

While exact styles differ, here are some general guidelines for good code formatting:

  • Indent code to indicate block structure. Pretty much every time you see an opening curly brace {, you’ll end up indenting the subsequent line. (Some text editors even do this automatically.)
  • Use two spaces (typically via emulated tabs) for indentation. Many developers use four or even eight spaces, but I find that two spaces are enough to indicate block structure visually while conserving scarce horizontal space.
  • Add newlines to indicate logical structure. One thing I particularly like to do is add an extra newline after a series of variable assignments, in order to give a visual indication that the setup is done and the real coding can begin. An example appears in Listing 4.9.
  • Limit lines to 80 characters (also called “columns”). This is an old constraint, one that dates back to the early days of 80-character-width terminals. Many modern developers routinely violate this constraint, considering it outdated, but in my experience the 80-character limit is a good source of discipline, and will save your neck when using command-line programs like less (or when using your code in a document with more stringent width requirements, such as a book). A line that breaks 80 characters is a hint that you should introduce a new variable name, break an operation into multiple steps, etc., to make the code clearer for anyone reading it.

We’ll see several examples of more advanced code-formatting conventions as we proceed throughout the rest of this tutorial.

We can add a second behavior using else, which serves as the default result if the first comparison is false (Listing 2.7).

Listing 2.7: Control flow with if and else.
>> password = "foobar"
>> if password.length < 6
>>   "Password is too short."
>> else
>>   "Password is long enough."
>> end
=> "Password is long enough."

The first line in Listing 2.7 redefines password by assigning it a new value. After reassignment, the password variable has length 6, so password.length < 6 is false. As a result, the if part of the statement (known as the if branch) doesn’t get evaluated; instead, Ruby evaluates the else branch, resulting in a message indicating that the password is long enough.

Unusually among programming languages, Ruby has a special elsif keyword meaning “else if”, as shown in Listing 2.8 (Figure 2.5).8

Listing 2.8: Control flow with elsif.
>> password = "goldilocks"
>> if password.length < 6
>>   "Password is too short."
>> elsif password.length < 50
>>   "Password is just right!"
>> else
>>   "Password is too long."
>> end
=> "Password is just right!"
images/figures/goldilocks
Figure 2.5: Goldilocks chooses control flow that is just right.

As a final example, it’s worth noting that Ruby allows us to place the if part after the statement when there’s only one line:

>> password = "foo"
>> "Password is too short." if password.length < 6
=> "Password is too short."

The if here can be negated using unless instead, with the opposite comparison as well:

>> "Password is too short." unless password.length >= 6
=> "Password is too short."

It’s essentially never wrong to use if, but in some cases the conditional sounds better using unless. I suggest pronouncing the conditional as if it were English and choosing whichever variant sounds more natural.

2.4.1 Combining and inverting booleans

Booleans can be combined or inverted using the && (“and”), || (“or”), and ! (“bang” or “not”) operators.

Let’s start with &&. When comparing two booleans with &&, both have to be true for the combination to be true. For example, if I said I wanted both french fries and a baked potato, the only way the combination could be true is if I could answer “yes” (true) to both of the questions “Do you want french fries?” and “Do you want a baked potato?” If my answer to either of those is false, then the combination must be false as well. The resulting combinations of possibilities are collectively known as a truth table; the truth table for && appears in Listing 2.9.

Listing 2.9: The truth table for && (“and”).
>> true && false
=> false
>> false && true
=> false
>> false && false
=> false
>> true && true
=> true

We can apply this to a conditional as shown in Listing 2.10.

Listing 2.10: Using the && operator in a conditional.
>> x = "foo"
>> y = ""
>> if x.length == 0 && y.length == 0
>>   "Both strings are empty!"
>> else
>>   "At least one of the strings is nonempty."
>> end
=> "At least one of the strings is nonempty."

In Listing 2.10, y.length is in fact 0, but x.length isn’t, so the combination is false (in agreement with Listing 2.9), and Ruby evaluates the else branch.

In contrast to &&, || lets us take action if either comparison (or both) is true (Listing 2.11).

Listing 2.11: The truth table for || (“or”).
>> true || false
=> true
>> false || true
=> true
>> true || true
=> true
>> false || false
=> false

We can use || in a conditional as shown in Listing 2.12.

Listing 2.12: Using the || operator in a conditional.
>> if x.length == 0 || y.length == 0
>>   "At least one of the strings is empty!"
>> else
>>   "Neither of the strings is empty."
>> end
=> "At least one of the strings is empty!"

Note from Listing 2.11 that || isn’t exclusive, meaning that the result is true even when both statements are true. This stands in contrast to colloquial usage, where a statement like “I want fries or a baked potato” implies that you want either fries or a baked potato, but you don’t want both (Figure 2.6).9

images/figures/fries
Figure 2.6: Turns out I only wanted fries.

In addition to && and ||, Ruby supports negation via the “not” operator ! (often pronounced “bang”), which just converts true to false and false to true (Listing 2.13).

Listing 2.13: The truth table for !.
>> !true
=> false
>> !false
=> true

We can use ! in a conditional as shown in Listing 2.14. Note that parentheses are required in this case, because otherwise we’re asking if !x.length is equal to 0.

Listing 2.14: Using the ! operator in a conditional.
>> if !(x.length == 0)
>>   "x is not empty."
>> else
>>    "x is empty."
>> end
=> "x is not empty."

The code in Listing 2.14 is valid Ruby, as it simply negates the test x.length == 0, yielding true:

>> !(x.length == 0)
=> true

In this case, though, it’s more common to use != (“not equals”):

>> if x.length != 0
>>   "x is not empty."
>> else
>>    "x is empty."
>> end
=> "x is not empty"

Because we’re no longer negating the entire expression, we can omit the parentheses as before.

2.4.2 Bang bang

Not all booleans are the result of comparisons, and in fact every Ruby object has a value of either true or false in a boolean context. We can force Ruby to use such a boolean context with !! (pronounced “bang bang”); because ! converts between true and false, using two exclamation points returns us back to the original boolean:

>> !!true
=> true
>> !!false
=> false

Using this trick allows us to see that a string like "foo" is true in a boolean context:

>> !!"foo"
=> true

As it happens, the empty string is also true in a boolean context:10

>> !!""
=> true

In fact, even 0 is true in Ruby:

>> !!0
=> true

The only Ruby object that’s false in a boolean context (other than false itself) is nil:

>> !!nil
=> false

2.4.3 Exercises

  1. If x is "foo" and y is "" (the empty string), what is the value of x && y? Verify using the “bang bang” notation that x && y is true in a boolean context. Hint: When applying !! to a compound expression, wrap the whole thing in parentheses.
  2. What is x || y? What is it in a boolean context? Rewrite Listing 2.15 to use x || y, ensuring that the result is the same. (Hint: Switch the order of the strings.)

Join the Mailing List

Get occasional notifications about things like product discounts, blog posts, and new or updated tutorials. Unsubscribe at any time.