Vous êtes sur la page 1sur 54

Written by Marin

The basics of the Ruby programming language


The purpose of this document is to teach people who are new to Ruby or programming as a
whole about some general programming concepts that apply to many more languages, but
also about Ruby specifically. The clean syntax of Ruby will make it easier to understand
everything and follow along with the tutorials.

Please note that there is so much more to Ruby than just what is covered here. These are
merely the basics with which you will be able to write your own programs or edit code you’ve
seen somewhere without having to worry about breaking things.

It is suggested that you follow the order of the Table of Contents, as they are listed from
easy to hard. You will not understand Modules if you don’t understand Classes, for instance.

If you’d like to link to this document, you also have the ability of linking to
https://bit.ly/RubyBasics​.

Cyan: This represents an additional note which may be confusing to beginners. It is mainly
meant for people who already know a bit about programming or have experience with other
programming languages. It’s also used to give extra information sometimes for those who
understand it and/or are interested in it. You will not miss anything important by skipping
over these.

Table of Contents
● 1​: Variables and data types
● 2​: Conditions
● 3​: Methods
● 4​: Arrays
● 5​: Ranges and for-loops
● 6​: Classes and objects
● 7​: More classes and polymorphism
● 8​: More variables and scopes
● 9​: Hashes
● 10​: Exception handling
● 11​: Blocks and Procs
● 12​: IO, Marshal, and Zlib
● 13​: Modules
1: Variables and data types
Every proper programming language has variables. These variables are used throughout
your code or program to hold certain information so you can use it whenever you’d like.
Such a variable also has a name. You can look at it as a box: the name is like a label which
allows you to identify the different boxes (variables) you have, and the information/data the
variables store can be considered the content of those boxes.

Data types
The information a variable stores is also called the ​value​ of the variable. This value is of a
certain type: a number, text, a fraction, a list of things, or else. There are a few names to
remember here:
● Fixnum​: This is an ​integer​, referring to any number without any decimal numbers
(e.g. ​3​, ​-85​, ​9230​)
● Float​: This is a ​fraction​, referring to any number ​with​ decimal numbers (e.g. ​5.6​,
-372.5234​, ​2.0​)
● String​: A series of characters which together represent a word, sentence, or else.

Here’s where we start coding: we’re going to ​define​/​initialize​/​create​ a variable by giving it a


name and a value. We do this like so:
my_variable = ​10

Here, we’ve created a new variable called ​my_variable​ and given it a value of ​10​. 10 is a
Fixnum (integer), so we would say that ​my_variable is a Fixnum​ or ​my_variable is of type
Fixnum​.

Ruby is a dynamically typed language, which means you do not need to specify a data type
(such as Fixnum, Float, or String) when defining a variable. The value of the variable does
not always need to have the same type and may change, unlike statically typed languages
such as C#.

Strings
While Fixnums and Floats are very basic (​5​, ​8.3​), Strings have a bit more to them.
Let’s say we want to put ​Hello world!​ in a string. A string is created by surrounding the text
you want to put into the string with double quotes. This would get us ​"Hello world!"​. This
is now a valid ​string​ with the content of ​Hello world!​.

There are a few exceptions to this basic rule though. If you want to use a ​double quote​ (​“​)
inside​ the string, such as in ​I like the word “wholesome”​, you have to put a ​backslash​ (​\​)
before each double quote. In string-form, this would look like ​"I like the word
\"wholesome\""​.
Aside from with double quotes, if you want to use an actual backslash ​inside​ the string, you
have to add a backslash before that backslash. For instance, ​Did you backup your C:\ drive?
would be ​"Did you backup your C:\\ drive?"​ as a string.
This process of adding backslashes to certain characters is called ​escaping​ that character.
When you escape the letter ​n​, which would look like ​\n​ when used inside a string, is a new
line, for example.

If you wanted to create a variable of the String type, you would write down something akin to
this:
my_greeting = ​"Hello everyone!"

To print out the value of a variable, ​"Hello everyone!"​ in the example above, for now, you
should use ​print​.
print my_greeting ​# This prints out "Hello everyone!" to the screen or
console.

As you can see, everything that comes after the ​#​ is grey. This is a comment, and not
actually part of the code. You can use it to leave notes inside your code to make it more
readable or understandable, which is what I will be doing a lot in this tutorial series.
Whenever I use ​#=>​, that still is a comment, I just add the ​=>​ part to indicate that it’s being
printed out.

Basic mathematical operations


The basic mathematical operations all have a unique ​mathematical operator​:
● +​ Addition
● -​ Subtraction
● *​ Multiplication
● /​ Division
● **​ Power To

Let’s start by getting a number.


​ 0
var = 1
print var ​#=> 10

To perform math, we take either a number or a variable and use one of the operators listed
above.
print ​10​ + ​2​ ​#=> 12
print ​10​ - ​2​ ​#=> 8
print ​10​ * ​2​ ​#=> 20
print ​10​ / ​2​ ​#=> 5
print ​10​ ** ​2​ ​#=> 100 (because 10 * 10 = 100)
This was with numbers, but we can also do this with plain variables:
num1 = ​10
num2 = ​2
​ => 12
print num1 + num2 #
print num1 * num2 #​ => 20

Everything we’ve done so far doesn’t actually change anything though. It just performs the
math we want, and that’s that. If we want to actually ​change​ the value of a variable, we have
to ​reassign​ to that variable.
var = ​10
print var ​#=> 10
var = ​10​ + ​2
print var ​#=> 12
var = var + ​2​ ​# var was set to 12, so 12 + 2 = 14
print var ​#=> 14

So based on this example, to increment a variable by 2, you should use ​var = var + 2​. While
this is fine, you can write it in a shorter way. Introducing:
● var = var + ​2​ is the same as ​var += ​2
● var = var - ​2​ is the same as ​var -= ​2
● var = var * ​2​ is the same as ​var *= ​2
● var = var / ​2​ is the same as ​var /= ​2
● var = var ** ​2​ is the same as ​var **= ​2

Obviously, these also work with variables rather than numbers:


a = ​10
b = 2​

a *= b
​ => 20
print a #
a **= b #​ a is now 20 and b is still 2, so 20 ^ 2 = 20 * 20
print a # ​ => 400 (20 * 20 = 40)
2: Conditions
We now know how we can define variables and give them different values, and we can even
do some basic math, but that’s it. In this tutorial, you’ll learn how to check variables to be
equal to something, greater than, and so forth. We do this using another set of ​operators​: the
relational operators​:
● ==​ Equal to
● >​ Greater than
● <​ Less than
● >=​ Greater than OR Equal to
● <=​ Less than OR Equal to
● !=​ NOT Equal to

We use this with a variable or number/string/int/etc on the left, and what you want to check
for on the right.
print ​15​ == ​15​ ​#=> true
print ​15​ > ​15​ ​#=> false
print ​15​ < ​15​ ​#=> false
print ​15​ >= ​15​ ​#=> true
print ​15​ <= ​15​ ​#=> true
print ​15​ != ​15​ ​#=> false

We can also do this with variables, like so:


a = ​9
b = ​10
print a == b ​#=> false
print a > b ​#=> false
print a < b ​#=> true
print a >= b ​#=> false
print a <= b ​#=> true
print a != b ​#=> true

If-statements
We can use these ​conditions​ inside ​if-statements​ too. These basically check if the given
condition is ​true​, and if so, runs the code inside it.
if​ 1​ 0​ > ​9
​# This code will run!
end

Notable here is the ​end​. Certain ​keywords​, including ​if​, ​must​ always be “closed”, so to
speak. We do this by writing down ​end​ after we write down the keyword. Where we do so
and what happens when we do so, depends on the keyword. I’ll touch upon them all briefly
when introducing a new keyword. If you forget the ​end​, though, your code will raise the
infamous ​SyntaxError​ because there are either too many or too few ​end​s.
Because we know ​10​ > ​9​ is always true (and can’t-not be true), it basically acts like so,
internally:
if​ t​ rue
​# This code will always run!
end

if-statements test the condition for being true, as mentioned. This means it essentially does
(condition) == ​true​. If ​(condition)​ is true, it will essentially evaluate as ​true​ == ​true​,
and so it will always run the code inside.

Similarly, if you were to write ​if​ ​false​, that will never run, because ​false​ == ​true​ is
false.

This naturally also works perfectly fine with variables.


a = ​9
b = ​10
if​ a > b
​# Snippet A
end

c = a > b
if​ c
​# Snippet B
end

One thing new in this example is ​c = a > b​. ​a > b​ is, as I said, a ​condition​, and a
condition is either true or false. Since the condition ​a > b​ is actually ​false​, ​c​ will be ​false​.

Since you can do ​c = ​true​ and ​c = ​false​, that means ​true​ and ​false​ are also data types.
true’s data type is ​TrueClass​, and false’s data type is ​FalseClass​.

