A Crash Course in Ruby

A beginner's guide to learning the basics of the Ruby programming language.

In the last five years or so, Ruby has gained up in popularity and a lot of software professionals have switched to or added it to their technical skills.

Though the language is still in the process of improvement and optimization, there is no denying that it is catching up with giants like PHP and Python.

Moreover, the language has helped realize some of the dream projects of the last decade such as Github and Twitter.

So, don't you think it is about time you learned it?

In this tutorial, you will learn about:

  1. Installing Ruby.
  2. Architectural foundations of the language.
  3. Operators.
  4. Data types.
  5. Conditional statements.
  6. Looping code.
  7. Object-oriented programming.
  8. Globals and constants.
  9. Loading external files/code.
  10. Ruby Gems.

In order to follow along swiftly, an intermediate level of expertise in any programming language is highly recommended.

If you are a programming beginner looking to learn Ruby, this tutorial is not for you.

This will be a very fast paced, practical hands-on of the Ruby language with lots to absorb. Move section by section and go at your own pace. I highly recommend you to play around with a single concept before moving on to the next one.

Installing Ruby

You can install Ruby using an executable installer (for Windows) or a package manager (for *nix and OSX).

The official installation guide provides a detailed account of the various methods available.

For this tutorial, I will show you how to install Ruby using a third party tool, Ruby Version Manager (RVM).

For beginners, managing Ruby versions may not be a big deal but as you develop more and more in Ruby, you will come to realize what a blessing RVM is.

Execute the following command to install the RVM author's public key.

$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3

Next, install RVM.

$ \curl -sSL https://get.rvm.io | bash -s stable

You will be prompted through the installation steps.

Once done, restart your terminal session and execute the following command to verify correct installation of RVM.

$ rvm --help

If you see RVM's comprehensive help page, congratulations!

Finally, we need to tell RVM which version of Ruby to install.

$ rvm --default install 2.3.1

We have specified the Ruby version 2.3.1 to be installed and also passed in the --default flag to set this as our default Ruby version.

RVM will prompt you with the installation steps as Ruby is installed.

Once finished, you can execute the following command to verify correct installation of Ruby.

$ ruby -v

If you see the Ruby version you specified to be installed, I have got good news for you!

Executing Ruby Code

You are free to try, hack, improvise, and build upon the code that will be presented during the course of this tutorial.

Simply place your code inside a file with a .rb extension.

Next, use your terminal to invoke Ruby and specify the code file to execute.

$ ruby <path-to-your-.rb-file>
# example: ruby /Volumes/Work/Ruby/my_code.rb

Your Ruby code will be executed and the output will be produced at the terminal.

If you want things to move a little quicker, you can try the interactive Ruby shell(IRB).

IRB is a program that is bundled with your Ruby installation and helps you test Ruby code by accepting Ruby statements and producing their output on the screen.

You can invoke IRB by simply executing the command irb after you have installed Ruby.

The IRB prompt will immediately show up on at the terminal, ready to accept Ruby commands.

Architectural Foundations

Ruby was created around the mid 1990s by Yukihiro "Matz" Matsumoto as a purely object oriented programming language, inspired by Perl, Smalltalk, Eiffel, Ada, and Lisp.

Ruby was created to make programming fun. At times, Ruby code is so beautifully written that it reads almost like English.

The best thing about Ruby is despite it's complexities, it's a joy to work with.

Everything you deal with in Ruby is actually an object, even though from a syntactical point of view, it may not appear to be.

Consider the following code snippet.

puts "This is a simple code snippet"
puts self.class

We have just echoed a very simple string and printed the class of the self(which points to the current context) object.

It produces the output.

This is a simple code snippet
Object

As we have come to know it, should not the self keyword work only inside a class? Why was our reality not shattered with an unpleasant not defined error?

It is because every type in Ruby inherits from the base object Object and even if our code does not define a class, it is compiled as part of and extends upon the object Object.

The fact that everything is an object and should be treated like so puts an enormous burden on you as a programmer where you have to make sure your code is purely object oriented.

Luckily, to keep things neat, easy to read, and understand, Ruby has a lot of, what we call, syntactic sugar.

puts 2 + 2
puts 2.+(2)

The first line of code adds two integers. The second line does the same but it actually calls the addition method on the integer object, passing in another integer.

Both the lines of code produce the same output.

4
4

