Variables

What is a Variable?

Variables are used to store information to be referenced and manipulated in a computer program. They also provide a way of labeling data with a descriptive name, so our programs can be understood more clearly by the reader and ourselves. It is helpful to think of variables as containers that hold information. Their sole purpose is to label and store data in memory. This data can then be used throughout your program.

Assigning Value to Variables

Naming variables is known as one of the most difficult tasks in computer programming. When you are naming variables, think hard about the names. Try your best to make sure that the name you assign your variable is accurately descriptive and understandable to another reader. Sometimes that other reader is yourself when you revisit a program that you wrote months or even years earlier.

When you assign a variable, you use the = symbol. The name of the variable goes on the left and the value you want to store in the variable goes on the right.

irb :001 > first_name = 'Joe'
=> "Joe"

Here we've assigned the value 'Joe', which is a string, to the variable first_name. Now if we want to reference that variable, we can.

irb :002 > first_name
=> "Joe"

As you can see, we've now stored the string 'Joe' in memory for use throughout the program.

Note: Make sure you don't confuse the assignment operator (=) with the equality operator (==). The individual = symbol assigns value while the == symbol checks if two things are equal.

Let's try a little something. Look at the following irb session.

irb :001 > a = 4
=> 4
irb :002 > b = a
=> 4
irb :003 > a = 7
=> 7

What is the value of b at this point? Take your best guess and then type this session into irb to find out.

You'll notice that the value of b remains 4, while a was re-assigned to 7. This shows that variables point to values in memory, and are not deeply linked to each other. If this is confusing, don't worry, we'll have plenty of exercises for you to complete that will make this information clear and obvious. And when in doubt, always try it out in irb.

Getting Data from a User

Up until now, you've only been able to assign data to variables from within the program. However, in the wild, you'll want other people to be able to interact with your programs in interesting ways. In order to do that, we have to allow the user to store information in variables as well. Then, we can decide what we'd like to do with that data.

One way to get information from the user is to call the gets method. gets stands for "get string", and is a lot of fun. When you use it, the program waits for the user to 1) type in information and 2) press the enter key. Let's try it out. Type these examples in irb to get the feel and play around with them for a bit if you'd like to.

irb :001 > name = gets
Bob
=> "Bob\n"

After the code, name = gets, the computer waited for us to type in some information. We typed "Bob" and then pressed enter and the program returned "Bob\n". The \n at the end is the "newline" character and represents the enter key. But we don't want that as part of our string. We'll use chomp chained to gets to get rid of that - you can put .chomp after any string to remove the carriage return characters at the end.

irb :001 > name = gets.chomp
Bob
=> "Bob"

There we go! That's much prettier. Now we can use the name variable as we so please.

irb :001 > name = gets.chomp
Bob
=> "Bob"
irb :002 > name + ' is super great!'
=> "Bob is super great!"

Variable Scope

A variable's scope determines where in a program a variable is available for use. A variable's scope is defined by where the variable is initialized or created. In Ruby, variable scope is defined by a method definition or by a block. They have different behaviors when it comes to variable scope.

Variable Scope and Method Definitions

We'll learn about methods in the next chapter. For now, you can think of methods as pieces of reusable code that your program can execute many times during its run. Method definitions look like this:

name = 'Somebody Else'

def print_full_name(first_name, last_name)
  name = first_name + ' ' + last_name
  puts name
end

Once we have the method defined, we can call it as many times as we need with different values for first_name and last_name:

print_full_name 'Peter', 'Henry'   # prints Peter Henry
print_full_name 'Lynn', 'Blake'    # prints Lynn Blake
print_full_name 'Kim', 'Johansson' # prints Kim Johansson
puts name                          # prints Somebody Else

In terms of variable scope, methods have self-contained scope. That means that only variables initialized within the method's body can be referenced or modified from within the method's body. Additionally, variables initialized inside a method's body aren't available outside the method's body. It's a bit like an impenetrable bubble. Thus, in the above code, we can't use or change the name variable from line 1 from inside the print_full_name method. We can, however, create and use a different name variable that is locally scoped to the method. That is why lines 4 and 5 work without changing the value of name from line 1.

Variable Scope and Blocks

A block is a piece of code that follows a method's invocation, delimited by either curly braces {} or do/end:

total = 0
[1, 2, 3].each { |number| total += number }
puts total # 6
total = 0
[1, 2, 3].each do |number|
  total += number
end
puts total # 6

In the examples above, { |number| ... } is a block, as is do |number| ... end. Though they look different, the behavior is identical. In both cases, the code can access and modify variables that are defined outside of the block. Thus, both blocks can access and modify total. However, any variables initialized inside the block (such as number) can't be accessed by the outer code.

With blocks, the one rule that we want to emphasize is that: Inner scope can access variables initialized in an outer scope, but not vice versa.

Looking at some code will make this clearer. Let's say we have a file called scope.rb.

# scope.rb

a = 5             # variable is initialized in the outer scope

3.times do |n|    # method invocation with a block
  a = 3           # is a accessible here, in an inner scope?
end

puts a

What is the value of a when it is printed to the screen? Try it out.

The value of a is 3. This is because a is available to the inner scope created by 3.times do ... end, which allowed the code to re-assign the value of a. In fact, it re-assigned it three times to 3.

Note that blocks can also be written with curly braces, though common practice is to only use them for one-liners: the method call and the block must fit on the same line. For instance, here's the times loop from above rewritten using braces:

3.times { |n| a = 3 }

Curly brace blocks are identical to do...end blocks in terms of scope.