Keyword Else
If you want to check if a condition is NOT true, you could either do the opposite (for instance,
the opposite of ​a > b​ is ​a <= b​ if you think logically, and the opposite of ​a == b​ is ​a != b​)
or use the ​else​ keyword. See the example below:
age = ​17
if​ age >= 1 ​ 8
​# 18+
else
​# 17 or younger
end
In this example, we’re checking if age is greater than or equal to 18. If that’s true, it runs the
code where ​# 18+​ is. If it’s ​false,​ though, it will see if there is an else-condition - which there
is, in this case, and run the code inside there, which is ​# 17 or younger​ here.

Logical operator NOT


You can have multiple conditions inside one conditions too. Let’s say we have condition A,
which is true, and condition B, which is false. If we wanted to run a certain piece of code only
if A is true and B is false, we could write something like this:
a = ​true
b = ​false
if​ a
​if​ !b
​# Run our code
​end
end

New here is ​if​ !b​. what ! basically does it ​invert​ the variable. If ​b​ is false, ​!b​ is true, ​!!b​ is
false, ​!!!b​ is true, etc. We use this to check for a variable being false - we hardly ever use ​b
== ​false​, but instead do ​!b​.

This also works for false and true themselves; ​!​false​ ​#=> true​, ​!​true​ ​#=> false

Logical operator AND


Rather than two separate if-statements, though, we can also ​combine​ them with ​logical
operators​:
● &&​ And
● ||​ Or
● !​ Not

I’ve just showed off all ​!​ Not does, but there’s still ​&&​ and ​||​.
Since we only want our code to run if A is true and B is false, we can look at the two
conditions we have, ​a​ and ​!b​, and combine those two. Since we want BOTH to be true, we
use AND. This would get us ​a && !b​. What we’ve done now is combine two conditions into
one condition which we can use in just one if-statement, like so:
a = ​true
b = ​false
if​ a && !b
​# Run our code
end
This works exactly the same with numbers, floats, and strings. You’ll just need some other
operators:
age = ​10
if​ age == ​10
​# Code A
end
if​ age == ​11
​# Code B
end
if​ age != ​10​ && age != ​11
​# Code C
end

In this example, we want to run Code A if age is 10, Code B if age is 11, and Code C
otherwise. One part of the condition for Code C is ​age != ​10​, which checks if age is not
equal to 10. It could still be 11 though, so we check for that too using ​age != ​11​. If that’s the
case, we can run Code C and we’d have our intended behaviour.

Keyword Elsif
The code above isn’t great though. We can compress it into fewer lines using the ​elsif
keyword. What we wrote above will check every if-statement, no matter if it has run any
previous code or conditions.

If ​age == ​10​ is true, for instance, we’re sure ​age == ​11​ and ​age != ​10​ are going to be
false, so we wouldn’t really need to even test those conditions anymore. What this means is
that we could ​skip​ those conditions, which we can achieve with ​else​. This would get us
something like this:
age = ​10
if​ age == ​10
​# Code A
else
​if​ age == ​11
​# Code B
​end
​if​ age != ​11
​# Code C
​end
end
The same applies to ​age == ​11​ and ​age != ​11​, so we can do the same there:
age = ​10
if​ age == ​10
​# Code A
else
​if​ age == ​11
​# Code B
​else
​# Code C
​end
end

We can compress this even further with ​elsif​ though:


age = ​10
if​ age == ​10
​# Code A
elsif​ age == ​11
​# Code B
else
​# Code C
end

It first tries ​age == ​10​. If that’s true, it runs Code A. If it’s false, it tries ​age == ​11​. If that’s
true, it runs Code B. If it’s false, it’ll run the else-condition if present - which it is here.

Logical operator OR
Aside from NOT and AND, there’s also OR. This takes two conditions and as a whole, will be
true if either one of them is true.

Let’s say we want to know if ​age == ​10​ OR ​age == ​11​. We’d combine those two conditions
into one with ​||​, which gets us ​age == 1
​ 0​ || age == ​11​.
age = ​11
if​ age == ​10​ || age == ​11
​# You are either 10 or 11 years old
end
Operator precedence
You should be careful if you use the OR condition in your code anywhere, as it may give you
undesired results. Just like in math, certain symbols/operators have precedence over others.
Let’s say we want to run Code A if gender is 0 and age is 10 or 11. We could write
something like this:
gender = ​0
age = ​10
if​ gender == ​0​ && age == ​10​ || age == ​11
​# Code A
end

...But this isn’t the behaviour we want. ​gender == ​0​ && age == ​10​ is one part of the
condition, and sure, that’s what we want. But the other part is ​age == ​11​, and if that’s true,
the condition as a whole is also true, regardless of the gender. And we don’t want that.

One way we can do resolve this is by adding another if-statement:


gender = ​0
age = ​10
if​ gender == ​0
​if​ age == ​10​ || age == ​11
​# Code A
​end
end

And that’d be that. While valid, sure, we can also use parentheses, just like in math. It
changes the precedence to give priority to whatever is in those parentheses and executes
that first.
gender = ​0
age = ​10
if​ gender == ​0​ && (age == ​10​ || age == ​11​)
​# Code A
end

This first executes ​age == ​10​ || age == ​11​, and that would be true in this case. It takes
the result, ​true​, and combines ​that​ with the condition as a whole: ​gender == ​0​ && ​true​. So
now, the only extra condition it has to check is ​gender == ​0​, and if that’s true, the condition
as a whole is true, and therefore Code A would be run.

You can add parentheses wherever and whenever you want. ​(gender == ​0​ && ((age ==
10​) || (age == ​11​)))​ is the exact same, for instance. Just make sure the amount of
opening and closing parentheses match up, or you’ll be greeted with a SyntaxError.
3: Methods
In short, all methods are, are shortcuts of longer bits of code. To get straight into creating
your own method, methods are created by writing the ​def​ keyword (as in “define”) followed
by the name you want to give your method, and then closed with ​end​, just like with
if-statements.
def​ ​my_method
print ​"Printing from my_method!"
end

my_method ​#=> "Printing from my_method!"

In this example, we’ve created a new method called ​my_method​, which prints out
"Printing from my_method!"​. We then close that method with ​end​ and ​call​/​run​/​execute
the code inside that method by simply writing down the name of that method, ​my_method​.

In various other languages, to call a method you have to follow the method name with (). In
Ruby, those parentheses are optional. They make it an explicit call; more on this near the
end of this tutorial.

Method scope
Let’s try to use a variable we created outside of the method inside the method.
a = ​10

def​ ​work
print a
end

work ​#=> NameError: Undefined variable or method 'a'

The ​work​ method is trying to access the ​a​ variable we created before defining the method.
But methods ​cannot​ access normal variables that were created outside the method.

​ ork
def​ w
a = ​10
end

work

print a ​#=> NameError: Undefined variable or method 'a'

In this example, we defined and called a method called ​work​, which creates a new variable
called ​a​ inside that method. That variable then exists inside that method, but when you try to
call it ​outside​ of that method, it raises an error. The variable ​only​ exists inside that method.
All this is because ​methods introduce a new scope​. This means you can’t access
variables created inside that new scope, and when inside that new scope, you can’t access
variables created outside that scope.

# This is the main scope.

​ ork
def​ w
​# This is the scope of the 'work' method
end

# This is the main scope.

Similarly, this is totally possible:


a = ​10
print a ​#=> 10

def​ ​work
a = ​2
print a
end

print a ​#=> 10

work ​# This method prints out '2'.

# But the 'a' variable remains unchanged.


print a ​#=> 10

Whenever the ​work​ method is called, it creates a new ​a​ variable inside that new scope,
which only exists inside that scope. When the method ends, it goes back to the main scope
and the ​a​ variable of the ​work​ scope no longer exists. But since we’re back in the main
scope, we still have the ​a​ variable of that main scope, which is still 10 because it wasn’t
changed.
Method arguments
Although we can’t actually access variables created outside of a method, we can still give
information ​to​ that method by giving it an ​argument​.
def​ ​work​(my_argument)
print my_argument
end

work(​1​) ​# Prints out '1'

When defining a new method, you can specify arguments between parentheses after the
name (separated with commas). When calling a method, you can give the method an
argument by putting a variable between parentheses after the call to the method, just like
work(​1​)​.

To further clarify the method above, this is essentially what happens internally:
def​ ​work
my_argument = ​1
print my_argument
end

The method expects 1 argument, which it calls ​my_argument​. The first argument passed
when we call the method is ​1​, so it essentially executes ​my_argument = ​1​ inside that
method. This means it creates a new variable and allows you to use it inside the method.
This also works for multiple arguments:
def​ ​work​(a, b, c)
print a * b * c
end

work(​1​, ​2​, ​3​) ​#=> 6

Look at it like so:


(a, b, c)
|| || ||
(1, 2, 3)

a = ​1​, ​b = ​2​, ​c = ​3

The amount of arguments the method expects and the amount of arguments given have to
match up though. The ​work​ method above expects three arguments (a, b, and c).
work(​1​, ​2​, ​3​)​ is valid because it provides three arguments.
work(​1​, ​2​)​ is invalid because it provides 2 out of 3 arguments (ArgumentError).
work(​1​, ​2​, ​3​, ​4​)​ is invalid because it provides 4 for 3 arguments (ArgumentError).
An exception to this, though, are methods that have ​default arguments​. This means that a
certain argument will take on a certain value if no value is given.
def​ ​mult​(a, b = ​1​)
print a * b
end