Obviously, the first line of code is more natural and easier to read. The second just re-iterates the point that everything in Ruby is an object.

I highly recommend you to read the official Ruby about page, some pretty fascinating bits of knowledge in there.

A Short Primer

Before we start, let us take care of the small things that will help you better understand the sections and code that follows.

Single-line code comments start with the hash # sign whereas multi-line comments are enclosed within =begin and =end blocks.

Further, the =begin and =end constructs should be right at the start of the line for multi-line comments to work. Single-line code comments can be added anywhere using the # construct.

Here are a few examples.

# This is a single line comment
test = true

income = 0 # set income to zero

# Correct multi-line comment
=begin
# lots of code
=end

# Incorrect multi-line comment
    =begin
# lots of code
    =end

Code blocks can be enclosed withing curly braces {} or do and end keywords.

while true do
  # some code
end

while true {
  # some code
}

At times, you have to yield or pass in variables to blocks or loops. You can do that using ||.

10.times do |num|
  puts num
end

[1, 2 ,3, 4, 5].each do |num|
  puts num
end  

Code lines are terminated using the new-line character so one you are done writing a line of code, simply hit enter. There is no need to terminate a line with ; or any other character.

Operators

Let us start with the various operators available in Ruby.

The following table provides a comprehensive list of the Ruby operators, in order of their precedence.

Operator Name Method Name Arity(numbers of operands)
Boolean not ! 1
Bitwise complement ~ 1
Unary plus + 1
Exponentiation ** 2
Unary minus - 1
Multiplication * 2
Division / 2
Modulo % 2
Addition + 2
Subtraction - 2
Bitwise shift-left << 2
Bitwise shift-right >> 2
Bitwise and & 2
Bitwise or | 2
Bitwise xor ^ 2
Less than < 2
Less than or equal to <= 2
Greater than or equal to >= 2
Greater than > 2
Equality == 2
Equality === 2
Equality != 2
Pattern matching =~ 2
Pattern matching !~ 2
Comparision <=> 2
Boolean and && 2
Boolean or || 2
Assignment =, *=, =, /=, %=, +=, -=, <<=, >>=, &&=, &=, ||=, |=, ^= 2
Boolean not not 1
Boolean and and 2
Boolean or or 2

You have used most of these operators in various programming languages over the years so it must be pretty clear how they are used.

Still, to iron things out, let us see them in action.

# Boolean not
puts (!false) # prints true
puts (!true)  # prints false

# Bitwise complement
puts "#{5783.to_s(2)} = #{(~5783).to_s(2)}" # prints 1011010010111 = -1011010011000  

# Unary plus
puts (+2) # prints 2

# Exponentiation
puts (8**2) # prints 64(8x8)

# Unary minus
puts (-2) # prints -2

# Multiplication
puts (2*2) # prints 4

# Division
puts (90/3) # prints 30

# Modulo
puts (9 % 2) # prints 1

# Addition
puts (2+2) # prints 4

# Subtraction
puts (100 - 100) # prints 0

# Bitwise shift-left
puts "#{5783.to_s(2)} = #{(5783 << 3).to_s(2)}"  # prints 1011010010111 = 1011010010111000 

# Bitwise shift-right
puts "#{5783.to_s(2)} = #{(5783 >> 3).to_s(2)}"  # prints 1011010010111 = 1011010010

# Bitwise and
puts (true & false) # prints false

# Bitwise or
puts (true | false) # prints true

# Bitwise xor
puts (false ^ false) # prints false

# Less than
puts (2 < 4) # prints true

# Less than or equal to
puts (4 <= 4) # prints true

# Greater than or equal to
puts (6 >= 2) # prints true

# Greater than
puts (6 > 10) # prints false

# Equality
puts (2 == 2 ) # prints true

# Equality
puts (2 === 2) # prints true

# Inequality
puts (2 != 5) # prints true

# Pattern matching
puts (/test/ =~ "abcdefghijtestklmnoqrstuvwxyz") # prints 10
puts (/test/ =~ "abcdefghijklmnoqrstuvwxyz") # prints nil

# Pattern matching
puts (/test/ !~ "abcdefghijtestklmnoqrstuvwxyz") # prints false
puts (/test/ !~ "abcdefghijklmnoqrstuvwxyz") # prints true

# Comparision
puts (5 <=> 10) # prints -1 
puts (10 <=> 5) # prints 1 
puts (5 <=> 5) # prints 0 