Let's try something else. We'll modify the original scope.rb code:

# scope.rb

a = 5

3.times do |n|    # method invocation with a block
  a = 3
  b = 5           # b is initialized in the inner scope
end

puts a
puts b            # is b accessible here, in the outer scope?

What result did you get when running that program? You should have gotten an error to the tune of:

scope.rb:11:in `<main>': undefined local variable or method `b' for main:Object
(NameError)

This is because the variable b is not available outside of the method invocation with a block where it is initialized. When we call puts b it is not available within that outer scope.

Be aware that not all do...end pairs imply a block. We will explore this in much greater detail in later courses. In particular, a do...end on a for or while loop is not a block.

Put another way, the key distinguishing factor for deciding whether code delimited by {} or do/end is considered a block (and thereby creates a new scope for variables), is seeing if the {} or do/end immediately follows a method invocation. For example:

arr = [1, 2, 3]

for i in arr do
  a = 5      # a is initialized here
end

puts a       # is it accessible here?

The answer is yes. The reason is because the for...do/end code did not create a new inner scope, since for is part of Ruby language and not a method invocation. When we use each, times and other method invocations, followed by {} or do/end, that's when a new block is created.

Types of Variables

Before we move on, you should be aware that there are five types of variables. Constants, global variables, class variables, instance variables, and local variables. While you should not worry too much about these topics in depth yet, here is a brief description of each.

Constants are declared by capitalizing every letter in the variable's name, per Ruby convention. They are used for storing data that never needs to change. While most programming languages do not allow you to change the value assigned to a constant, Ruby does. It will however throw a warning letting you know that there was a previous definition for that variable. Just because you can, doesn't mean you should change the value. In fact, you should not. Constants cannot be declared in method definitions, and are available throughout your application's scopes.

Example of a constant declaration:

MY_CONSTANT = 'I am available throughout your app.'

Global variables are declared by starting the variable name with the dollar sign ($). These variables are available throughout your entire app, overriding all scope boundaries. Rubyists tend to stay away from global variables as there can be unexpected complications when using them.

Example of a global variable declaration:

$var = 'I am also available throughout your app.'

Class variables are declared by starting the variable name with two @ signs. These variables are accessible by instances of your class, as well as the class itself. When you need to declare a variable that is related to a class, but each instance of that class does not need its own value for this variable, you use a class variable. Class variables must be initialized at the class level, outside of any method definitions. They can then be altered using class or instance method definitions.

Example of a class variable declaration:

@@instances = 0

Instance variables are declared by starting the variable name with one @ sign. These variables are available throughout the current instance of the parent class. Instance variables can cross some scope boundaries, but not all of them. You will learn more about this when you get to OOP topics, and should not use instance variables until you know more about them.

Example of an instance variable declaration:

@var = 'I am available throughout the current instance of this class.'

Local variables are the most common variables you will come across and obey all scope boundaries. These variables are declared by starting the variable name with neither $ nor @, as well as not capitalizing the entire variable name.

Example of a local variable declaration:

var = 'I must be passed around to cross scope boundaries.'

Summary

In this chapter, we talked about how to use variables to store information for later use and how to get information from a user. We also showed that not all variables are created equal and that the scope in which a variable is defined changes its availability throughout the program. Now that you know the different types of variables and how to use them, let's put some of that knowledge into practice with some exercises.

Exercises

  1. Write a program called name.rb that asks the user to type in their name and then prints out a greeting message with their name included.

    Solution

    # name.rb
    
    puts "What is your name?"
    name = gets.chomp
    puts "Hello " + name
    

    Video Walkthrough

    Please register to play this video

  2. Write a program called age.rb that asks a user how old they are and then tells them how old they will be in 10, 20, 30 and 40 years. Below is the output for someone 20 years old.

    # output of age.rb for someone 20 yrs old
    
    How old are you?
    In 10 years you will be:
    30
    In 20 years you will be:
    40
    In 30 years you will be:
    50
    In 40 years you will be:
    60
    

    Solution

    puts "How old are you?"
    age = gets.chomp.to_i
    puts "In 10 years you will be:"
    puts age + 10
    puts "In 20 years you will be:"
    puts age + 20
    puts "In 30 years you will be:"
    puts age + 30
    puts "In 40 years you will be:"
    puts age + 40
    

    Video Walkthrough

    Please register to play this video

  3. Add another section onto name.rb that prints the name of the user 10 times. You must do this without explicitly writing the puts method 10 times in a row. Hint: you can use the times method to do something repeatedly.

    Solution

    # name.rb continued
    
    10.times do
      puts name
    end
    

    Video Walkthrough

    Please register to play this video

  4. Modify name.rb again so that it first asks the user for their first name, saves it into a variable, and then does the same for the last name. Then outputs their full name all at once.

    Solution

    # name.rb continued again
    
    puts "What is your first name?"
    first_name = gets.chomp
    puts "Thank you. What is your last name?"
    last_name = gets.chomp
    puts "Great. So your full name is " + first_name + " " + last_name
    

    Video Walkthrough

    Please register to play this video

  5. Look at the following programs...

    x = 0
    3.times do
      x += 1
    end
    puts x
    

    and...

    y = 0
    3.times do
      y += 1
      x = y
    end
    puts x
    

    What does x print to the screen in each case? Do they both give errors? Are the errors different? Why?

    Solution

    The first prints 3 to the screen. The second throws an error undefined local variable or method because x is not available as it is created within the scope of the do/end block.

    Video Walkthrough

    Please register to play this video