mult(​6​, 1
​ ​) ​#=> 6
mult(​6​) #​ => 6 (b ​defaults​ to 1)
mult(​6​, 2 ​ ​) ​#=> 12

However, ​mult(​6​, ​2​, ​5​)​ will still raise an ​ArgumentError​, because it’s giving too many
arguments. So would ​mult​, because it doesn’t give any arguments at all.

Explicit and implicit calls


You ​can​ actually have variables and methods with the same name. Let’s say we have a
method called ​var​ and a variable called ​var​. If you wanted to refer to the variable, you’d write
down just ​var​. This normally calls the method, but since there’s also a variable with that
name, it’ll give priority to that variable. If you want to call the method though, you’d only have
to add ​()​ and it would call the method.
var = ​"Variable!"

def​ ​var
print ​"Method!"
end

print var ​#=> "Variable!"


print var() ​#=> "Method!"

When we use ​var​ to refer to a method, we call the implicit. When we use ​var()​, we call that
explicit - it can’t be anything else, it’s not ambiguous, and it makes perfectly clear that it’s
supposed to call a method. Therefore, if there isn’t a method called var and we use var(), it’ll
treat it as a method, even if it doesn’t exist.
var = ​"Variable!"

print var ​#=> "Variable!"


print var() ​#=> NameError: undefined method 'var'
Return value
Whenever you call a method, that method call will ​return​ something. This means that, when
the method call is done, it will give you something. A number, true/false, a string, nothing
(nil), or else.
def​ ​work​(a_number)
​return​ a_number
end

work(​6​) ​# Nothing happens

print work(​6​) ​#=> 6

Whenever we call ​work​ with an argument, it returns the given argument. So ​work(6)​ is
basically the same as just ​6​. And as you can imagine, when you just write down 6 without
anything, no assignments, no calls, no nothing, it calls the method and it’s done. It doesn’t
do anything, just like ​work(​6​) ​# Nothing happens​.

However, we can also make it do something else to better illustrate the use-cases for this.
def​ ​mult​(a, b)
​return​ a * b
end

print mult(​5​, ​6​) ​#=> 30

var = ​0
print var ​#=> 0
var = mult(​5​, ​6​)
print var ​#=> 30

The line ​var = mult(​5​, ​6)


​ ​ is essentially turned into v
​ ar = ​30​, because ​mult(5, 6)​ ​returns
30. It gives back 30 to where it’s called from.

return​ also immediately quits the method. Any code that would come after it is never called.
def​ ​work
​return​ ​1
print ​"This will never be printed!"
​return​ -​1
end

print work ​#=> 1


Nil
The ​print​ method itself returns ​nil​, which is another data type (NilClass). You can assign it to
variables, too.
var = print ​1​ ​# Prints out 1
print var ​#=> nil
print print ​1​ ​#=> nil
print print print ​1​ ​#=> nil
print print var ​#=> nil

Yes, you can call methods in method arguments. And even methods in those. And methods
in those.
def​ ​mult​(a, b = ​2​)
​return​ a * b
end

print mult(mult(mult(mult(​2​)), mult(​2​))) ​#=> 64


Wouldn’t suggest ever doing this though. It’s not exactly readable.

Explicit and implicit


In the examples above we’re explicitly using the ​return​ keyword to give back information to
where the method was called from, but there’s also an implicit way to do so. By default,
methods will return the very last line of code inside the method.
​ ult​(a, b = ​2​)
def​ m
a * b
end

var = mult(​2​, 2
​ ​)
print var ​#=> 4

So if you were to add anything below the line you’d want to be returned, it would return the
new last line instead. This is called ​implicit​.
def​ ​mult​(a, b = ​2​)
a * b
-​1
end

var = mult(​2​, 2
​ ​)
print var ​#=> -1
But then again, you could just use the explicit ​return​ keyword and all would be good.
def​ ​mult​(a, b = ​2​)
​return​ a * b
-​1​ ​# This is never executed
end

var = mult(​2​, 2
​ ​)
print var ​#=> 4

You can also just write ​return​ without anything after it. In that case, it’ll just give back ​nil
and act as though it said ​return​ n​ il​.

A few use-case examples:


def​ ​get_seconds​(minute)
​if​ minute < ​0
​return​ ​"You cannot have a negative amount of minutes!"
​end
​return​ minute * ​60​ ​# The 'return' is optional here. It would work fine
without.
end

print get_seconds(​4​) ​#=> 240


print get_seconds(​1​) ​#=> 60
print get_seconds(​0​) ​#=> 0
print get_seconds(-​1​) ​#=> "You cannot have a negative amount of
minutes!"

def​ ​seconds_lived​(age)
​if​ age < ​0
​return​ ​0
​elsif​ age > ​122
​return​ ​"Nobody has ever lived longer than 122 years!"
​end
​return​ ​60​ * ​60​ * ​24​ * ​365​ * age
end

print seconds_lived(​16​) ​#=> 504576000


print seconds_lived(​121​) ​#=> 3815856000
print seconds_lived(​122​) ​#=> 3847392000
print seconds_lived(​123​) ​#=> "Nobody has ever lived longer than 122
years!"
print seconds_lived(​0​) ​#=> 0
print seconds_lived(-​1​) ​#=> 0
4: Arrays
Arrays are essentially lists of multiple values. Just like we have Fixnum, String, Float,
FalseClass, TrueClass, and NilClass, ​Array​ is also a data type. An array is an ordered list of
values which are usually called ​elements​ or ​entries​. Each element has a certain ​index​ with
which they’re referred to, for instance, Element 0, Element 1, Element 2 - that’s right,
indexes start at ​0​ instead of 1. An array is created by writing down square brackets, ​[]​, and
inserting your values while separating them with commas. Here’s a basic example of what
an array could look like:
my_array = [​7​, ​3​, ​34​]

At index 0, the first element of the array, we have ​7​.


At index 1, the second element of the array, we have ​3​.
At index 2, the third and last element of the array, we have ​34​.

This means that the ​size​ of the array is 3, but the last index is actually 2.
To retrieve an element of the array at a certain index, you would write down ​[index]​ after
the array.
my_array = [​1​,​2​,​3​]
print my_array[​0​] ​#=> 1
print my_array[​1​] ​#=> 2
print my_array[​2​] ​#=> 3
(You can have as many spaces as you want when creating an array, you can either have no spaces at all or you
can have tons)

You can also change an element just like you would assign to a normal variable. Where
my_array = ​7​ would change the whole array to 7, ​my_array[​1​] = ​7​ would only change
the second element (index 1) to 7.
my_array = [​1​,​2​,​3​]
print my_array ​#=> [1,2,3]

my_array[​1​] = ​7
print my_array ​#=> [1,7,3]

my_array = ​7
print my_array ​#=> 7
Different data types inside arrays
You can have all sorts of data types inside an array.
Unlike in statically typed languages like C and C#, an array does not accept just one data
type. You can have floats, integers, strings, array, hashes, custom objects, booleans,
whatever you want, all in one array.
a = [​"hello"​, ​3.14​, [​"this"​, ​"is"​, ​"an"​, ​"array"​, [​"in"​, ​"an"​,
[​"array"​]]], [​"i"​, ​"bet"​, ​"you"​, ​"like"​, ​"that"​]]
print a[​0​] ​#=> "hello"
print a[​1​] ​#=> 3.14
print a[​2​] ​#=> ["this", "is", "an", "array", ["in", "an", ["array"]]]
print a[​2​][​0​] ​#=> "this"
print a[​2​][​1​] ​#=> "is"
print a[​2​][​2​] ​#=> "an"
print a[​2​][​3​] ​#=> "array"
print a[​2​][​4​] ​#=> ["in", "an", ["array"]]
print a[​2​][​4​][​0​] ​#=> "in"
print a[​2​][​4​][​1​] ​#=> "an"
print a[​2​][​4​][​2​] ​#=> ["array"]
print a[​2​][​4​][​2​][​0​] ​#=> "array"
print a[​3​] ​#=> ["i", "bet", "you","like", "that"]

Going outside of bounds


If you choose an index that would be larger than the highest index currently in an array,
you’ll be given ​nil​.
In various other languages, this would raise an ArgumentOutOfRange error.
a = [​1​,​2​,​3​]
print a ​#=> [1,2,3]
print a[​0​] ​#=> 1
print a[​1​] ​#=> 2
print a[​2​] ​#=> 3
print a[​3​] ​#=> nil
print a[​4​] ​#=> nil
print a[​3242​] ​#=> nil
You can actually assign variables when you’re out of bounds though.
a = [​1​,​2​,​3​]
print a ​#=> [1,2,3]
print a[​0​] ​#=> 1
print a[​1​] ​#=> 2
print a[​2​] ​#=> 3
print a[​7​] ​#=> nil
print a ​#=> [1,2,3]
# Everything is like normal so far

a[​7​] = ​8
print a ​#=> [1,2,3,nil,nil,nil,nil,8]