# Boolean and
puts (true && false) # prints false

# Boolean or
puts (true || false) # prints true

# Assignment
num = 10
num **= 10
puts num # prints 10000000000

# Boolean not
puts (not false) # prints true

# Boolean and
puts (true and false) # print false

# Boolean or
puts (true or false) # prints true

An interesting thing to know about operators, as you have seen in the previous section, is they are simply methods on an object.

Expanding on that concept, here is the equivalent code for operators (wherever available), calling them as object methods.

# Boolean not
puts (false.!) # prints true
puts (true.!)  # prints false

# Bitwise complement
puts "#{5783.to_s(2)} = #{5783.~.to_s(2)}" # prints 1011010010111 = -1011010011000 

# Unary plus
puts (+2) # prints 2

# Exponentiation
puts (8.**(2)) # prints 64(8x8)

# Unary minus
puts (-2) # prints -2

# Multiplication
puts (2.*(2)) # prints 4

# Division
puts (90./(3)) # prints 30

# Modulo
puts (9.%(2)) # prints 1

# Addition
puts (2.+(2)) # prints 4

# Subtraction
puts (100.-(100)) # prints 0

# Bitwise shift-left
puts "#{5783.to_s(2)} = #{5783.<<(3).to_s(2)}" # prints 1011010010111 = 1011010010111000 

# Bitwise shift-right
puts "#{5783.to_s(2)} = #{5783.>>(3).to_s(2)}" # prints 1011010010111 = 1011010010

# Bitwise and
puts (true.&(false)) # prints false

# Bitwise or
puts (true.|(false)) # prints true

# Bitwise xor
puts (false.^(false)) # prints false

# Less than
puts (2.<(4)) # prints true

# Less than or equal to
puts (4.<=(4)) # prints true

# Greater than or equal to
puts (6.>=(2)) # prints true

# Greater than
puts (6.>(10)) # prints false

# Equality
puts (2.==(2)) # prints true

# Equality
puts (2.===(2)) # prints true

# Inequality
puts (2.!=(5)) # prints true

# Pattern matching
puts ("abcdefghijtestklmnoqrstuvwxyz".=~(/test/)) # prints 10
puts ("abcdefghijklmnoqrstuvwxyz".=~(/test/)) # prints nil

# Pattern matching
puts ("abcdefghijtestklmnoqrstuvwxyz".!~(/test/)) # prints false
puts ("abcdefghijklmnoqrstuvwxyz".!~(/test/)) # prints true

# Comparision
puts (5.<=>(10)) # prints -1 
puts (10.<=>(5)) # prints 1 
puts (5.<=>(5)) # prints 0 

# Boolean and
puts (true && false) # prints false

# Boolean or
puts (true || false) # prints true

# Assignment
num = 10
num **= 10
puts num # prints 10000000000

# Boolean not
puts (not false) # prints true

# Boolean and
puts (true and false) # print false

# Boolean or
puts (true or false) # prints true

Operator methods also serve as reserved keyword method names for when you wish to define an operator based method on your objects.

For example, say we have an employee class and we wish to define an addition method on it.

For our case, let us say we wish to have an addition method on the employee class based on their salaries.

class Employee

  def +(employee)
    @salary + employee.salary
  end

end

Core Data Types

The core data types in Ruby are -numbers -strings -symbols -arrays -hashes -and booleans.

Since it is imperative that you learn to use the correct data type for each use case, let us look at each of them in a bit of detail.

Numbers

Numbers are simply numbers. A number can be an integer like 123 or a decimal like 12.34.

Moreover, numbers can be positive or negative with a leading minus(-) sign.

num1 = 12345
puts num1 # prints 12345

num2 = 123.45
puts num2 # prints 123.45

num3 = -12345
puts num3 # prints -12345

num4 = -123.45
puts num4 # prints -123.45

When declaring numbers, you can use a _ to separate them by thousands for better readability and Ruby will just ignore the underscore. For example, 1_000_000 can be used to declare the number 1000000.

num1 = 1_000_000
puts num1 # prints 1000000

Behind the scenes, different object types are used to account for different types of numbers.

For example, the Fixnum class is used for integers but for very large integers, the Bignum class is used. Similarly, Float and BigDecimal classes are used for small and very large decimal numbers respectively.

The last thing to remember about numbers is, unless there is a decimal involved, the output of a calculation will always be integer, even if mathematically, the calculation results in a decimal based answer.