The array is filled up with ​nil​ up to the index you’re assigning to. In this case, the highest
index was 2, and we assigned index 7. That means index 3, 4, 5 and 6 are empty, and those
are set to nil.

Another neat trick you can do is to turn something like this:


gender = ​1
if​ gender == ​0
life_expectancy = ​79
elsif​ gender == ​1
life_expectancy = ​80
end
print life_expectancy ​#=> 80

...into this:
gender = ​1
life_expectancy = [​79​,​80​][gender] ​# gender is 1, so [79,80][1] #=> 1
print life_expectancy ​#=> 80

Methods on arrays
A data type can have a collection of methods. Just like we created methods in the main
scope in the tutorial on methods, you can also have methods on data types (such as Array).
These methods will be available to everything of that data type. Exactly how this works and
what this is will be covered in tutorials 6 and 7 (classes).

size​: Returns the size of the array. (synonym: ​length​; does the exact same.)
a = [​"one"​,​"two"​,​"three"​,​"four"​]
print a.size ​#=> 4
push(value)​: Adds ​value​ to the array.
a = [​"one"​,​"two"​,​"three"​]
print a ​#=> ["one","two","three"]

a.push(​"four"​)
print a ​#=> ["one","two","three","four"]

delete_at(idx)​: Deletes the element at ​idx​.


a = [​"one"​,​"two"​,​"three"​,​"four"​]
print a ​#=> ["one","two","three","four"]

a.delete_at(​1​)
print a ​#=> ["one","three","four"]

insert(idx, value)​: Inserts ​value​ at the ​idx​ index, but doesn’t overwrite anything. The other
elements just move one over.
a = [​"one"​,​"three"​,​"four"​]
print a ​#=> ["one","three","four"]