num1 = 123/2
puts num1 # prints 61

num2 = 123/2.0
puts num2 # prints 61.5

Strings

Strings are a bunch of characters knit together. Apart from using single ' and double " quotes, there are a number of ways you can declare strings.

str1 = 'This is a string'
puts str1 # prints This is a string

str2 = "This is a string"
puts str2 # prints This is a string

str3 = %Q(This is a string)
puts str3 # prints This is a string

str4 = %q(This is a string)
puts str4 # prints This is a string

str5 = String.new("This is a string")
puts str5 # prints This is a string

%Q and %q are the equivalent of double quote and single quote delimiters. You can also define your custom delimiter by prepending it with a %.

str1 = %(This is a string)
puts str1 # prints This is a string

str2 = %{This is a string}
puts str2 # prints This is a string

str3 = %[This is a string]
puts str3 # prints This is a string

str4 = %-This is a string-
puts str4 # prints This is a string

You can declare multi-line strings using here docs. Here docs are simply string delimiters that are prepended with <<. Here docs give you the freedom to spread your strings over multiple lines.

str1 = <<GROCERIES
1. Milk
2. Sugar
3. Banana
4. Orange
5. Strawberries
GROCERIES
puts str1 # prints 1. Milk\n2. Sugar\n3. Banana\n4. Orange\n5. Strawberries\n

A very important concept regarding strings is interpolation. Using the symbol #{}, you can call in dynamic data and variables in your strings.

str1 = "2 + 10 is #{2+10}"
puts str1 # prints 2 + 10 is 12

Notice how the content between #{ and } was treated as numbers and the actual value of the calculation was substituted in the string.

Symbols

Symbols are declared by prepending a colon : to them. If your symbol contains spaces, you have to enclose it within single ' or double " quotes after prepending it with a colon :.

If you are just starting out with Ruby, symbols can be a funny concept to grasp.

For a start, you can think of symbols as tokenized strings. The major difference between strings and symbols is if declared multiple times, no two strings point to the same object. However, in case of symbols, no matter where and how many times you declare them in your programs, a single symbol will always point to the same object.

The following code is a proof of that concept.

str1 = "I am a string"

str2 = "I am a string"

str3 = "I am a string"

puts str1.object_id # prints 70229514771560

puts str2.object_id # prints 70229518541680

puts str3.object_id # prints 70229518606820

str1 = :"I am a string"

str2 = :"I am a string"

str3 = :"I am a string"

puts str1.object_id # prints 1151348

puts str2.object_id # prints 1151348

puts str3.object_id # prints 1151348

Notice how in the case of a symbol, the str1, str2, and str3 objects have the same object_id which proves they point to the same object.

Symbols are highly memory efficient as compared to strings and are used in cases where identical strings are required such as hash keys.

The object_id is a unique id that Ruby uses internally to keep track of all the objects in your program.

Arrays

An array is simply a collection of objects. You can add any type and number of objects into an array and retrieve them using a zero-based index.

Arrays can be declared using multiple ways.

arr1 = ["I", "am", "an", "array"] # results in ["I", "am", "an", "array"]

arr2 = %W(I am an array) # results in ["I", "am", "an", "array"]

arr3 = %w(I am an array) # results in ["I", "am", "an", "array"]

puts arr1[0] # prints I

puts arr1[1] # prints am

puts arr1[2] # puts an

puts arr1[3] # prints array

The push and pop methods available on an array object can enable you to use an array as a stack.

arr = []

arr.push(1) # results in [1]
arr.push(2) # results in [1, 2]
arr.push(3) # results in [1, 2, 3]

arr.pop # returns 3 / arr = [1, 2]
arr.pop # returns 2 / arr = [1]
arr.pop # returns 1 / arr = []

Hashes

You can think of hashes as special arrays where you not only have the freedom to declare the values, but also the keys associated with those values.

The values in a hash can be any object. The keys are usually a string or a symbol.

From our earlier discussion, since symbols are memory efficient, it is advised that you use symbols as keys in your hash.

Another important thing to know about hashes is up until Ruby's version 1.9.3, hashes were declared using =>, which is known as the hash rocket syntax.

hsh1 = { "one" => 1, "two" => 2, "three" => 3 } # results in {"one"=>1, "two"=>2, "three"=>3}

hsh2 = { :one => 1, :two => 2, :three => 3 } # results in {:one=>1, :two=>2, :three=>3}