a.insert(​1​, "
​ two"​)
print a ​#=> ["one","two","three","four"]

delete(value)​: Deletes ​every​ occurance of the value.


a = [​"one"​,​"two"​,​"three"​,​"one"​,​"two"​,​"three"​,​"one"​,​"two"​,​"three"​]
print a ​#=>
["one","two","three","one","two","three","one","two","three"]

a.delete(​"two"​)
print a ​#=> ["one","three","one","three","one","three"]

include?(value)​: Returns true if the value exists in the array, false if not.
a = [​"one"​,​"two"​,​"three"​]
print a.​include​?(​"three"​) ​#=> true
print a.​include​?(​"four"​) ​#=> false
5: Ranges and for-loops
There’s yet another data type to introduce now, and that’s Range. A Range is, as the name
suggests, a range of numbers with a start and an end. If you want to make a range with the
number 1, 2, 3 and 4, you have two options:
r = ​1​..​4
print r ​#=> 1..4 (1, 2, 3, and 4)

r = ​1​...​5
print r ​#=> 1...5 (1, 2, 3, and 4)

You first type a number, ​1​ in this case (can also be negative). If you then write down ​two
dots, you’re ​including​ the number written down after the dots. If you write down ​three​ dots,
you’re ​excluding​ the number written down after the dots. So ​1​..​4​ starts at 1 and includes
up to 4. ​1​...​5​ starts at 1 and goes up to 5, but doesn’t include it.

You can also have negative ranges.


● -​7​..-​3​ (-7, -6, -5, -4, and -3)
● -​2​..​2​ (-2, -1, 0, 1, and 2)

You ​can’t​ have ranges go from big to small, though. You can’t use Floats either.
● 2​..-​2​ is empty. Just like an empty array, ​[]​, this is empty. Nothing.
● 1.1​..​1.3​ is also empty. It won’t work when you try to do anything with it (regardless
of using parentheses)

Naturally, ranges also work with variables.


a = ​1
b = ​4
print a..b ​#=> 1..4
print a...b ​#=> 1...4
print (a)...(b) ​#=> 1...4
print ((a)..(b)) ​#=> 1..4
The parentheses don’t make a difference, as long as they’re valid.
Iterating through ranges
If you want to take each number in a range and do something with it (printing it out, for
instance), you’d use ​for-loops​. These use the keyword ​for​ and iterate over every number in
a range. This ​for​ keyword also has to be “closed” with ​end​.
for​ i ​in​ ​1​..​3
print i
end

# Consecutively prints out:


# => 1
# => 2
# => 3
What this does is take every number in ​1​..​3​, which are 1, 2, and 3, and it does ​i = the
number​. It then runs the code ​inside​ the for-loop, which is ​print i​ in our case.

Important to note is that ​for-loops​ ​don’t introduce a new scope​, which means the value of i
will be overwritten in the loop.
i = ​9
print i ​#=> 9
for​ i ​in​ ​1​..​3
print i ​#=> 1, 2, 3
end
print i ​#=> 3

At the last iteration of the for-loop, it essentially does ​i = ​3​. This isn’t reversed, so i
becomes 3 outside of the loop, too. This is because it’s the same scope. Look at it like this:
# Outside the loop
i = ​9
print i ​#=> 9

# Inside the loop


i = ​1
print i ​#=> 1

i = ​2
print i ​#=> 2

i = ​3
print i ​#=> 3

# Outside the loop


print i ​#=> 3
Iterating through arrays with ranges
The array ​[​"one"​,​"two"​,​"three"​]​ has indexes ranging from 0 to 2. We’d write this as
0​..​2​ or ​0​...​3​. The size of the array is 3. If we wanted to setup a range that corresponds
with an array’s indexes, we’d start at 0, because that’s where array indexes start, and then
we have to use either ​..2​ or ​...3​. Since a​ rray.size​ would give us 3, it’s easiest to use ​...3​.
a = [​"one"​,​"two"​,​"three"​]
print a.size ​#=> 3
print ​0​...(a.size) ​#=> 0...3
print ​0​...a.size ​#=> 0...3
print ​0​..a.size - ​1​ ​#=> 0..2
print ​0​..(a.size - ​1​) ​#=> 0..2
Again, the parentheses don’t make any difference.

The range 0...3 contains 0, 1, and 2 - the same indexes as in the array. This means that if
we iterate through this 0...3 range and use them as indexes in the array, we get every single
element in the array.
a = [​"one"​,​"two"​,​"three"​]
print a.size ​#=> 3
r = ​0​...a.size
print r ​#=> 0...3

for​ i ​in​ r ​# Rather than 'for i in 0...3', 'for i in var' works, too.
print a[i] ​#=> "one", then "two", then "three"
end

We usually skip the extra variable for the range.


a = [​"one"​,​"two"​,​"three"​]
for​ i ​in​ ​0​...a.size
print a[i] ​#=> "one", then "two", then "three"
end

And that’s your ​typical​ array loop!


Iterating through enumerables
Another way to iterate over an array which may be more practical at times, is to iterate over it
directly rather than using indexes. Imagine this setup:
a = [​"one"​,​"two"​,​"three"​]
print a[​0​] ​#=> "one"

e = a[​0​]
print e ​#=> "one"

e = ​7
print e ​#=> 7
print a[​0​] ​#=> "one"

We assign a new variable called ​e​ and set it to the first element of the ​a​ array, which is
“one”​. Since that’s now a ​new variable,​ if we change its value, the value of the first element
in the array won’t change - only that new variable will.

If we take this concept and use it inside a for-loop, we get something a little bit like this:
array = [​"one"​,​"two"​,​"three"​]
for​ e ​in​ array ​# You can also do 'for e in ["one","two","three"]
print e ​#=> "one", then "two", then "three"
end

What we’re doing here is iterating through each element directly. First it does ​e = “one”​, then
e = “two”​, and then ​e = “three”​, rather than the indexes.

You can change the value of e, too. It just won’t affect the array being iterated through.
array = [​1​, ​2​, ​3​]
for​ e ​in​ array
e *= ​2​ ​# the same as e = e * 2
print e ​#=> 2, then 4, then 6
end
print array ​#=> [1,2,3]

However, if you were to do this with ​indexes,​ you are actually changing the array:
array = [​1​, ​2​, ​3​]
for​ i ​in​ ​0​...array.size
array[i] *= ​2​ ​# We're directly modifying the array here
print array[i] ​#=> 2, then 4, then 6
end
print array ​#=> [2, 4, 6]
Calling methods on ranges
There’s just one method on ranges that’d be useful to know for now, and that’s ​include?(n)​.
Just like with arrays, it will return true if ​n​ is included in the range, and false if not.

You have to be careful when calling a method on a ​range​ though. If you were to type
something like this:
print ​1​..​4​.​include​?(​2​)

...It’ll raise an error. Internally, it’s seen like so:


print (​1​)..(​4​.​include​?(​2​))

...And there isn’t a method called ​include?​ on Fixnums.


So to make sure you’re actually calling the method on the ​range​ and not on a ​Fixnum​, you
should surround the range with parentheses.
print (​1​..​4​).​include​?(​2​) ​#=> true
print (​1​..​4​).​include​?(​3.14​) ​#=> true

This is one of the only instances where parentheses with ranges really matter.
6: Classes and objects
Variables stores values of a certain type. The best way to refer to that is with ​object​. The
number ​10​ is an object. If you assign ​10​ to a variable (e.g. ​my_var = ​10​), the variable will
have the object of 10. The type of the object is the same as the value’s type (data type), we’ll
just be referring to it differently.

A variable stores an object for when you need to use it multiple times, such as this:
gender = ​0

But an object doesn’t ​have​ to be associated with a variable.


gender = ​0
life_expectancy = [​79​,​80​][gender]
There are three objects in this example:
● 0
● 79
● [79,80]

Out of these, ​0​ and ​79​ can be reused because they’re associated with a variable (​gender
and ​life_expectancy​). The array ​[79,80]​ is still an object and a newly created array, but once
the line ​life_expectancy = [​79​,8 ​ 0​][gender]​ is done, [79,80] is no longer accessible.
The object is disposed by the ​garbage collector​ (which is just something that deletes old
unreachable objects/code to lower RAM usage), because there’s no way to access ​[79,80]​,
as it isn’t a variable.

Classes and conditions


The data type of an object (such as Fixnum, String, Float, Array, Range) is also called the
class​ of the object. An object of class Fixnum (such as ​10​) is an ​instance​ of that class. The
class provides information as to what the class does and what it looks like, and 10 basically
fills in the missing details like the value. You can think of classes like the ​structure​ of the
object.

To programmatically determine the class of an object, you can use the ​.class​ method on the
object.
print ​"a"​.​class​ ​#=> String
print ​1​.​class​ ​#=> Fixnum
print ​3.14​.​class​ ​#=> Float
print [​1​,​2​,​3​].​class​ ​#=> Array
print (​1​..​3​).​class​ ​#=> Range
print ​true​.​class​ ​#=> TrueClass
print ​false​.​class​ ​#=> FalseClass
print ​nil​.​class​ ​#=> NilClass
a = ​"hello"
print a.​class​ ​#=> String
Technically, this means you could write code like this:
var = ​"17"
if​ var.​class​ == Fixnum
print ​"var is a number!"
elsif​ var.​class​ == String ​# This is true
print ​"var is a string!"
else
print ​"var is something else!"
end

Fixnum​, ​String​, ​Array​, ​Range​, ​FalseClass ​and such may refer to the class the object is an
instance of, but that too is actually an object; an object of type ​Class​.

However, there is a method for this on all objects which you should use ​instead:​
.is_a?(class)​. Just why you should use this and why there’s a difference will be covered in ​7:
Classes and polymorphism​.
var = [​17​]
if​ var.is_a?(Fixnum)
print ​"var is a number!"
elsif​ var.is_a?(String)
print ​"var is a string!"
elsif​ var.is_a?(Array) ​# This is true
print ​"var is an array!"
else
print ​"var is something else!"
end

Another quick example:


print [​1​].is_a?(Array) ​#=> true
print ​3.14​.is_a?(Float) ​#=> true
print ​3.14​.is_a?(Array) ​#=> false
Classes and instance methods
To reiterate one of the first things you learned:
var = ​10
var *= ​2
print var ​#=> 20

var *= 2​ is equal to ​var = var * 2​, which, in this case (because var is 10), would be ​10 * 2​.
This multiplication is actually an ​instance method​ on the ​Fixnum​ class. Just like we saw
with arrays (​array.size​, ​array.delete_at​, ​array.push​), it ​calls a method on the object​ ​10​; the
*​ method. Another completely valid (and standard) way to perform ​10 * 2​ would be ​10​.*(​2​)​.

This is essentially the method below, but then for the Fixnum class:
def​ ​*​(number2)
​return​ some_way_to_perform_the_calculation(number2)
end

This kind of notation (​10 * 2​) isn’t possible to replicate; it, again, is just a built-in, hard-coded
shortcut in Ruby (and basically every other Object Oriented Programming language). It’s just
a thing you have to accept.

As mentioned, ​10​ is an object of the Fixnum class. The ​Fixnum​ class provides a layout or
structure for what ​10​ should do, and also provides the ​methods​ for it. These are called
instance methods​.

If you try to multiply something that isn’t a number though (such as ​nil​), you’ll get an error
similar to this:
var = ​nil
var *= ​2​ ​#=> NoMethodError: Undefined method '*' for nil:NilClass

This would translate to ​nil​.*(​2)


​ ​, but the N
​ ilClass​ doesn’t provide an instance method
called ​*​, whereas Fixnum does. (​nil:NilClass​ stands for <object>:<class>; object is whatever
object it’s looking for the method in, and class is the class of that object). It’s just like calling
a method that doesn’t exist, but then for an object.
def​ ​methA
print ​"Called methA!"
end

methB ​#=> Undefined method 'metB' for main:Object


​ ain​ is something internal we’ll cover in a future tutorial - it just means it’s referring to the main scope, and not a
(m
class of some kind.)

Whenever we write ​<Class>#<Method>​ in documentation or elsewhere, we refer to the


instance method called <Method> of the <Class> class. ​10​.*(​2​)​ would be ​Fixnum#*​ with an
argument of 2. ​nil​.*(​2​)​ is ​NilClass#*​ with an argument of 2, too.
Different methods per class
While ​10 * 2​ performs a multiplication and ​nil * 2​ raises an error, strings and arrays do have
a different use for ​<object> * 2​. This heavily depends per class, but when you try to multiply
a string, it’ll just copy-paste the content of the string N times, where N is the amount you’re
multiplying with.
var = ​"a"
var *= ​3
print var ​#=> "aaa"

var *= ​3
print var ​#=> "aaaaaaaaa"

Unlike ​NilClass​, ​String​ ​does​ have a ​*​ method. It just does something different than
multiplication. ​Array​ has one too:
var = [​1​]
var *= ​3
print var ​#=> [1,1,1]

var = [​1​,​2​,​3​]
var *= ​3
print var ​#=> [1,2,3,1,2,3,1,2,3]

Object assignment vs variable assignment


An important thing to understand is that you can ​only​ assign to ​variables​, and ​not​ to objects.
​ Hello!"​ * ​2
var = "
print var ​#=> "Hello!Hello!"

This works perfectly fine; we’re performing “Hello!”.*(2) and setting var to the result of that,
and then print it out.

However, if we wanted to do something like this, we’d get an error.


"Hello!"​ = ​"Hello!"​ * ​2

"Hello!"​ = ​3.14

Both of these raise a SyntaxError when trying to start the script, stating “​unexpected ‘=’​”.
This is because ​“Hello!”​ is an object, and you ​cannot​ assign to an object. Objects are
temporary, unless stored somewhere (such as a variable or array). And only variables/arrays
can be assigned to with ​=​.
Improper arguments
While ​10 * 2​ and ​2 * 10​ might do the same, that’s not the case when you try to multiply
different data types.
Fixnum#*​ expects a Fixnum or a Float.
String#*​ expects a Fixnum or a Float.
Array#*​ expects a Fixnum or a Float.

Now take ​“a” * 3​ as an example. This calls ​String#*​ with a Fixnum, so this works fine. But if
you reversed it, ​3 * “a”​, we’re calling ​Fixnum#*​ with a String. This raises an error, because
Fixnum#* only accepts Fixnums and Floats - not Strings. The same applies to Arrays.

This ​isn’t​ something internal, but rather a ​design choice​. You can write your own code in a
way that supports both ways, or you can write something that only supports one way - it’s up
to you to figure out what you want.

Two examples of normal methods below, illustrating different ways you can write it.
def​ ​mult​(a, b)
​if​ a.is_a?(Fixnum) || a.is_a?(Float)
​if​ b.is_a?(Fixnum) || b.is_a?(Float)
​return​ a * b
​else
​return​ ​"You can't multiply #{a.​class​} with #{b.​class​}!"
​end
​elsif​ a.is_a?(String) || a.is_a?(Array)
​if​ b.is_a?(Fixnum) || b.is_a?(Float)
​return​ a * b
​else
​return​ ​"You can't multiply #{a.​class​} with #{b.​class​}!"
​end
​else
​return​ ​"You can't multiply #{a.​class​} with #{b.​class​}!"
​end
end

print mult(​"hi"​,​3​) ​#=> "hihihi"


print mult(​3​,​3​) ​#=> 9
print mult(​3​,​"hi"​) ​#=> "You can't multiply Fixnum with String!"
print mult(​3​,​1​..​3​) ​#=> "You can't multiply Fixnum with Range!"
def​ ​mult​(a, b)
​if​ a.is_a?(Fixnum) || a.is_a?(Float)
​if​ b.is_a?(Fixnum) || b.is_a?(Float)
​return​ a * b
​elsif​ b.is_a?(String) || b.is_a?(Array)
​return​ b * a
​else
​return​ ​"You can't multiply #{a.​class​} with #{b.​class​}!"
​end
​elsif​ a.is_a?(String) || a.is_a?(Array)
​if​ b.is_a?(Fixnum) || b.is_a?(Float)
​return​ a * b
​else
​return​ ​"You can't multiply #{a.​class​} with #{b.​class​}!"
​end
​else
​return​ ​"You can't multiply #{a.​class​} with #{b.​class​}!"
​end
end

print mult(​"hi"​,​3​) ​#=> "hihihi"


print mult(​3​,​3​) ​#=> 9
print mult(​3​,​"hi"​) ​#=> "hihihi"
print mult(​3​,​1​..​3​) ​#=> "You can't multiply Fixnum with Range!"

As you can see, it really is just a ​choice​ you make here. Ruby chose not to support
3​ * ​"hi"​, but it’s really up to you what you support and what you don’t.
7: More classes and polymorphism
Classes aren’t just stand-alone classes, but they’re actually kind of like a family tree in that
they have ancestors. A String, for example, has the Object class as ancestor (yes, Object is
also a class). To illustrate which ancestors a class has, we’ll write it down like so:
Kernel -> Object -> Comparable -> String

This is the ancestor line for the String class. The first class that comes before ​String​,
Comparable​, is the ​superclass​ of the String class.

The ancestor line of the ​Fixnum​ class is this:


Kernel -> Object -> Comparable -> Numeric -> Precision -> Integer ->
Fixnum

Now let’s say we’re doing ​12​.odd? ​#=> false​. This method returns whether or not the
number is odd (​13.odd?​ would be true).
When you call a method on an object (an instance method), it will look at the highest class
there is, which is ​Fixnum​ in this case. If that Fixnum class contains an instance method
called ​odd?​, it will call that method.
If that Fixnum class does ​not​ contain an instance method called odd?, it will look at the class
below it, which is Integer. If that Integer class contains an instance method called odd?, it’ll
call that. If not, it’ll check ​Precision​, ​Numeric​, ​Comparable​, ​Object​, and then ​Kernel​. If it still
hasn’t found it after Kernel, it will raise a NoMethodError. This is the concept of
inheritance​/​polymorphism​; a class will ​inherit​ all methods from its ancestors.

Comparable has an instance method called ​.is_a?​, which Strings, Arrays, Floats, Integers
and what not all use. But Kernel also has an instance method called .is_a?, which is used by
classes that ​don’t​ have Comparable (such as NilClass). NilClass’s ancestors are:
Kernel -> Object -> NilClass

So when you call ​14​.is_a?(Fixnum)​, you’re calling it on Comparable.


When you call ​nil​.is_a?(Fixnum)​, you’re calling it on Kernel.

Speaking of ​.is_a?​, if you give it a class that is a part of the class’s inheritance tree, it’ll also
return true.
14​.is_a?(Numeric) ​#=> true
14​.is_a?(Object) ​#=> true
14​.is_a?(String) ​#=> false

Every single object in Ruby inherits both Kernel and Object, which means
var.is_a?(Object)​ is always going to return true no matter what (as long as var exists).
The same applies to ​var.is_a?(Kernel)​.
Applying knowledge on inheritance trees
This might sound useless, but it can actually come in handy.
Where normally, to determine if a number is a Float or a Fixnum, we’d use
var.is_a?(Float) || var.is_a?(Fixnum)​, their inheritance trees reveal this:
Fixnum:
Kernel -> Object -> Comparable -> Numeric -> Integer -> Fixnum
Float:
Kernel -> Object -> Comparable -> Numeric -> Float

What we can gather from this, is that ​var.is_a?(Numeric)​ will be true for both Floats ​and
Fixnums. You just have to know if any other classes use Numeric, but that isn’t the case, so
it’s safe to use. If we did Comparable, for instance, we’d also get String in there, which we
don’t want.

Writing your own classes


You’re not limited to just the existing, internal classes like ​String​, ​Array ​and the like. You can
also create your own. A class has a name, and this name is a constant - this means that it
starts with an uppercase letter and cannot contain any special characters or operators like
!@#$%^&*()​ (and a lot more, probably).
To ​create​ or ​define​ a class, you use the ​class​ keyword, followed by the name. Just like ​for​,
def​, and ​if​, ​class​ has to be closed with ​end​. In the example below we’ll create a class
named CustomClass with nothing in it:
class​ ​CustomClass
end

Now, if you type ​CustomClass​ in your code, you’re referring to this newly created class. If
you do this ​before​ the class is actually defined, it won’t exist yet, and using it will cause
errors. But now you can also use this in methods like ​something.is_a?(CustomClass)​, or
something.​class​ == CustomClass​.

We now have a class, but we don’t have any instances of it yet. To initialize an instance of
CustomClass​, you write ​CustomClass.new​. This returns an object, which is an instance of
CustomClass​. You should store this in a variable somewhere so you can use it again later.
When you do ​[​1​,​2​,​3​].size ​#=> 3​, you’re calling the ​instance method​ called ​size​ on an
instance​ of an array (hence it’s called ​instance​ method, duh). You can very easily add
similar instance methods to your class by simply defining a method like you normally would.
class​ ​CustomClass
​def​ ​say_hello
print ​"Hello!"
​end
end

To test this out, we create an instance of the class with ​CustomClass.new​, and then call
.​say_hell​o on it:
var = CustomClass.new
var.say_hello

This might be a bit confusing when you’re used to the special ​1..6​, ​“hello”​, ​[1,2,3]​ formats
and the like to create an instance of a class. These are actually just very special ways to
create an instance of those classes - normally, for any class, you would use
<ClassName>.new​. (And no, there’s no way to make one of these kind of formats yourself,
unfortunately. You’re stuck with ​.new​).

Class methods
All methods we’ve seen so far were ​instance​ methods, but there’s also a thing called ​class
methods​. As the name suggests, these are methods on the ​class​ rather than the ​instance.​
Where an instance is a creation or version of the class, methods on the class are only
accessible on that class and will always be the same. They’re not bound to any instances.
An instance would be ​10​, ​“hello”​, ​[1,2]​, ​1..5​, ​false​, or else; these are versions or ​instances​ of
the classes, respectively ​Fixnum​, S ​ tring​, ​Array​, ​Range​, and ​FalseClass​, of which you can
have as many as you want. As there are no simple examples of class methods by default,
we’ll create our own first. You do everything the way you normally would when creating an
instance method in a class, but instead of ​def <MethodName>​, you write
def self.<MethodName>​ (or ​def <ClassName>.<MethodName>​, even).
class​ ​CustomClass
​def​ ​self​.​say_hello
print ​"Hello!"
​end
end

Another way to call class methods (which works ​only​ with class methods), is ::say_hello
rather than .say_hello on the class.
We would use this on CustomClass itself, and ​not ​on its instances:
CustomClass.say_hello ​#=> "Hello!"
var = CustomClass.new
var.say_hello ​#=> Undefined method 'say_hello' for #<CustomClass>
#<CustomClass> means it’s an i​ nstance​ of said class (it usually has a 0x0000 (hex) number after it - ignore this)

The constructor
Looking at this, you should think “But isn’t ​CustomClass.new​ also a class method?”, and
you’d be right. This might not seem very interesting, but ​Class::new​ (just like ​Class#method
refers to an instance method, ​Class::method​ refers to a class method in speech) is a very
special kind of class method. It creates an instance of itself and allocates the memory
needed, and then immediately after creation, it calls an instance method called ​initialize
if it exists - this is called the ​constructor​ of the class, which just means it’s called upon
creation. We can implement this and add code to it, if we want:
class​ C​ ustomClass
​def​ i​ nitialize
p " ​ This object was just created!"
​end

​def​ ​say_hello
print ​"Hello!"
​end
end

var = CustomClass.new ​# Before var is assigned, it prints "This object


was just created!"
var.say_hello ​# Prints "Hello!"

We can even pass arguments via ​Class::new​ (the constructor). You just need to make sure
Class#initialize​ matches those arguments.
class​ ​CustomClass
​def​ ​initialize​(string)
print string
​end
end

var = CustomClass.new(​"Hello!"​) ​# Prints out "Hello!"

So in short: ​Class::new​ calls ​Class#initialize​, and their arguments are matched up.
Class::new​ is internal and shouldn’t be changed, but ​Class#initialize​ can be overriden and is
called upon creation of the object. If you typo ​def initialize​, it won’t be called. This can cause
you a severe headache if you don’t notice it if it doesn’t cause any errors, so make sure you
spell it right.
There’s another catch with the constructor though - its return value is disregarded. No matter
what​ you return in def initialize, it’s going to return the newly created object. You can’t
change that.
class​ ​CustomClass
​def​ ​initialize
print ​"Hello!"
​return​ ​7
print ​"Goodbye!"
​end
end

var = CustomClass.new
# Prints out "Hello!"
var.​class​ ​#=> CustomClass

Normally, you’d expect var to be 7 and ​var.class​ to be ​Fixnum​ because the return value of
def initialize ​is 7, but this return value is disregarded. It does still cancel the rest of the
method, though, so​ “Goodbye!”​ is never printed.

Adding and overwriting methods


You can open and close a class whenever and wherever you want without any issues. If we
wanted, we could define a method like this:
class​ ​CustomClass
end

class​ ​CustomClass
​def​ ​say_hello
print ​"Hello!"
​end
end

class​ ​CustomClass
end

var = CustomClass.new
var.say_hello ​#=> "Hello!"

It opens and closes the class, opens it again, adds a method, closes, and opens and closes
again. It doesn’t actually modify anything. The only time opening and closing directly may be
useful is if the class isn’t actually defined yet in the first place.
If you add a method that has the same name as a method that already exists, it will be
overwritten​. All calls to that will then call the new method.
class​ ​CustomClass
​def​ ​say_hello
print ​"Hello!"
​end
end

CustomClass.new.say_hello ​#=> "Hello!"

class​ ​CustomClass
​def​ ​say_hello
print ​"Hi!"
​end
end

CustomClass.new.say_hello ​#=> "Hi!"

You can also add methods to already existing classes, or overwrite methods that already
exist (whether or not that’s a good idea is a different story, but it’s possible).
class​ ​String
​def​ ​greet
print ​"Hello!"
​end
end

"test"​.greet ​#=> "Hello!"

Inheriting classes
By ​default,​ a new class ​inherits​ Object (which in turn inherits Kernel), which means the
inheritance tree for any new class is:
Kernel -> Object -> YourNewClass

To change which class a new class inherits from, you write ​< ClassName​ after the new class
name when you define a class:
class​ ​One
end

class​ ​Two​ < One


end
The Two class now inherits from the One class, so the inheritance trees are as follows:
Kernel -> Object -> One
and
Kernel -> Object -> One -> Two

So if we defined a method in class One, we could call it in class Two:


class​ ​One
​def​ ​say_hello
print ​"Hello!"
​end
end

class​ ​Two​ < One


end

Two.new.say_hello ​#=> "Hello!"

To overwrite this method, you just add the method to class Two and make it do something
else.

To open a class with inheritance back up later, you can either include the inheritance part of
leave it out - that doesn’t matter.
class​ ​One
end

class​ ​Two​ < One


end

class​ ​Two
end

class​ ​Two​ < One


end
However, if a class is going to inherit from a different class, whenever that class is ​defined
(the first time it’s opened), it ​has​ to inherit from that class. You can’t not-inherit and then
inherit later anyway.
class​ ​One
end

class​ ​Two
end

class​ ​Two​ < One ​#=> Error: Superclass mismatch for class Two
end

The ‘super’ keyword


When you use ​super​ in a method, it will look at the superclasses of the current class for a
method with the same name as the one it’s called it, and call that:
class​ O​ ne
​def​ h​ i
p " ​ Hi!"
​end
end

class​ T​ wo​ < One


​def​ h​ i
p " ​ Hello!"
​end
end

Two.new.hi ​#=> "Hello!"

class​ ​Two
​def​ ​hi
p ​"Hello!"
​super
​end
end

Two.new.hi ​# Prints "Hello!", then calls One#hi which prints out "Hi!"
If none of the superclasses have a method by that name, it will raise an error.
class​ ​One
end

class​ ​Two​ < One


​def​ ​hi
p ​"Hello!"
​super
​end
end

Two.new.hi ​# Prints "Hello!", then raises an "Undefined superclass


method 'hi" error.

You can also give arguments to ​super​.


class​ ​One
​def​ ​self​.​greet​(string)
print string
​end
end

class​ ​Two​ < One


​def​ ​self​.​greet
print ​"Hello!"
​super​(​"Hi!"​)
​end
end

Two.greet ​#=> "Hello!", then "Hi!"


8: More variables and scopes
So far, we’ve been using normal variables such as the one you see in the example below.
age = ​17
if​ age >= ​18
print ​"You are 18 or older"
else
print ​"You are 17 or younger"
end

Here, ​age​ is what’s called a ​local variable​. Any variable that doesn’t start with an uppercase
letter or a character like ​@​, @
​ @​ or $
​ ​, is always a local variable.

In a script, you have multiple ​scopes​. A scope is essentially a range of code with its own
collection of methods, local variables, accessibility, et cetera. As we touched upon with
methods, a new method has a different scope than the “main” scope. This means that local
variables in the main scope aren’t the same as local variables in the method’s scope, even if
they have the exact same name.

Constants
While you can’t access local variables from different scopes, ​constants​ can help work
around that. You can have constants in the main scope, or a class’ scope. What
differentiates local variables from constants, is the uppercase letter all constants start with.
Variable​ would be a constant, whereas ​variable​ would be a local variable.

These constants are accessible from anywhere at any time, as seen in the example here.
Age = ​17

def​ ​print_age
print Age
end

print_age ​#=> 17

This works because the constants can be accessed from different scopes too. The ​Age
constant is defined in the main scope, and is later accessed in the ​print_age​ method’s
scope. If you were to try this with a local variable, it would raise an error, stating the variable
doesn’t exist.

There’s one more difference aside from visibility/accessibility, which comes in when you try
to change the value of the constant during run-time. A constant is initialized ​once​, and isn’t
meant to be changed after that.In most languages, this isn’t possible in the first place. In
Ruby, however, the value does in fact change, but you’ll get a warning that the constant has
already been defined before.
Constants and classes
Classes can also have constants within them. When you do, they’re treated kind of similar to
class methods and the like. Constants aren’t a part of an instance of the class, but they exist
on the class itself. To then reference the constant inside that class, you’ll first have to type
the name of the class, followed by ​::​, followed by the constant name.
​ ewClass
class​ N
Age = ​17
end
print NewClass::Age ​#=> 17

The ​Age​ constant has been created not in the main scope this time, but in ​NewClass​’s
scope. This means that you can’t access it directly (which would be ​print Age​), but that you
have to go into the class’ scope first (​NewClass::​).

You can also have a ​class in a class ​in a class, et cetera et cetera. When you then want to
refer to a so-called subclass, you ​also​ type ​MainClass::SubClass​ to reference it. (A
subclass is completely independent of the class it’s in; it just has a different scope, which
means you always need to type ​MainClass::SubClass​ to use it (unless you’re in ​MainClass
itself, in which case just ​SubClass​ suffices, because you’re already in the ​MainClass​’s
scope)).
class​ ​One
​class​ ​Two
Three = ​3
​end
end

print One::Two::Three ​#=> 3


(You can’t have a class and a constant with the same name in the same scope.)
Instance variables
As the name suggests, ​instance variables​ are variables bound to class instances and its
scope. Whereas local variables exist in one scope only, instance variables are carried by the
instance/object of the class. The difference between local variables and instance variables is
that the latter always start with ​1​ ‘​@​’. Whether or not the letter after that is uppercase or
lowercase doesn’t matter.
class​ ​NewClass
​def​ ​initialize
@age = ​10
​end

​def​ ​print_age
print @age
​end
end

var = NewClass.new
# var now carries around the @age variable internally.
var.print_age ​#=> 10

If you were to try this with local variables (without the ​@​), it would perform ​age = ​10​ in ​def
initialize​, but then that method ends and the variable is gone again. Then calling ​print_age
would try to access a non-existent variable, because there’s no local variable defined in
print_age​ called ​age​.

This is solved by using instance variables. These variables exist as long as the object they’re
in exists. We can’t directly access this variable from outside of the class though, nor on the
object itself (var). You have to be ​in​ the class to manipulate instance variables. For this you
need to create ​helper​ methods, methods that allow you to read and write to the variable
from the outside. This is pretty basic:
class​ ​NewClass
​def​ ​initialize
@age = ​10
​end

​def​ ​get_age
​return​ @age
​end

​def​ ​set_age​(new_value)
@age = new_value
​end
end
var = NewClass.new
print var.get_age ​#=> 10
var.set_age(​17​)
print var.get_age ​#=> 17
var.set_age(​10​)
print var.get_age ​#=> 10

But that’s convoluted and annoying to do for ​every single​ instance variable (if it’s a big class,
you can easily have dozens of instance variables). That’s why we can create ​getter​ and
setter​ methods. Getter methods are basically just methods with the same name as the
instance variable, which return the value of said instance variable (exactly the same as
get_age but with a different name). Setters are a bit different though. Rather than
set_age(10), setter methods allow you to use assignment with = to call the method (yes,
assignment is also a method!)
First to demonstrate the getter method:
class​ ​NewClass
​def​ ​initialize
@age = ​10
​end

​# Getter
​def​ ​age
​return​ @age
​end
end

p NewClass.new.age ​#=> 10

Next comes the setter method:


class​ ​NewClass
​def​ ​initialize
@age = ​10
​end

​# Getter
​def​ ​age
​return​ @age
​end

​# Setter
​def​ ​age=​(new_value)
@age = new_value
​end
end
var = NewClass.new
p var.age ​#=> 10
var.age = ​17
p var.age ​#=> 17
var.age=(​10​) ​# Identical to normal assignment
p var.age ​#=> 10
# You can even perform mathematical functions with it:
var.age **= ​2
p var.age ​#=> 100

So when you create a method with an equal sign ​=​ at the end of the method name, that
method will be treated as an assignment method, with ​=​.

There’s really no need for getter and setter methods, unless you want the instance variable
to BE public and to be used outside of the class itself.

These getter and setter methods can be very useful, but it’s a lot to write if you have a lot of
instance variables. That’s why there are shortcuts for these getters and setters:
● attr_reader​: This is the getter method. Equal to ​def​ ​age​ in the previous examples.
● attr_writer​: This is the setter method. Equal to ​def​ ​age=​(v)​ in the previous
examples.
● attr_accessor​: This is both the getter ​and​ the setter method. Equal to ​def​ a​ ge​ and
def​ ​age=​(v)​ in the previous examples.

attr also means Attribute. Attribute reader (getter), Attribute writer (setter), Attribute accessor
(getter + setter).

To use these shortcuts, you choose the keyword followed by a colon ​:​, followed by the name
of the instance variable. When used, it will basically turn into the proper method(s) in the
order you wrote them. This means that you can overwrite the functionality later, if you want,
just by typing out the method and implementing your desired functionality (just like a normal
method).
class​ ​NewClass
​# They're usually at the top, along with constants
​attr_accessor​ ​:age

​def​ ​initialize
@age = ​10
​end
end

var = NewClass.new
print var.age ​#=> 10
var.age = ​17
print var.age ​#=> 17
Class variables
Just like you have instance variables that are linked to instances of the class, class variables
exist only on the class itself, and are thus always the same no matter how you access it
(different instances of the same class can have different values for their instance variables,
this isn’t the case with class variables). All variables that start with ​@@​ are class variables.
class​ ​NewClass
@@my_cvar = ​0
end

We have no way to access this variable outside of the class currently, so we have to create
some more getter and setter methods. Since we’re dealing with class variables that are
shared across all instances of the class, we should make them class methods:
class​ ​NewClass
@@my_cvar = ​0

​def​ ​self​.​my_cvar
​return​ @@my_cvar
​end

​def​ ​self​.​my_cvar=​(v)
@@my_cvar = v
​end
end

​ => 0
print NewClass.my_cvar #
NewClass.my_cvar = ​1
print NewClass.my_cvar #​ => 1

You might not realize why this is a class variable and not an instance variable, but class
variables aren’t different per object - each object has the same value of that class variable
(​technically, they don’t even carry around the variable to begin with; the class itself does
that. Instances just look it up in the class​), so changing its value means it would change the
value everywhere, which can lead to unexpected or unwanted results, such as shown below:
class​ ​NewClass
@@my_cvar = ​0

​def​ ​my_cvar
​return​ @@my_cvar
​end

​def​ ​my_cvar=​(n)
@@my_cvar = n
​end
end
obj1 = NewClass.new
obj2 = NewClass.new

p obj1.my_cvar ​#=> 0
p obj2.my_cvar #​ => 0

obj1.my_cvar = ​1

p obj1.my_cvar ​#=> 1
p obj2.my_cvar #​ => 1

As useless as class variables might seem (and frankly, you’ll hardly ever use them), they do
actually have their uses. What if we wanted to count how many instances we’ve ever created
of a class, for instance? We’d have a class variable (independent of any instances) be
incremented by 1 every time the constructor is called.
class​ ​NewClass
@@instance_count = ​0

​def​ ​initialize
@@instance_count += ​1
​end

​def​ ​self​.​instance_count
​return​ @@instance_count
​end
end

p NewClass.instance_count ​#=> 0

NewClass.new

p NewClass.instance_count ​#=> 1

NewClass.new
NewClass.new
NewClass.new

p NewClass.instance_count ​#=> 4
Global variables
Another type of variables are ​global variables​. As the name implies, these are ​global​ can
be accessed from everywhere, at any time, in any scope. Unlike constants that are tied to a
specific scope, global variables are everywhere. Where instance variables use ​@​ to
differentiate them from other variable types, global variables are preceded by a ​$​ sign.
$my_gvar = ​0

print $my_gvar ​#=> 0

$my_gvar = ​1

print $my_gvar ​#=> 1

def​ ​change_that_var​(n)
$my_gvar = n
end

change_that_var(​2​)

print $my_gvar ​#=> 2

class​ ​NewClass
$my_gvar = ​3
end

print $my_gvar ​#=> 3

Global variables are generally more memory-heavy though, so you shouldn’t go overboard
with them.
9: Hashes
Introducing yet another data type: Hash. Hashes are very much like arrays in that it’s a list of
objects with a specific location in the array/hash. The key difference ​(pun intended)​ is that
objects inside arrays are always at a specific ​index​ which is a whole number between 0 and,
well, infinity, whereas the “indexes” of objects inside a hash can be pretty much any object: a
symbol, a string, a number, an array, another hash, or even a class.
Hashes are denoted by typing a bunch of ​key => value​ structures, separated by a comma,
within two curly brackets. Here, key stands for an object of some kind, and value stands for
an object of some kind. These don’t have to be the same.
hash = {
​"start"​ => ​"hello world!"
}
print hash ​#=> {"start"=>"hello world"}
print hash[​"start"​] ​#=> "hello world"
Like most other things in Ruby, spaces and newlines are almost always disregarded. It doesn’t matter how many
or how little whitespace you have.

In this example we see a hash with one key-value pair. The key is ​“start”​, and the value that
belongs to that key (in arrays, index) is ​“hello world!”​.
Those keys aren’t limited to just strings (the logical choice, because they’re more descriptive
than, say, ​482​ as a key), but you ​can​ also use other data types:
hash = {
​"one"​ => ​1​,
​2​ => ​"two"​,
​:three​ => [​"three"​,​3​]
}

print hash ​#=> {"one"=>1,2=>"two",:three=>["three",3]}


print hash[​"one"​] ​#=> 1
print hash[​2​] ​#=> "two"
print hash[​:three​] ​#=> ["three",3]
print hash[​:three​][​1​] ​#=> 3

Now that we know this, we could technically also make a fake “array” with hashes as long as
we use numbers for the keys.
hash = {
​0​ => ​"zero"​,
​1​ => ​"one"​,
​2​ => ​"two"
}
print hash[​0​] ​#=> "zero"
print hash[​1​] ​#=> "one"
print hash[​2​] ​#=> "two"
array = [
​"one"​,
​"two"​,
​"three"
]
print array[​0​] #
​ => "zero"
print array[​1​] #​ => "one"
print array[​2​] # ​ => "two"

From this point it’s basically like a regular ol’ array. We can use the keys to access the
objects in the hash and change their values:
hash = {
​"three"​ => ​3​,
}

print hash[​"three"​] ​#=> 3


hash[​"three"​] = ​9278343
print hash[​"three"​] ​#=> 9278343

...Or we can add to it (if a key doesn’t exist in a hash, it returns ​nil​):
hash = {}

print hash[​"a key"​] ​#=> nil


hash[​"a key"​] = ​"a value"
print hash[​"a key"​] ​#=> "a value"

Iterating through hashes


Unlike arrays, hashes don’t have neatly organised indexes you can just iterate over. Instead,
you have to loop through the ​keys​ of the hash and use those instead.
hash = {
​"james"​ => ​10​,
​"carl"​ => ​13​,
​"trevor"​ => ​21
}
print hash.keys ​#=> ["james", "carl", "trevor"]

So as you can see, Hash#keys nicely organizes the keys present in a hash. Important to
note though is that ​the order of Hash#keys is not constant​. As you add/remove/change
the hash, this order may change, meaning that you can’t rely on “the first array element is
“james”” and the like. It might return ​[​"trevor"​, ​"james"​, ​"carl"​]​ later on in your code,
for instance.
However, you can still iterate through this array as long as you make use of the keys
themselves.
hash = {
​"a"​ => ​1​,
​"b"​ => ​2​,
​"c"​ => ​3
}
for​ key ​in​ hash.keys
print key
end

This will print ​“a”​, ​“b”​, and ​“c”​ in an unknown order. You can then use ​hash[key]​ to retrieve
the value of whichever key you’re currently iterating over. Another possibility is to store the
keys somewhere before iterating, and then use a for-loop with a range.

hash = {
​"a"​ => 1 ​ ​,
​"b"​ => 2 ​ ​,
​"c"​ => 3 ​
}

keys = hash.keys
for​ i ​in​ ​0​...keys.size
print keys[i] ​#=> "a", "b", or "c"
print hash[keys[i]] ​#=> 1, 2, or 3
end
A bunch of useful methods for hashes
For all code in the list below, assume the following applies:
hash = {
​"one"​ => ​1​,
​"two"​ => ​2​,
​"three"​ => ​3
}

Checking keys
print hash.has_key?(​"three"​) ​#=> true
print hash.has_key?(​"four"​) ​#=> false
print hash.key(​2​) ​#=> "two"
print hash.key(​3​) ​#=> "three"
print hash.key(​4​) ​#=> nil

Deletion
print hash[​"one"​] ​#=> 1
hash.delete(​"one"​) ​# Give it the key you want to delete
print hash[​"one"​] ​#=> nil

Checking values
print hash.has_value?(​3​) ​#=> true
print hash.has_value?(​4​) ​#=> false
hash[​"something"​] = ​4
print hash.has_value?(​4​) ​#=> true
print hash.values ​#=> [1, 2, 3, 4]
The reason there’s no Hash#value (despite there being a Hash#key), is because you can easily fetch a value
based on key with hash[key] already. This isn’t possible with fetching a key based on a value, which is why
Hash#key exists.
10: Exception handling
To be added soon.

Vous aimerez peut-être aussi