The hash rocket syntax works for both symbol and string keys.

For versions greater than 1.9.3, a shorthand syntax is available for when you use symbols as hash keys.

hsh = { one: 1, two: 2, three: 3 } # results in {:one=>1, :two=>2, :three=>3}

True, False, and Nil

The true and false objects represent booleans and are used to check for truth values.

In Ruby, you also have a nil object which symbolizes the absence of something. It is used for cases where you wish to check if something exists, not that it is true or false.

The true, false, and nil classes are also represented by the capitalized versions, TRUE, FALSE, and NIL.

Conditionals

You would hardly ever write a computer program that does not make decisions or alters its path of execution based on certain conditions.

Through examples, let us look at the conditional constructs Ruby provides.

income = 1200
expense = 1000

if expense > income
  puts "You need to reduce your expenses"
end

The ever famous if conditional is used to take action if a certain condition is true.

The if conditional can be further extended to check for other cases using elsif and else.

income = 1200
expense = 1000

if expense > income
  puts "You need to reduce your expenses."
elsif income > expense
  puts "You are doing great!"
elsif income == expense
  puts "I also like to live dangerously :)"
else
  puts "How did I get here?"  
end

The elsif conditional allows us to check for the other two possible scenarios in case of income and expense variables. The final else allows us to execute a statement if none of the conditional statement before it turn out to be true.

The opposite of if, the unless conditional is also available.

income = 1200
expense = 1000

unless income > expense
  puts "You need to reduce your expenses."
end

The unless conditional fires for all cases where your provided expression turns out to be false.

The unless conditional can also be extended using the else conditional.

income = 1200
expense = 1000

unless income > expense
  puts "You need to reduce your expenses."
else
  puts "You are doing great!"
end

The else that is paired with an unless would fire when the expression you have provided turns out to be true.

In Ruby, the if and unless conditionals are often mixed with the assignment operator, resulting in a conditional assignment.

income = 1000
expense = 1500

progress = "positive" if income > expense

progress = "negative" unless income > expense

Lastly, you have the case statement that can be used to check a single variable for a range of values.

age = 18

case age
  when 18
    puts "You need to grow up!"
  else
    puts "Enjoying adulthood?"
end

You can also specify a comma-separated list of values to check against a single case.

age = 18

case age
  when 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
    puts "You need to grow up!"
  else
    puts "Enjoying adulthood?"
end

Iterators

Imagine a world where any instruction that needed to be executed a number of times, had to be explicitly repeated in a computer program. What kind of a world would that be?

Savage!

Fortunately, iterators spare us the savagery.

Let us look at the different ways to loop code.

While

The while loop is used to continuously execute a piece of code until a certain condition evaluates to false.

There are two variations of the while loop available. One where you specify the condition before the code to loop, and one where the condition comes after.

num = 0

while num < 5 do 
  puts num
  num += 1
end

# prints 0\n 1\n 2\n 3\n 4\n

num = 0

begin
  puts num
  num += 1    
end while num < 5

# prints 0\n 1\n 2\n 3\n 4\n

Generally, inside the while loop, you would have some sort of code that would eventually cause the condition to evaluate to false, hence breaking out of the loop.

A while loop is also used to keep taking a certain action during the course of a program such as prompting the user for input.

while true do
  num = gets
  puts num
end

Until

An until loop works the exact same way as the while loop. The only difference is an until loop continues to execute as long as the condition evaluates to false.

Just like the while loop, there are also two variations of the until loop available. One where you specify the condition before the code to loop, and one where the condition comes after.

num = 0

until num > 5 do 
  puts num
  num += 1
end

# prints 0\n 1\n 2\n 3\n 4\n 5\n

num = 0

begin
  puts num
  num += 1    
end until num > 5

# prints 0\n 1\n 2\n 3\n 4\n 5\n

For/Each

The for and each loops are used in conjunction with arrays and ranges to execute a piece of code a certain number of times.

Before we move forward, let us have a quick word on ranges.

In Ruby, ranges are simply a way to declare a sequence of consecutive values.

They are declared using the .. and ... operators.

Ranges are inclusive of the starting and ending members in case of the ..operator whereas in case of ..., the ending member is excluded.

rng = 1..100 # rng is integers from 1 to 100

rng = 1...100 # rng is integers from 1 to 99

rng = a..z # rng is alphabets from a to z

rng = a...z # rng is alphabets from a to y

Coming back to for and each loops, here is how to go about them.

arr = [0, 1, 2, 3, 4, 5]

for i in arr do
  puts i
end

# prints 0\n 1\n 2\n 3\n 4\n 5\n

arr.each do | i |
  puts i
end

# prints 0\n 1\n 2\n 3\n 4\n 5\n

rng = 0..5

for i in rng do
  puts i
end

# prints 0\n 1\n 2\n 3\n 4\n 5\n

rng.each do | i |
  puts i
end

# prints 0\n 1\n 2\n 3\n 4\n 5\n

Next/Break

The next and break keywords don't quite allow you to loop a piece of code but since they are used to skip on and break out of iterations respectively, it's worth mentioning them with loops.

The following code sample demonstrates their usage.

rng = 1..100

rng.each do | i |
  if i == 51
    break
  end  
  if i % 2 == 0
    puts i
  else
    next
  end
end

Our rng is declared from one to hundred. All even numbers from one to hundred should be printed. Since we break out of the loop when i becomes fifty one, only even numbers from one to fifty are printed. In case a number is not even, we simply move to the next iteration using next.

Methods/Functions

A method/function definition starts with the def keyword, followed by the name of the function, and ends with the end keyword.

In Ruby, a function always returns something hence the last statement of a function, whatever it evaluates to, is the returned value.

Since the last statement is always the returned value, you can explicitly use the return keyword or skip it, it works the same way.

The following function definitions, though syntactically different, are the same.

# function declaration 1
def multiply(param1, param2)
  return param1 * param2
end

# function declaration 2
def multiply(param1, param2)
  param1 * param2
end

Object-Oriented Constructs

As I have mentioned, Ruby is purely an object-oriented programming language. It provides a comprehensive syntax to write modules and classes.

Apart from writing modules and classes, meta-programming is one of the strongest aspects of Ruby.

Though meta-programming is not a subject for beginners, you can think of it as "computer programs who write computer programs" on the go at runtime, know that it is a part of Ruby, and is one of the sharpest tools in a Rubyist's arsenal.

For now, let us look at how to go about object oriented programming in Ruby.

Diving into a Class Definition

We are going to start with a comprehensive class definition for an employee object, followed by a detailed description and commentary on the code produced forth.

class Employee

  # change scope to public
  public

  # static class variable
  @@count = 0

  # constructor
  def initialize(name, designation, salary)
    @name = name
    @designation = designation
    @salary = salary
    @@count += 1
  end

  # class method
  def self.count
    @@count
  end

  # getters/setters for name, designation, and salary
  def name
    @name
  end

  def name=(name)
    @name = name
  end

  def designation
    @designation
  end

  def designation=(designation)
    @designation = designation
  end

  def salary
    @salary
  end

  def salary=(salary)
    @salary = salary
  end

  # change scope to private
  private

  def organization
    "Hackers de Anonymous"  
  end

  # change scope to protected
  protected

  def owner
    "We never mention his name!"
  end

end

We start with the class keyword followed by the name of the class which in this case is, Employee.

Inside a class, we can use the public, private, and protected keywords to scope our methods and variables.

Since we want to make the next series of methods and variables public, we specify the public scope. Publicly scoped class members are accessible from within and outside the class.

The employee class has a static variable @@count to keep an account of total employee objects. Static variables are declared using @@ followed by the name of the variable.

initialize is a reserved keyword in Ruby which is used to declare the constructor method for a class.

For our constructor, we simply pass in the name, designation, and salary of the employee and set the respective instance variables of the same name. Instance variables are declared using @ followed by the name of the variable.

The next part of the class is a static method defined on the employee class, which simply returns the total count of the employee objects created.

Notice how the self keyword has been used.

In Ruby, a class definition is an instance of the class Class. Our employee class inherits from the object Class so specifying the self keyword gives it the context of the current class, which is the employee class. Thus, our method is defined on the employee class object, and not the employee object.

I am sure this sounds a bit tricky and hard to get your head around so for now, let's keep it simple. Just remember to use the self keyword when declaring class methods.

The next six methods simply define getters and setters for name, designation, and salary respectively.

The getter methods are pretty straight forward. However, notice the = in the setter method's name.

The equality symbol in a setter method's name is a Ruby convention that needs to be followed when declaring setter methods. Generally, setter methods are declared using the template def <instance-variable-name>=(<instance-variable-name>).

Once we are done with the publicly accessible parts of our class, we change the scope to private using the private keyword. It means that the variables and methods that follow are private. Privately scoped class members are accessible from within the class only.

Finally, towards the end of the class definition, we change the scope to protected. Protected methods are accessible from within child classes.

Speaking of child classes, in Ruby, classes are inherited using the < operator.

So, let us say, we wanted to have a SoftwareEngineer class that inherits from the employee class.

In such a case, our class definition would go as follows:

class SoftwareEngineer < Employee

  # code for SoftwareEngineer class

end

Coming back to the employee class, though our class definition is correct and the code works, I would like to introduce a few more Ruby constructs which would greatly simplify our class definition.

The first thing to know is the default scope inside a class is public so you do not have to specify it explicitly.

For getter and setter methods, Ruby provides the attr_reader, attr_writer, and attr_accessor functions.

The attr_reader function is used to specify readable class attributes.

The attr_writer function is used to specify writable class attributes.

The attr_accessor function is used to specify class attributes that are both, readable and writable.

All three functions accept symbolized, comma separated attribute names and the getter/setter methods are automatically generated.

Incorporating the above improvements, here is our revised class definition.

class Employee

  attr_accessor :name, :designation, :salary

  @@count = 0

  def initialize(name, designation, salary)
    @name = name
    @designation = designation
    @salary = salary
    @@count += 1
  end

  def self.count
    @@count
  end

  private

  def organization
    "Hackers de Anonymous"  
  end

  protected

  def owner
    "We never mention his name!"
  end

end

Globals and Constants

If you wish to declare global variables in your program, just prepend the variable name with a $ and the variable will be accessible from anywhere in your program.

$some_global = "some important global value"

Global variables are generally not very famous among programmers because they can make the process of isolating problems very difficult. They can also make debugging your programs harder. Due to these reasons, use global variables sparingly and only in situations, where an alternative is not available.

Similarly, constants can be declared by capitalizing the first letter of their name.

Constant = 5

Though the Ruby interpreter does not ensure that a constant's value remains unchanged, it will produce a warning in case you re-declare a constant.

The following code snippet produces the already initialized constant warning.

Constant = 5

puts "Do some great stuff!"

Constant = 10

Generally, by convention, constant names are fully capitalized.

CONSTANT = 5

Exceptions

Code that may produce an exception is wrapped inside a begin/rescue block.

Let us write the famous "division by zero" code to see exception handling in action.

result = 100 / 0

puts result

The above code produces the divided by 0 (ZeroDivisionError) error.

If you were say, writing a calculator application in Ruby, handling such exceptions would be paramount to your application functioning gracefully.

begin
  result = 100 / 0
  puts result
rescue StandardError => e
  puts "You cannot divide by 0"
end 

Now, when the exception is raised, control is immediately shifted to the rescue block and your code outputs You cannot divide by 0.

Also, all exceptions inherit from the base class Exception so in your code, you can rescue Exception instead of StandardError.

However, the problem with that approach is Exception is at the top of the food chain so if you rescue it, your code would just swallow everything, even the syntax errors.

Luckily, as mentioned, you have a toned down version in the StandardError class that makes sure your code only catches the useful exceptions.

The last bit of useful information regarding exceptions is the ensure block.

The ensure block encapsulates code that executes irrespective of whether the exception occurs or not.

Revising our earlier code with the ensure block.

begin
  result = 100 / 0
  puts result
rescue StandardError => e
  puts "You cannot divide by 0"
ensure
  puts "Exception or no exception, now we here!"
end 

Working with Distributed Code

Ruby has the extend, include, load, and require keywords to incorporate your code from external files.

Let us write a very simple class that we can use to understand how load and require works.

Create a new file named gandalf.rb and add the following code.

class Gandalf
  def speak
    puts "You shall not pass!"
  end
end

load

When you use the load keyword to load a file, you need to provide the complete filename otherwise you will receive a cannot load such file error.

At the same location where you created the gandalf.rb file, create another file test.rb and add the following code.

load './gandalf.rb'

Gandalf.new.speak

The code produces the output You shall not pass! which means our external class was loaded successfully.

When you load a file, it is loaded as many times as you load it.

For example, let us say you have three classes A, B and C. Class A and B both load class C. When you load class A and B, class C will be loaded twice into memory.

Of course this sounds a bit inefficient, which is where the require keyword comes in.

require

When you use require to load a file, it is loaded only once into memory irrespective of how many times you require it.

You can also skip the file extension if it's .rb when you require a file.

require './gandalf'

Gandalf.new.speak

Mix-Ins

Before we move to understanding include and extend, you should know a Ruby concept known as "mix-in".

A mix-in is simply modularized code that can be merged into your classes.

Let's walk through an example.

Suppose you are writing classes for different animals in the animal kingdom.

Since all animals eat, you need an eat instance method on all of them. So, if we have a Cat, Dog, and Human class, our design would go something like.

# file: cat.rb
class Cat
  def eat
    puts "I am chewing!"
  end
end

# file: dog.rb
class Dog
  def eat
    puts "I am chewing!"
  end
end

# file: human.rb
class Human
  def eat
    puts "I am chewing!"
  end
end

Code repetition is never a wise move and since Ruby does not support multiple inheritance, this seems to be the only option.

Modularizing Code

First, we are going to move the eat method into a separate module named BasicFunctions. While we are at it, just for demonstration, let's throw in the drink, and sleep methods as well.

Create a file (at the same location where you created cat.rb, dog.rb, and human.rb) named basic_functions.rb and add the following code.

module BasicFunctions
  def eat
    puts "I am chewing!"
  end

  def drink
    puts "Slurp! Slurp! Slurp!"
  end

  def sleep
    puts "Zzzzzzzzzzzzzzzzz!"
  end
end

Let's get back to understanding include and extend then, shall we?

include

When you use the include keyword to add external code into your classes, the methods you define in your module are added as instance methods to your class.

Instead of repeating code, we can now re-write our Cat, Dog, and Human class using include.

# file: cat.rb

require './basic_functions'

class Cat
  include BasicFunctions
end

# file: dog.rb

require './basic_functions'

Class Dog
  include BasicFunctions
end

# file: human.rb

require './basic_functions'

Class Human
  include BasicFunctions
end

And here is the Cat, Dog, and Human class in action.

require './cat
require './dog'
require './human'

cat = Cat.new
cat.eat
cat.drink
cat.sleep

dog = Dog.new
dog.eat
dog.drink
dog.sleep

human = Human.new
human.eat
human.drink
human.sleep

extend

The extend keyword works like the include keyword, the only difference is the methods are added as class methods to your class.

require './basic_functions'

class Cat
  extend BasicFunctions
end

Class Dog
  extend BasicFunctions
end

Class Human
  extend BasicFunctions
end

Since eat, drink, and sleep are now class methods, we do not need object instances anymore.

require './cat
require './dog'
require './human'

Cat.eat
Cat.drink
Cat.sleep

Dog.eat
Dog.drink
Dog.sleep

Human.eat
Human.drink
Human.sleep

Using Ruby Gems

Ruby gems are packaged modules/classes that can be used to extend Ruby's core functionality.

If you require certain functionality in your Ruby applications, you can search for a gem and use it instead of writing the code yourself.

For example, let's say you are building an HTML email parser that automatically extracts certain information from incoming HTML emails and dumps them into the database.

In such a scenario, instead of writing complex parsing logic, you can use a markup parsing gem such as Nokogiri.

First, install the gem from the command line.

$ gem install nokogiri

Then, in your code file, require the gem and you are good to go.

require 'rubygems' # required in Ruby versions less than 1.9.*
require 'nokogiri' # require the nokogiri gem

# your code that uses the nokogiri gem

The official Ruby gems repository has millions of gems that you can search from.

If you are using a Ruby version lesser than 1.9.*, you might have to setup Ruby gems separately. The official Ruby gems installation guide explains how to do it.

Curtains

Our run through of the Ruby language is almost at it's end but hopefully, not your love for the Ruby programming language.

Ruby was created to make programming fun again and as you work with it over the years, you will realize the creators of Ruby have succeeded emphatically in that endeavour.

Where to go from here, you did ask.

I have found that there are multiple ways to do a single thing in Ruby so it can get hard at times, to consolidate code from all the different developers. Perhaps, reading a Ruby coding standards guide next would be a good idea?

I hope you found this tutorial interesting and knowledgeable. Until my next piece, happy coding!

Noman Ur Rehman

I am a full stack, freelance web developer who specializes in Laravel, Rails, and Amazon Web Services. I love Regular Expressions and discussing ideas.