Ruby For Beginners
Ruby Monday Study Group curriculum for beginners
This book has been written after we have run 4 beginners groups at our Ruby Monstas groups in Berlin, and it outlines the current state of our beginner groups curriculum.
After completing this curriculum you’ll be able to read, understand, and write basic Ruby code yourself:
You can use this knowledge to create small tools that might help you in your day-to-day work (such as converting numbers, or extracting data from files) that normally would cost a lot of manual work. You’ll be able to jump into other tutorials, and have a much easier time understanding what they’re talking about. And you can start working on our next curriculum which will walk you through the basics of building an actual web application.
If you’d like to print this book, or export it as a PDF try using this page, which is a single-page version of the entire book.
You can find the source code of this book here.
Preface
Read this book at your own pace, and do exercises at your own pace.
We recommend reading at least a page a day (ideally more), and taking some more time, at least once a week, in addition to the weekly meeting on Mondays. It often helps to meet with others, have some coffee and cake, and hang out while reading some more of this book, or doing a few exercises together.
We suggest that you read the book from beginning to end, and do exercises as you go. If you come across chapters that you feel are too theoretical you can skip them for the time being and come back to them later, if needed.
If you’d prefer to jump right in, you can also skip over the introductory
chapters. Jump to the chapter “Your tools”, and read the introductory
chapters later. If you already feel familiar with your editor and terminal, and
know how to use ruby
to execute a Ruby file, then you can skip over the
chapter “Your tools”, too.
Take notes about whatever questions come up during the week, things that you don’t understand, and everything else worth mentioning (for example, epiphanies you have, or things you found interesting). Bring your notes to the study group so we can talk about them in the group.
Please help us improve this book for others: Whenever you find something unclear or missing then please tell us. You can do this during the study group, email the mailing list, or ideally file an issue here. (This also helps you get familiar with GitHub, which we will use a lot later on.)
By using the name “Ada” in examples in this book we give credit to Ada Lovelace, the world’s first computer programmer :)
Resources
We also recommend looking at, reading, and working through other resources, as much as you can. Every beginners book expresses things a little bit differently, in a different order, and from a different angle.
Overview
- Codecademy Ruby Glossary
- Rails Beginner Cheat Sheet by Tobias Pfeiffer
- Ruby Docs Cheat Sheet
- Ruby API documentation
Recommended reading
- Ruby in 100 minutes by JumpstartLab
- A Beginner’s Guide To Ruby by Marc Gayle
- Learn to Program by Chris Pine
- Ruby for Webdesigners by Patrick J. Sparrow
- The Poignant Guide by Why
- Mr. Neighborly’s Humble Little Ruby Book by Jeremy McAnally
- Beginning Ruby by Peter Cooper
- Programming Ruby, “The pickaxe” by Dave Thomas
- Eloquent Ruby by Russ Olsen
Online courses
Puzzles
- Ruby Warrior by Ryan Bates and Bloc.io
Role model stories
Resources for coaches
Programming is creation
Things coming to life
Programming is creation. Whenever you run a program a little universe is created. Things come to life and interact with each other, according to the rules that you, as their creator, define.
Imagine you build an application like Twitter. Then, as a developer, in the programming language of your choice, you would say things like “Let there be users! And let there be tweets!”, and once your application starts these things will come into existence. Next you would go ahead and define “Users can create tweets, and they can follow each other”. And from now on, every time new users are created in the little universe that is your application, these users will have the ability to tweet and follow each other.
Learning to program
Brainwashing yourself
Learning to program means, fundamentally, two things: Learning a new language, and learning to solve problems by way of using this language. This means writing code and describing your solution to the problem.
When this text, the one that you are reading right now, was written, the author used the language English trying to come up with an adequate solution to the problem “How can someone best start learning to program?” Reading this text you also use the language English. If you think about what you are doing right now, you will notice that you do not think about English as a language at all. Right? Instead you use the language in order to figure out what this author might be talking about, and what that might mean to you.
Try to remember when you last walked up the stairs. Walking up the stairs is a pretty complex series of movements, and it took you quite a while to properly learn it as a child. The exact order and coordination of movements, bending your knees, ankles, while keeping your overall balance, is so complex that we probably wouldn’t even be able to describe it properly. However, we have somehow managed to learn this ability and we can now use it.
The point I am trying to make here is: When you last walked up the stairs you were thinking about all sorts of things. Maybe you were on the phone with someone, searching for your keys in your pockets, and thinking about looking for a nice introduction to programming online … maybe you did all of this at the same time. Whatever it was, you did not think about how exactly to bend your knees and ankles while trying to maintain your balance and get up to the next step.
Programming is very much the same. As programmers, while we program, we do not think about the programming language, and how to use it. Instead we simply use it, and meanwhile think about very different things, such as the problem we are trying to solve, how we can make things easier for fellow programmers, how to best suit the needs of our users, or customers, and so on.
When you start learning to program you will first learn about the basic concepts of the programming language. And you sometimes may feel slightly overwhelmed, and wonder how programmers might be able to remember all this stuff. The trick is: they don’t, consciously. Instead they have assimilated these concepts so much that they are able to simply use them, without consciously remembering or thinking about them. Just like you use English, or your ability to walk up the stairs.
That’s why learning a programming language, just like learning any other language, or skill, is a lot about repetition: You basically brainwash yourself into being able to form meaningful “sentences” (code) without having to think about the concepts that you are using.
Over time, while you repeat basic concepts of Ruby over and over again (by way of doing exercises and writing code) you will notice that things become very natural, and this probably will happen much quicker than you think.
At first, you will not understand anything. It’ll be weird, just like with learning any human language. You will struggle with words, and not know what symbols are what, and it’ll all be very confusing. Then one day BANG your brain will snap and you will suddenly “get it.” If you keep doing the exercises and keep trying to understand them, you will get it. from the great book Learn Ruby The Hard Way by Zed Shaw.
Learning modes
People are different
Different people learn things in different ways.
Some like to listen to lectures, or read books that thoroughly explain concepts before they then start exercising, and experiment with what they just learned. Others like to get their hands dirty, and play with code until it does what they want.
Some want to fully understand what a certain line of code does, and why. Others don’t care that much about the details, and want their code to produce the right things instead. They might memorize what worked, and understand things more fully later.
Some understand concepts in logical ways, and go precisely by their definitions. Others understand things better by coming up with good analogies and metaphors. And yet others tend to simply memorize things and how they get used.
Therefore there is no one true path or one-size-fits-all approach to learning programming. Try different things, and pay attention to what works well for you, what is the most fun to you, and keeps you motivated.
If you find it hard to keep motivated working through a certain online tutorial, or if you have a hard time fully understanding what an exercise is about, then try to pick a different approach, or talk to your study group.
Consider meeting with others during the week, have some coffee and cake and hang out, read some more of this book, or do a few exercises together.
Also, consider joining local programming events, like meetups, hackdays, or a conference once in a while. That gives you a broader perspective, even if you might not be able to fully understand everything all the time. Meeting with others and hearing about their experiences can also be a good source for inspiration and motivation.
If you can’t put a lot of time into learning programming, then consider spending at least a few minutes on it every day. For example, every morning, before you leave the house, take 10 minutes to read a page in a programming book, review an exercise, or read some code. Or spend some time with it on the train on your way home from work later. This will keep your subconscious mind busy with programming concepts, and help you pick things up more easily later.
Don’t believe everything we say
While you are learning to program you’ll talk to seasoned developers, ask questions, and listen to their advice, tips, and experiences.
That’s great! We recommend you listen closely, try to understand, and ask more questions.
However…
Always try to put things into perspective. Whatever you hear, try to understand, compare with other opinions, and judge for yourself.
As in any other field, developers, as much as any other person, will always give answers based on their own perspective, based on their own experience, and based on what they think is a helpful answer to you. Now, being a great Ruby developer (“knowing your stuff”) and being a great Ruby coach (“giving the right advice”) are not the same thing.
Developers who have never coached, or are new at coaching, tend to, for example, overload you with information. They know all this stuff, and, as said before, they aren’t even fully aware of it. They just use the language, and all these features.
Now, when asked to explain things, and may suddenly remember: “Oh, right, and then there’s also this other language feature, and that one, and another one, and …!” They may be excited to explain all the things at once, without realizing that, at this particular point, you actually don’t need to know them all at once.
Also, they might present a conclusion, and omit the learning process. In programming, as much as in any other field, there’s rarely any silver bullet. There are always alternative solutions, with different advantages and downsides. Often it’s hard to fully understand them unless one tries a particular solution and sticks with it for a while.
On top of this, whatever is considered a “good” solution by the community at any point of time, is always the conclusion of a decades-long evolutionary learning process. Often it makes sense to start practicing with an older and simpler solution that worked well enough a couple of years ago, and experience some of the problems it caused, and only then try using the latest, more modern solution.
Developers often tend to be excited about their current preferences, and things they’ve learned just recently, and will happily explain at length why one solution works better for them than another. Often you can learn a lot from listening to these explanations. However, keep in mind that this doesn’t necessarily mean a tool that’s great for an experienced programmer is also a tool that’s great for learning to program.
On top of all this, there’s also the problem that the tools we use, over time, become more and more optimized for advanced users. New features are being added that make our day-to-day work easier, and over the years, up-to-date documentation and tutorials will focus on these features.
While that’s great for advanced users, it’s sometimes bad for beginners, as you now not only need to understand the basic concept, you also need to understand this additional, more abstract feature that sits on top of it.
A good coach will try and adjust to these things, but doing so also is a skill that needs to be learned. Try to give feedback, and help your coaches improve.
Of course, everything said above also applies to ourselves :)
Here are a few examples of decisions we made for our study groups. Don’t worry if you don’t understand what we’re talking about at this point. You’ll fully understand them once we get there during study group.
We use the old syntax for hashes in beginners groups: When you first learn what a hash is, and how it is used, this is quite a bit of information to process. The new hash syntax puts an extra burden on you, and raises extra questions that just aren’t required to be answered in order to understand what a hash is, how it can be used, and why it is useful. We do mention the new syntax, and explain why we do not use it.
We purposefully ignore tons of language features, such as for and while loops, lambdas, splats, class level methods, class variables, and so on. Our goal is to get to the point where you can write a simple Sinatra application while fully understanding everything you use in terms of Ruby language features. We found this is the point where it starts getting more and more exciting for most students: You can now build your own web applications, and understand what you’re doing. For this, many language features simply aren’t required.
We use the test framework that is integrated into Ruby on Rails, rather than RSpec, another, more modern framework that is preferred by many (but not all) Ruby programmers. Why? It is easier to understand: methods are just methods, not method calls that generate other methods that magically do things. You don’t need to learn a lot in order to start writing your first test, and there is no additional setup required. Later on, once you’ve written a good bunch of tests, it is a great idea to then learn RSpec, and try that one for a while.
One example of technology improving, adding features optimized for more advanced users, and thus making it harder to understand for newcomers, is the aforementioned new hash syntax. Other examples are: Rails’
resources
macro in routes (it is much easier to understand the whole concept if you type out these 7 routes yourself), and Rails’ migrations (they’ve gone, for good reasons, from being numbered to being timestamped, making the naming choicemigrate:up
andmigrate:down
harder to understand; and they’ve introduced the methodchange
, replacing the methodsup
anddown
).
Formatting your code
Keeping things pretty
By “formatting code” we refer to the way code is indented, or not, where and how whitespace is being used in order to separate things visually, or not, etc.
For example, even though this code won’t mean a lot to you at this point (it will, in a few chapters), it is formatted well:
def widths(row)
row.map do |cell|
cell.width
end
end
Even without understanding what these few lines of code do at all, from just looking at it one can recognize a certain structure: things are somehow nested, and stuff that sits on the same horizontal indentation relates to each other visually.
On the other hand the following code also is perfectly valid Ruby code, in the sense that Ruby will happily run it, and it does exactly the same. However, it is not formatted very well at all (in fact it’s quite a mess, isn’t it?):
def widths ( row
)
row.map do | cell| cell.width
end
end
This does the same, technically: Ruby will run it. But it does not look and read the same, simply because of the way it is formatted.
Pattern recognition
When you talk to developers about your code you will probably notice very quickly that we are all obsessed about how exactly we’d like code to be formatted.
There’s a good reason for that, and you should try hard to learn how to use your editor in order to format your code properly, so over time, you can become obsessed, too :)
The reason for this obsession is pattern recognition: The ability of our brain to help us recognize things subconsciously, from just glancing over them.
Imagine you are driving a car, and there’s a kid playing with a ball on the sidewalk. Even if you only see the kid from the corner of your eye your brain will immediately process this information and even assign a meaning to it. You’ll start paying attention, and maybe lower your speed immediately.
Often when you search for something online, and look at a couple of websites, your brain will help you recognize things that you are looking for without consciously thinking about the structure of the site.
In the same way, as a programmer, when you look at code, you will immediately recognize some of its structure, and thereby understand it, without reading it thoroughly. This allows you to to grasp the overall picture of what you’re looking at, and quickly find something that you’re looking for, in order to then read this particular part of the code more carefully.
As you can imagine, in order for this kind of pattern recognition to kick in, code needs to be formatted consistently and carefully.
Another aspect of this is that looking at “badly” formatted code causes discomfort to an extent that it is described as causing physical pain. While this might seem like an exaggeration, there’s something important to it.
Because of how pattern recognition works, as a programmer, whenever there’s a small issue with the formatting of some code, you will immediately notice it, and distract yourself from other thoughts. You’ll need to re-focus on what you were trying to accomplish, and whenever you come back to the code it will distract you again.
That’s why developers often feel the urge to fix even small formatting mistakes immediately. People might outright refuse to talk about your actual code, and any questions that you have, before you’ve added this one extra space here, and indented that other single line there.
(One certain aspect of this has been even baked right into the language Ruby. For example, by definition, class names must start with an uppercase letter. On top of this, the Ruby community has agreed on conventions for how to write other names. We’ll get to this once we talk about variables and classes more.)
Reading error messages
Your new best friends
As programmers we want to build things that work. When we do exercises we want to figure out the solution to the given problem. Seeing error messages therefore has a negative connotation to it: we haven’t managed to get it right! Right?
Well, yes. But also, no. Errors can also indicate progress. Often error messages indicate that we’ve done the right first step, and now have to figure out the right next step.
Whenever you find an error message printed on your terminal, don’t fret. Instead, appreciate it. Read it carefully. Ruby is trying to give you a hand and help. Normally an error message tells you exactly what went wrong in your code, and where. Once you’ve understood what has happened look at your code and try to understand why it happened, and how you might fix it.
Often, when you fix one thing, you’ll get a different error message. That’s progress. Rinse and repeat until your code does what you want.
For a beginner, Ruby error messages can sometimes be hard to read. There are certain conventions used, e.g. for indicating there’s an instance of a certain class involved. Backtraces might be hard to read and might look frightening. But all they do is tell you the exact path of execution Ruby took until it ended up raising an error.
If you do not understand an error message and you have the opportunity to ask someone else, then do so. You’ll get better and better at reading error messages over time. And you’ll become friends with them.
Using Google
Someone else has already had that question
Whenever you have a question, and you’re not sure where to start looking for an answer, simply go ahead and ask Google. Some other person most probably has had the same question before, and often times you will find a helpful blog post, tutorial, documentation, or answer on community sites like Stack Overflow.
Finding the right terms to type into Google might take some time. Experiment with this. If one question does not yield a helpful result on the first page, try to rephrase your question. You’ll get better and better at this over time.
Always include the keyword “ruby” though, so you don’t get answers for other programming languages.
E.g. if you are wondering if there is a method that gets you all the keys from a hash, then google for “ruby hash get keys”. If you are wondering if there’s a method to align strings so they all have a certain length, then google for “ruby string align”.
Google results that link to the Ruby documentation on ruby-doc.org are often good ones to look at, but these pages also often are very long. Look at the summary of the page on Google, and the highlighted terms, then go to the page and use your browser’s search feature to find them on the page.
Also, results on Stack Overflow are often very worthwhile to read. This is a programming community site where people with all kinds of experience levels post questions, which then will be answered by others. People can rank answers, and the best ranked answer appears at the top. Often it is worth skimming through most of the answers, as sometimes the question asked is just slightly different from yours, but someone might still suggest a solution that might be similar to the one you are looking for.
Your tools
When you start learning programming in Ruby you’ll need three tools: A text editor, a terminal, and a Ruby runtime.
As a programmer you’ll spend tons of time working with your text editor and terminal (or “in” them, as we say). It makes sense for you to learn how to use these tools, and how to customize them so they fit your taste.
There are also text editors that can be integrated into a terminal window. This means you don’t have to switch between applications. There are also online services that integrate all of this as a web application, with a nice interface in your browser.
Feel free to try all these things out, and play with them as much as you like. However, for our study groups, we have found that using the simplest tools possible, at least for a while, works best: It reduces the amount of things people need to learn in the beginning, and everyone learns the same few basic things that are required to run a Ruby program in this environment.
Text Editor
For our study groups we recommend using Sublime Text as a text editor, which you can download and run on Mac OSX, Ubuntu, or Windows. This is an editor that has been specifically designed for writing code, comes with a lot of great tools, and can be easily customized.
Other text editors that might be worth looking at are Atom, Textmate 2, and, if you like to use some of the powerful oldschool tools from the early times of Unix, VIM and Emacs. These are all great editors to use.
Whatever editor you use, you want it to insert 2 spaces when you hit the “tab” key, i.e. when you want to indent your code. Make sure your editor is configured to do this.
For Sublime Text you can do the following: In the menu item “Sublime Text” go to “Preferences” and select “Settings - User”. This opens up a configuration file that you can edit just like any other file. Make sure it looks like this:
{
"tab_size": 2,
"translate_tabs_to_spaces": true
}
Also, we recommend enabling auto-saving your files. This will automatically save your changes when you switch to another application (like your terminal), and protect you from the mistake of forgetting to save:
{
"tab_size": 2,
"translate_tabs_to_spaces": true,
"save_on_focus_lost": true
}
Whenever you open a new file, make sure to save it with a filename that ends
with .rb
first. This will tell the editor that you want this to be a Ruby
file. Your editor will start highlighting your code as Ruby code, and enable other
Ruby specific editor features. Alternatively, select “Ruby” in the extensions
menu at the bottom right.
Some keyboard shortcuts that are extremely useful to know are:
- In order to indent or unindent a single, or multiple lines of code, select
them and hit
tab
orshift-tab
respectively. - In order to comment single, or multiple lines of code, select them, and
hit
cmd-/
on Mac OSX, orctrl-/
on Linux/Windows. - Cut out code with
cmd-x
, copy code withcmd-c
, and paste it withcmd-v
on Mac OSX, on Linux/Windows use thectrl
key instead.
Terminal
Every operating system comes with some kind of terminal application built in, and they’ll be fine to use for us.
A terminal is an application that doesn’t do much more but provide a window to run another program, called a “shell”. A shell is a text-based program, so it does not have a window, and thus needs the terminal to be run on your graphical user interface.
A shell is an interactive program that waits for you to type a command and hit enter (or “return”). It will then run the command, display any output, and then, again, sit and wait for you to type the next command.
This is how working with computers was done in early times before there was the idea of a graphical user interface with clickable icons, windows, and a mouse as an input device. Instead, everything was done by typing commands.
While this might take a little while for you to get used to, you’ll discover why many programmers feel that working with the shell helps them be so much more productive and get simple things done so much quicker than using a mouse and a graphical interface.
The default settings for most terminal applications that come built in to operating systems are quite poor, unfortunately. For example the Terminal.app on Mac OSX opens a tiny little mini window with a very small font. You want to make the window much (much!) bigger, and find the settings to pick a bigger font size. As a programmer, the terminal (along with your editor) is your new home. You want to be as comfortable reading and writing in your terminal as in any other application.
In case you are using Microsoft Windows it is highly recommended to use any kind of unix based terminal. One option is Git-Bash (https://gitforwindows.org/). However, as Ruby and Microsoft Windows are not best friends, it is even better to install the Windows Subsystem for Linux which is available here for Windows 10: https://docs.microsoft.com/en-us/windows/wsl/install-win10. With this option you can install everything you need for your Ruby development under Ubuntu and use Bash as your main shell.
All commands in the following texts are unix commands and will not work in a Microsoft Windows terminal.
Once you have started the terminal program you will see your shell’s prompt,
and can start typing commands. You’ll want to learn at least the command cd
which allows you to navigate to a particular directory (where you have stored
your code), and the command ls
which lists the contents of a directory.
Also learn how to use the command line completion using tab
: For example,
type cd
, space, and the first two or so letters of the directory you want to
navigate to, then hit tab
. The shell will complete the rest of the directory
name for you. If nothing happens, then there are multiple directory names
starting with the same few letters. In this case hit tab
twice, quickly,
and your shell will display a list of choices. Type the next letter
and hit tab
again.
Ruby runtime
The last tool you need is a Ruby runtime. This is a program that can execute
Ruby code, and it is a program that can be run in your shell (much like cd
and ls
).
If you have some code in a file with the name hello.rb
, and you have
navigated to the directory where the file is saved, then you can type the
following and hit enter, in order to execute the code:
ruby hello.rb
This will tell the ruby
runtime to interpret the contents of the file
hello.rb
as Ruby code, and execute it.
One can also use this program to do other things. You probably won’t need these a lot, but we think it’s good to know. You can try these by typing these commands in your terminal.
E.g. you can print out the version of the program:
ruby --version
Or execute Ruby code without storing it to a file:
ruby -e 'puts 123'
Programming workflow
In order to do programming excercises on your computer, play around with things you learned, and write actual, useful programs you want to learn the following workflow:
- Write some code in your text editor.
- Save the code to a file in a particular directory. The filename should end
with
.rb
. - Open your terminal.
- Navigate to that directory using
cd
. - Execute the file using
ruby
. - Switch back and forth between the text editor and terminal, so you
can make small changes in your code, and then run it through
ruby
to see what it does.
In order to switch back and forth between apps quickly you can use the keyboard
shortcut cmd-tab
on Mac OSX, and alt-tab
on Ubuntu and Windows.
In your shell you can use the cursor up
key to go through your last used
commands: you don’t have to type ruby hello.rb
again. Just hit cursor up
and then enter to run it again.
Interactive Ruby
One other tool that is worth mentioning, and that comes with your Ruby runtime,
is irb
. You can start it by typing irb
in your shell and hitting enter.
Its name is short for “Interactive Ruby Shell”, and yes, it is another kind of
shell: Just like the shell running in your terminal irb
is also a program
that interactively waits for you to type something, and hit enter. However,
since this is a Ruby shell, it will expect that you type Ruby code instead of
system commands.
IRB is pretty handy for quickly trying something out, and it is a great tool for exploring the language Ruby, and things that come built in.
For example:
$ irb
> puts "Hello world!"
Hello world!
=> nil
>
The first line starts the IRB program. Notice how the “prompt” indicator
changes. The prompt will look a little different depending on your system and
shell configuration, but often $
is used to indicate that this is a
system shell prompt, while >
is used by IRB to indicate that this is an
interactive Ruby shell.
The second line is a piece of Ruby code. When you type this line and hit enter then Ruby will execute the code, and print out the text “Hello world!”.
It will then also print out the return value for this statement, which in this
case is nil
. This is something that you can simply ignore for the time being.
On the last line you see IRB again waiting for input with a prompt.
You can exit the IRB session, and get back to your system shell, by typing
exit
and hitting enter. Or you can also just hit ctrl-d
, which does the
same thing.
Our Roadmap
Ok, let’s get started.
Here’s a rough overview of the road that lies in front of us, and topics that we’ll cover.
This curriculumn is built on top of two main ideas:
- We introduce one concept after another. Doing so we try to make sure not to assume any knowledge but things that already were explained.
- We get you to a point where you can create your own classes, objects, and methods as quickly as possible: We found that’s the point where it starts to be most fun for most people.
Of course you don’t have to follow exactly this path. For example, if you’re curious, after we’ve introduced the most basic built-in types of things in Ruby, numbers and Strings (text), you might find there’s another chapter about more such types. Feel free to skip ahead, and read it. Just be aware that you might find this chapter assume a couple more things.
So, here’s our roadmap:
1. Object Oriented Programming
We’ll look at the main idea (concept, paradigm) behind programming in Ruby from a little bit of a philosophical perspective. We do this to set a little bit of a context for the terms object, class, and method which will be used quite frequently from then on.
2. Variables
In order to do things with objects, and form readable sentences (code) we need to be able to assign names to them.
This chapter explains variables in Ruby.
3. Built-In Data Types
Ruby comes with lots of batteries included. Things that are built-in, and ready for you to be used when you open an empty Ruby file, or IRB.
We’ll look the two most basic, and most widely used ones:
Numbers, and Strings (text). These will be good enough for us to introduce other concepts without spending too much time on talking about all the other things that Ruby has built-in (we’ll look at the most important ones later).
We’ll quickly mention 3 primitive things that are widely used in Ruby: true
,
false
, and nil
, so you’ve already heard of them when we come across them
later.
Then we discuss Arrays and Hashes, which are data structures. I.e. these are things that you can use to store other things. They’re pretty powerful, and widely used.
Symbols are quite an odd beast, and you normally wouldn’t even need to know them in order to write your own, working code. However, they’re very widely used and many (many!) examples out there use them. So we introduce them briefly, too.
4. Objects, Classes, Methods
This chapter discusses how objects and classes relate to each other, how methods can be called on objects, and explores a number of different methods. It also has a quick look to see what’s up with that magic, so-called top-level object in Ruby.
5. Writing Methods
While variables give names to things, methods give names to behaviour. Methods are like the swiss-army knife of programming. We’ll have a look at the anatomy of a method, and learn how we can define our own ones.
6. Writing Classes
With this knowledge at hand we can now go ahead and define our first class
(kind of thing) ourselves: We’ll define the class Person
, create some
“people” (objects), and let them interact. We’ll learn how to make our objects
“know” things, and how to give them “behaviour”.
7. Blocks
Blocks are a super powerful feature in Ruby. We love them, and use them everywhere. Many built-in methods use them, too. So it’s good to introduce them now.
Blocks are like methods that don’t have a name, and they are passed when calling actual methods. We’ll discuss why this is cool, and how it works.
8. Conditionals, Truthiness, and Nothing
Sometimes you want to run some part of your code only if a certain condition applies (e.g. you only want to allow users to login if the supplied password is correct).
Conditionals allow you to do that. Ruby comes with some pretty cool features included, and we’ll quickly look at them.
9. Operators
There are lots of different operators built into Ruby, and at this point
you’ve already used a number of them (such as the arithmetical operator +
,
and the assignment operator =
).
We’ll look at a few more, and also reveal that operators, in fact, are … methods in disguise!
Bonus and advanced topics
This concludes the core of our “Ruby For Beginners” curriculum. With this knowledge you’ll have all the tools you need in order to play with things, read (most of) other people’s code, and write your own useful things.
There are a couple bonus chapters that you might want to skim through. Some of these are still waiting to find their place somewhere else in the book. Some will probably be marked as optional.
Also there are some extra chapters about advanced topics, some of which are relevant to the more advanced exercises, and for the next curriculum about building your own web applications.
Object-oriented programming
Classes and objects
Ruby is, like many other popular languages, an object-oriented programming language. Other object-oriented languages that you might have heard of are C++, Java, Python, PHP, and Javascript. At some point in the 1990s this paradigm started to become more and more popular and nowadays it is the most prevalent one.
One reason for this is that object-oriented programming very much fits the way we perceive and think about the world, at least in the occidental culture. Our way of thinking was, historically, very much influenced by Plato, and philosophers who followed him:
We somehow tend to think about the world as if there are abstract “ideas” (or concepts) and concrete “things”, which are entities of the ideas, or implementations of the concepts. So, “ideas” are like blueprints for “things”.
For example there’s the idea of a human being, and then there are actual human beings: you, and me. Our idea of a human being includes the ability to, for example, remember our own name, and tell it to others when asked. We know that we can ask people for their name, and they’ll know it.
Now, object-oriented programming languages allow you to describe things exactly this way: In object-oriented programming languages “ideas” are called classes. And actual “things” are called objects.
As a programmer you would define classes, like a class “User” and a class “Tweet”, and then create actual instances of these classes: an actual concrete user with a name, email address, a password and actual tweets. You would also define that users have the ability to remember their own name, and the ability to tell it to others when asked.
Classes are like ideas, objects are concrete things, manifestations of their ideas.
Every object-oriented programming language comes with some “ideas”, that is, classes, already baked in, and so does Ruby. For example Ruby has classes for numbers, strings (text), and other useful things. You therefore can, without any further effort, do number calculations, or text transformations.
In your first excercises you will do just that. Later on you will learn to define your own “ideas” as classes, and then use them by the way of creating actual instances or objects, from these classes, and then make them interact with each other and do interesting stuff.
By the way, here is a quote from a book titled The Early History Of Smalltalk from 1993. It has been written by Alan C. Kay, one of the designers of Smalltalk, which was created in the 1970s, and can be considered the first fully object oriented programming language. We think it’s pretty interesting to read.
“[Smalltalk’s] way of making objects is quite Platonic in that some of them act as […] Ideas from which manifestations can be created. That the Ideas are themselves manifestations (of the Idea-Idea) and that the Idea-Idea is a-kind-of Manifestation-Idea — which is a-kind-of itself, so that the system is completely self-describing — would have been appreciated by Plato as an extremely practical joke.” — Alan C. Kay
Anyhow, you can take away from all of this that those universes that we create when we write programs will be populated by objects (“things”), and their characteristics are defined by classes (“types of things”, or “ideas”).
This will start to make a lot more sense to you once you write your own classes.
There is, today, some debate about the paradigm of object-oriented programming, and whether we should move on to another one, called “functional programming”. You might hear about this if you talk to developers. Some functional languages are Haskell, Go, and Clojure, and they have recently started gaining some traction. Don’t let yourself be distracted by this too much, unless you really want to. Ruby is a very good language to start learning programming for lots of reasons.
Variables
Naming things
In order to refer to the “things” (objects) that our program deals with we want to assign names to them.
Every practical programming language has a feature to do this, called variables. This is basically the same concept that you might know from math, although in Ruby there are different kinds of variables (you will get to know another one in a couple chapters).
We’ll discuss this concept quickly because you already need to know it in the next chapter, and the respective exercises.
If some of this seems rather abstract to you, don’t fret. It will become very practical, and you won’t even think a lot about it any more, as soon as you actually work with variables while learning other things.
Alright. Let’s jump right in:
In Ruby you can assign a name to something (an object) by using the so called
assignment operator =
. Like so:
number = 1
This will assign the name number
to the “thing” (object) that is the number
1
. From now on we can refer to this object by using the name number
. For
example the following code would output the number 1
:
A name on the left side of the assignment operator =
is assigned
to the object on the right side.
number = 1
puts number
One important thing to note here is that a variable is not a “thing” an object
by itself. Instead it’s just a name for an actual object. In our example the
number 1
is an object, while number
is a name for it because we’ve assigned
it.
A variable itself is not a “thing”. It’s just a name for a thing (an object).
You can think of it like a post-it note with the name number
written on it,
and stuck on the actual thing, which is an object (in this case, a number).
Imagine you were in the middle of learning some Spanish, and sticked post-its onto things in your apartment: the name nevara onto the refrigerator, cama onto your bed, and puerta del baño onto the bathroom door.
Now, whenever you use one of these terms, as in abrir la nevera (open the refrigerator) in order to learn the language and rehearse vocabulary, you’ll obviously refer to the object, and open its actual, physical door.
That’s pretty much how variable assignment works in Ruby. There’s a “thing”,
the object on the right side of the assignment operator =
, and the name on
the left side is being assigned to it.
Whenever we use a variable name that has been defined before we refer to the
actual object that it has been assigned to. E.g. on the second line of the
code example above number
refers to the actual object, the number 1
that
it has been assigned to on the first line. Therefore puts number
outputs
the number.
You can pick whatever variable names you want, they’re just names, like post-it notes stuck onto actual objects.
Since names are just names, the following examples would do exactly the same:
a = 1
puts a
large_number = 1
puts large_number
apples = 1
puts apples
However, also note that it makes sense to try and pick names that reveal your
intention. The chapter Using the right
words at the end of this book will talk more about this. Feel free to jump
ahead if you are curious. In short, the first example using a
as a name would
be frowned upon because it’s wasting the opportunity to use a meaningful name.
The second and third examples are just trying to be stupid, and pick names that
don’t match the “thing” (the object, number 1
) at all.
There are a two more things about variable assignments that we’d like to point out before we move on.
Reusing variable names
It is also important to keep in mind that a name is unique, in the sense that the same name can only be assigned to one value (object) at a time.
In other words, if you assign different values to the same variable, then assignments that happen later will simply overwrite previous ones. Like so:
number = 4
number = number * 3
puts number + 2
This, again, would output 14
.
Variable names can be re-used, and re-assigned.
The first line assigns the name number
to the number 4
. The second line
re-assigns it to another object.
Getting back to our post-its metaphor … this would stick a post-it with the
name number
on one thing, and then later take it off of it, and stick it on
something else.
Let’s look at it under the microscope:
- On the first line Ruby creates the number (object)
4
. - It then assigns the name
number
to it (sticks a post-it onto it). - On the second line, Ruby first looks at the stuff on the right side,
and evaluates the expression
number * 3
. Doing so it will create the number (object)3
and multiply it with the object that currently has the namenumber
, which is4
. This operation results in a new number (object)12
. - Now Ruby is finally ready to stick the name
number
on the result12
. I.e. from now on the namenumber
refers to a different object, which is12
. - On the third line Ruby will, again, first look at the expression
number + 2
on the right. It creates the object2
and adds it to the object that currently has the namenumber
. This results in a new number (object)14
. - Finally Ruby passes this object
14
toputs
, which outputs it to the screen.
Of course, you would probably never actually write this exact code in practice
since you can simply do all this in just one line instead: puts 4 * 3 + 2
.
However, sometimes you’ll find or write code that assigns an initial value to a variable, and then keeps working on it for a few more lines. This sometimes is useful to break up long lines, and make code more readable.
Using variable names can be useful to break up long lines and make code more expressive and readable.
Also, Ruby has different kinds of variables.
The kind of variable that we’ve introduced so far is called a local variable, and it’s the one used most often. You will learn about another type of variables later when we talk about classes and objects.
On formatting: Note that there are spaces around the assignment operator `=` as well as the arithmetical operators `+` and `*`.
Things on the right go first
One last thing that is worth mentioning about variables:
Just as in traffic (well, at least in most parts of the world), things on the right side go first :)
For a variable assignment, in order to assign the thing on the right side to the name on the left side, Ruby first needs to figure out the thing on the right. As you will see later the same is true for many other expressions in Ruby.
In our example above it will first create the object that is the number 1
,
and then assign it to the name number
. The following example makes that more
clear:
number = 2 + 3 * 4
puts number
When Ruby looks at the first line number = 2 + 3 * 4
it notices that this is
using the assignment operator =
. Therefor, before it can assign the name
number
to the “thing” (object) on the right … it first needs to know what
that thing is.
So, before she does anything else, Ruby will first look at the expression 2 +
3 * 4
, which will result in the number (object) 14
. She will then assign
the name number
to this object (i.e. evaluate the assignment operator =
).
You can imagine that in this moment, when Ruby starts evaluating the assignment
=
the code temporarily looks like this: number = 14
(because the
calculation has returned the number 14
).
Does this make sense?
Again, on the second line Ruby will then pass the thing with the name number
(which is 14
) to puts
, which will output it to the screen.
Ruby evaluates the expression on the right first.
Built-In Data Types
As mentioned before, Ruby comes with lots of things already baked in, and provides you with tons of tools to use and hit the road running.
We’ll look at some of the most common data types in Ruby. Data types are types of “things” that are mainly used to represent data, such as numbers, text, and other values.
This is basically the “stuff” that you, as a Ruby programmers will work with, when we work with actual data, that is interesting to you, or your users in one way or the other.
We will discusss the following data types:
- Numbers
- Strings (texts)
- True, False, and Nil
- Symbols
- Arrays
- Hashes
These “kinds of things” (objects) cover like 98% of all built-in data types that you’ll be using on a day to day basis, i.e. these are being used all over the place. There are more data types, but those are rather exotic, and used much less often.
Numbers and Strings (which is just a strange name for “texts”) are some of the most basic “things” that you deal with in Ruby on a regular basis. The are also just enough lego bricks for us to dive into more interesting topics, such as how objects, classes, and methods relate to each other, and how you can create your own ones.
Before we do so we will also briefly mention the “things” true
, false
, and
nil
, just because these are things that we’ll come across along the way
anyway.
Symbols also are very commonly used, but quite an odd concept. You normally wouldn’t need to understand Symbols in order to write your own code. We’ll still cover them briefly, and use them once in a while, just in case you find them elsewhere.
Arrays and Hashes are “things” (objects) that are used to store other things, and they’re super useful, and widely used.
Numbers
Numbers are simply numbers.
You can create one by writing it: 123
is Ruby code and it represents the
number one-hundred-twenty-three.
Negative numbers are created by prepending a minus -
: This is the number
minus-ninety-nine: -99
.
And of course there are decimal numbers, too. Again, you create one by writing
it: 12.34
.
You can also use an underscore to separate thousands places: E.g. 1_234.56
is the
number one-thousand-two-hundred-thirty-four-point-five-six. However, this is
optional. This is the exact same number in Ruby: 1234.56
.
A number is defined by a series of digits, using a dot as a decimal mark, and optinally an underscore as a thousands separator.
Note that different countries use different punctuation for decimal and thousands separators. Ruby is using the same notation used in the USA, which happens to be the exact opposite of what’s used in Germany.
Kinds of numbers
Under the hood, for reasons that are mostly technical, there are actually different kinds of numbers:
For example there are integer numbers (those without a fraction, i.e. a dot and decimal places), and depending on their size there are two kinds of them. For floating point numbers there are even more. Unless you are super curious, you can perfectly ignore all of that for now, and just think of numbers as numbers.
However, when you do calculations with numbers, keep in mind that integer numbers (“integers”) and decimal point numbers (floating point numbers, aka “floats”) are different.
If you do a calculation that uses integer number you’ll always get an integer back:
$ irb
> 1 + 2
=> 3
However, if any of the numbers involved is a float, then you’ll get a float back:
$ irb
> 1.0 + 2
=> 3.0
> 1 + 2.0
=> 3.0
Mathematical operations result in a floating point number except if all numbers used are integer numbers.
This is, for example, important when you do a division (/
means “divide by”):
$ irb
> 3 / 2
=> 1
As you can see any decimal places will be just cut off, since the result needs to be an integer number.
However, if you use floats:
$ irb
> 3.0 / 2
=> 1.5
> 3 / 2.0
=> 1.5
Use floating point (decimal) numbers when doing devisions.
Exercises: How about doing some of the exercises on numbers next?
Strings
A String, in programming languages, is text.
More precisely a String is an object that represents a specific text. E.g. your name, represented in Ruby, is one String. The title of this text is another String, and so is this paragraph, and this full text in its entirety.
In web applications Strings are everywhere. In fact they’re used by far more often than numbers. That probably is because most applications that we deal with on a daily basis are dealing with text messages (Email, Facebook, Twitter, …) or descriptions of things (Amazon, Ebay, …).
Did you ever use a web application that is a complicated calculator app, and mostly deals with numbers? There you go :)
In Ruby there are a couple of flexible ways to create Strings, but the most simple and by far most common way is to simply enclose some characters in quotes:
"This is one String!"
'And this is another one.'
Strings can be defined by enclosing any text with single or double quotes.
Both of these are good to use. Technically they are almost the same, except for one important feature called “String interpolation”, which we’ll explain later.
Things you can do with Strings
Here are a few things you can do with Strings.
You can glue them together by using +
. We also call this
concatenation.
$ irb
> "snow" + "ball"
=> "snowball"
> "hi" + "hi" + "hi"
=> "hihihi"
The last operation can also be done by using *
, like so:
$ irb
> "hi" * 3
=> "hihihi"
I.e. multiplying a String by a number in Ruby means repeating the String as many times.
The following examples are worth noting, too:
$ irb
> "1" + "1" + "1"
=> "111"
> "1" * 3
=> "111"
This demonstrates that Ruby behaves the same for Strings that contain nothing but numbers.
Some other things that you can do with Strings that you can try yourself in
irb
:
> "hello".upcase
=> "HELLO"
> "hello".capitalize
=> "Hello"
> "hello".length
=> 5
> "hello".reverse
=> "olleh"
These last few examples are examples of “calling methods on objects that are Strings”. Methods are “behaviour” that objects are capable of. We’ll explain methods, and how they relate to objects more in just a few chapters.
Exercises: How about doing some of the exercises on Strings next?
History
The reason why pieces of text, in the context of programming languages, usually are not referred to as “text”, but instead with the slightly odd term “String” is a historical, and technical one:
In early programming languages Strings were implemented as lists of characters, and programmers had to deal with them as such: imagine pellets lined up on a String. One had to take care of nasty things like manually managing the length of the character list, and inserting a couple characters in the middle of such a list was quite a complicated operation.
When languages evolved over time they made things like this easier for developers, and added built-in abstractions for the concept of “Strings”. So the name sticked, and resembles the way programmers had thought about text before: as characters lined up on Strings.
Luckily, nowadays, in Ruby and any other modern language, you can just think about Strings as text.
True, False, and Nil
There are three more “things” that we’d like to mention quickly, just because
you’ll see quite frequently: These are true
, false
, and nil
.
The first two, true
and false
are just what you think they are:
The object true
represents “truth”, while false
represents the opposite of it.
In other words, in Ruby, true
and false
are also “things”, just like
numbers, Strings, Arrays, and Hashes. You can assign them to variables, pass
them around, and otherwise use them. They’re fairly simple things, but they’re
also very useful.
The third object nil
represents “nothing”. Yeah, that’s right. In Ruby
there’s a “thing” that represents the absence of things. This might be a
interesting topic to discuss on a philosophical level, but for now we’ll
just see how it works in Ruby.
The object nil
represents “nothing”.
You’ll see later that every operation (“method”) in Ruby always returns exactly one thing (i.e. one object), and that’s why there needs to be a “thing” that represents “nothing”.
This will start to feel pretty natural to you pretty soon.
Symbols
Symbols are like strings, except they are code.
Symbols are a rather strange concept to be honest, and we only introduce them because symbols are used so often and widely that you’ll very likely find them used in code elsewhere. For your first steps in learning programming we would not necessarily need them.
A symbol is written like this: :something
I.e. there is a word that is preceded by a colon. This means that normally
symbols do not contain spaces. Instead, if we have symbols that consist of
multiple words we would concatenate them with underscores, like so:
:another_funny_symbol
A symbol is created by adding a colon in front of a word.
So when to use strings, and when to use symbols?
There’s actually no perfectly clear line or simple definition.
One rule of thumb is that if the text at hand is “data”, then use a string. If it’s code, then use a symbol, especially when used as keys in hashes (see below).
Another way of looking at symbols is that they aren’t really text, even though they read well. Instead they are unique identifiers, like numbers, or bar codes. While strings represent data that can change, symbols represent unique values, which are static.
And more technically, if you use strings that contain the same text in your code multiple times, then a new string object will be created every time. E.g. if you do
puts "Hello!"
10 times then 10 actual string objects will be created (and then discarded, because they’re not being used any longer). On the other hand, if you’d use a symbol for this and doputs :hello
10 times, then only one single object will be created, and re-used.
Symbols are unique identifiers that are considered code, not data.
If you find all of this confusing, don’t worry, many people do. You’ll understand symbols better once you get to use some of them in your code. For now you can think of them as a special, limited variation of Strings (text).
Symbols are a special, limited variation of Strings.
The technical difference
Skip the following unless you’re really curious about the underlying technical difference between Strings and Symbols.
Try this in IRB:
$ irb
> "a string".object_id
=> 70358630335100
> "a string".object_id
=> 70358640625960
> "a string".object_id
=> 70358644379620
As you can see, even though the 3 Strings created are exactly the same, every
new String created has a different object_id
: They’re actually different
objects, even though they contain the same text.
With Symbols however:
$ irb
> :a_symbol.object_id
=> 1086748
> :a_symbol.object_id
=> 1086748
> :a_symbol.object_id
=> 1086748
We now get the same object_id
for each of the Symbols, meaning they’re
referring to the exact same object.
The object_id
is a unique id that Ruby uses internally in order to keep track
of all the objects that are flying around in the universe that makes up your
program. E.g. Ruby needs to know which objects are still being useful, and
which ones can be cleaned up and thrown away. The object_id
is a way to
identify each and any object by a unique id.
By true
, false
, and nil
(as well as all numbers) also behave this way:
Whenever you use these in your code you’ll get the same object. Try it in IRB
and check their object_id
.
Let’s move on though.
Arrays
Arrays are like bags that contain things
While numbers, Strings, true
, false
, and nil
all represent simple,
primitive things, Arrays are more interesting, and very useful.
Arrays are things that store (or “hold”) other things. You can think of an Array as a collection or list of things. Or better yet, as a bag that you can throw things in: The bag itself is a thing (an object), too.
An Array is an object that stores other objects.
An Array is created by separating values by commas, and enclosing this list with square brackets, like so:
[1, 2, 3]
This is a simple Array that holds three numbers.
An Array is created by listing objects, separated by commas, and enclosed by square brackets.
Arrays can contain all kinds of things:
["A string", 1, true, :symbol, 2]
This creates an Array with 5 elements, i.e. a bag that contains 5 things: a
string, a number, true
, a symbol, and another number.
Arrays can contain all kinds of objects.
Note that in Ruby Arrays always keep their order: Unlike a real bag, where, when you throw in a bunch of apples, they might come out in a different order, an Array always keeps its objects in the same defined order (unless you do something to change the order).
Also note that in Ruby you can store any kind of object in an Array. For example, you can also store Arrays in an Array: that’s a 2-dimensional Array, like a table that has many rows, and each row has many cells (“things”).
Arrays have a defined order, and can store all kinds of objects.
Retrieving an element from an Array
Arrays can be used in a lot of different, and useful ways, but the most basic one is to retrieve a certain element by the way of referring to its position: Please get me the element at position 1!
In Ruby, you can do this with square brackets like so:
words = ["one", "two", "three"]
puts words[1]
Do you recognize how we use a variable to assign a name to our Array here?
words[1]
means: from the Array words
get the element at position
1
. puts
will then print this value out to the screen. (By the wayputs
stands for “(out-) put string”.)
So, as you can see square brackets []
mean different things when used in
different contexts. The same is true for other punctuation, too.
In our case here the square brackets on the first line are used to create an
Array object with the given elements. On the second line the square brackets
are now appended to a variable name. This means we want look up an element from
the Array that (at this moment) is called words
(i.e. assigned to the
variable words
).
Punctuation (such as square brackets) has different meanings in different contexts.
Arrays are zero based
What do you think our example code will print out to the terminal? Funnily,
it’s two
, and not one
. Huh?
The reason for this is: The first position in an Array is 0, not 1. So the position 1 refers to the second element, not the first one.
In programming books and the Ruby documentation you’ll often find the word “index” to be used instead of “position” in this context.
Arrays start with the index 0, not 1.
Appending an element to an Array
In order to add an element to the end of an existing Array you can use the
operator <<
, called “shovel operator”, like so:
words = ["one", "two", "three"]
words << "four"
puts words[3]
This prints out four
: The string "four"
has been “shoveled” into, that
means, appended to the Array. And since it now sits at position 3
we can
access it using words[3]
.
Setting an element to a position
You can also set an element to a specific index, like so:
words = ["one", "two", "three"]
words[3] = "four"
puts words[3]
This also prints out four
.
You could also overwrite existing elements the same way. For example this
would set the word "uno"
to the position 0
(i.e. overwrite "one"
):
words = ["one", "two", "three"]
words[0] = "uno"
puts words[0]
So, this would now output uno
.
On formatting: Note that there are no spaces inside the square brackets, and there’s one space after each comma.
Missing elements
What if we try to retrieve an element that does not exist, for example the element at the fourth, or fifth position (index)?
Right, we get back nil
, meaning “nothing”:
> words = ["one", "two", "three"]
> words[3]
=> nil
> words[4]
=> nil
You’ll learn later that every operation (“method”) in Ruby always returns exactly one thing (i.e. one object), and that’s why there needs to be a “thing” that represents “nothing”.
This will start to feel pretty natural to you pretty soon.
Nested Arrays
As said before, Arrays can contain all sorts of things. So, they can also contain more Arrays.
This is quite a common thing to use when you need to represent some data that has the characteristics of a table, like an spreadsheet.
The outer Array represents the table, and each inner Array represents one row. Each value then represents a cell. For example:
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[0]
]
This is a nested Array that represents the structure of number keys on a phone.
Things you can do with Arrays
Here are a few things you can do with Arrays.
Look at them, and play with them in irb
. Don’t necessarily try to memorize
all of them … you can always look things up in the documentation when you
actually need these. It might be useful if you’ve seen some of them before
though.
Funnily, you can “calculate” with Arrays. Remember set theory from your math classes? This is pretty similar.
You can add Arrays:
$ irb
> [1, 2] + [3, 4]
=> [1, 2, 3, 4]
Subtract them from each other:
$ irb
> [:one, :two, :three, :four] - [:three, :four]
=> [:one, :two]
Multiply with a number:
$ irb
> ["Ruby", "Monstas"] * 3
=> ["Ruby", "Monstas", "Ruby", "Monstas", "Ruby", "Monstas"]
And find the intersection:
$ irb
> [1, 2, 3] & [2, 3, 4]
=> [2, 3]
first
and last
are alternative ways to retrieve the first and last element:
$ irb
> [1, 2, 3].first
1
> [1, 2, 3].last
3
Some other things that you can do with Arrays that you can try yourself in
irb
:
> [1, 2, 3].length
=> 3
> [3, 1, 2].sort
=> [1, 2, 3]
> [1, nil, 2, 3, nil].compact
=> [1, 2, 3]
> [1, 2, 3].index(3)
=> 2
> [1, 2, 3, 4].rotate(2)
=> [3, 4, 1, 2]
> [[1, 2, 3], [4, 5, 6], [7, 8, 9]].transpose
=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
In these examples compact
removes all nil
values from the Array.
transpose
works with a nested Array and “flips” the table, i.e. turns columns
into rows, and rows into columns.
Exercises: Now would be a good time to do some of the exercises on Arrays.
Hashes
Dictionaries: look up one thing by another
Hashes are another very useful, and widely used kind of thing that can be used to store other objects.
Unlike arrays which are mere lists, Hashes are like dictionaries:
You can use them to look up one thing by another thing. We say: we look up a value from a Hash using a key. Or one could say: Please get me the value that is associated with this key.
Imagine a real dictionary that translates from English to German. When you look up the English word “hello” then you’ll find the German “Hallo”. When you look up “one” then you’ll find “eins”, and so on. The authors of this dictionary have assigned a German word (a value) to an English word (the key).
Hashes work pretty much like this.
A Hash assigns values to keys, so that values can be looked up by their key.
We also refer to a value that is assigned to a key as key/value pairs. A Hash can have as many key/value pairs as you like.
Creating a Hash
In Ruby you can create a Hash by assigning a key to a value with =>
, separate
these key/value pairs with commas, and enclose the whole thing with curly
braces.
This is how it looks:
{ "one" => "eins", "two" => "zwei", "three" => "drei" }
This defines a Hash that contains 3 key/value pairs, meaning that we can look
up three values (the strings "eins"
, "zwei"
, and "drei"
) using three
different keys (the strings "one"
, "two"
, and "three"
).
By the way, the Ruby community has come up with the name hash rocket for the
bit of syntax =>
which separates a key from a value, … we think that is
pretty cool to know :)
A Hash is created by listing key/value pairs, separated by hash rockets, and enclosed by curly braces.
Looking up a value
Now, how do you actually look up the value that is associated with the key
"one"
?
Just like with Arrays you use sqare brackets, but instead of passing a number indicating the position you now pass the key. Like so:
dictionary = { "one" => "eins", "two" => "zwei", "three" => "drei" }
puts dictionary["one"]
Do you reckognize how we, on the second line, use a variable name to refer to the Hash defined on the first line?
In this example, dictionary
is a Hash defined on the first line. ["one"]
looks up the value associated to the key "one"
, which is "eins"
. This value
will be passed to puts
which outputs it to the screen.
Setting a key to a value
Also, just like with Arrays, there’s a way to set key/value pairs on an existing Hash:
dictionary = { "one" => "eins", "two" => "zwei", "three" => "drei" }
dictionary["zero"] = "null"
puts dictionary["zero"]
This prints out null
.
You could also overwrite existing key/values the same way. For example this
would set the word "uno"
to the key "one"
(i.e. overwrite the existing pair
with the value "eins"
):
dictionary = { "one" => "eins", "two" => "zwei", "three" => "drei" }
dictionary["one"] = "uno"
puts dictionary["one"]
So, this would now output uno
.
Any kind of objects
You can use any kind of object as keys, and you can store any kind of object as values.
For example, these are all valid Hashes, too:
{ 1 => "eins", 2 => "zwei", 3 => "drei" }
{ :one => "eins", :two => "zwei", :three => "drei" }
{ "weights" => ["pound", "kilogram"], "lengths" => ["meter", "mile"] }
{ :de => { :one => "eins", :two => "zwei", :three => "drei" } }
The first example uses numbers as keys, while the second one uses Symbols, which is quite a common thing to do in Ruby.
In the third example you can see that one can use Arrays as values in Hashes.
So if you’d look up the key "weights"
you’d now get an Array back.
Not quite sure where you’d use a Hash like the third line in real code, but the last line is actually pretty darn close to what Rails uses for translating strings to different languages:
It is also an example of a nested Hash. There’s another Hash associated
(stored) as a value for the key :de
(representing the language German). And
this other Hash now has three key/value pairs. So a Rails programmer could look
up how to translate “one” to German, and get “eins” back.
Hashes can use any kind of objects as keys and values.
On formatting: Note that there’s one space inside the curly braces on both
sides, around the =>
Hash rocket, and after each comma. But there
are, again, no spaces inside that square brackets []
that define
the Arrays on the third line.
Missing values
What happens if we try to look up a key that does not exist?
Again, just as with Arrays, we’ll get back nil
, meaning “nothing”:
$ irb
> dictionary = { "one" => "eins", "two" => "zwei", "three" => "drei" }
> dictionary["four"]
=> nil
Does this make sense?
Things you can do with Hashes
The main purpose of a Hash is being able to lookup a value by a key.
However, here are a few other things you can do with Hashes, too.
Again, look at them, and play with them in irb
. Don’t necessarily put too
much effort into memorizing all of them …
You can merge two Hashes:
$ irb
> { "one" => "eins" }.merge({ "two" => "zwei" })
=> { "one" => "eins", "two" => "zwei" }
fetch
does just the same as the square bracket lookup []
discussed before,
but it will raise an error if the key is not defined:
$ irb
> dictionary = { "one" => "eins" }
> dictionary.fetch("one")
=> "eins"
> dictionary.fetch("two")
KeyError: key not found: "two"
keys
returns an Array with all the keys that a Hash knows:
$ irb
> dictionary = { "one" => "eins", "two" => "zwei" }
> dictionary.keys
=> ["one", "two"]
length
and size
both tell how many key/value pairs the Hash has:
$ irb
> dictionary = { "one" => "eins", "two" => "zwei" }
> dictionary.length
=> 2
> dictionary.size
=> 2
Exercises: Now would be a good time to do some of the exercises on Hashes.
Hash syntax confusion
You can skip the following and jump right to the chapter Variables, or you can keep reading if you’re curious.
We’ve found it’s important for us to explain the following somewhere in our book, because many online resources use another, alternative syntax for defining Hashes.
The term syntax, in programming, refers to ways to use punctuation in order to (amongst other things) create objects.
E.g. the fact that we can use square brackets []
and commas ,
in order to
define Arrays, and curly braces {}
and hash rockets =>
in order to define
Hashes, is part of Ruby’s syntax.
Today, there are two different syntaxes for defining Hashes with curly braces, and they have the potential to confuse newcomers a lot.
There’s an old-style one, and a new, modern one.
We use the old syntax, and ignore the new one, for now.
Here’s why:
Up to a certain version of Ruby the syntax that uses hash rockets was the only syntax to create Hashes, and it works the same for all Hashes no matter what kinds of objects you use as keys.
Then, a few years back, a new syntax was introduced. Many Ruby programmers love to use it, because it takes a little less space, and it also looks a lot like JSON, which is a data format that is widely used in web applications.
The new Hash syntax looks like this:
{ one: "eins", two: "zwei", three: "drei" }
Using this syntax we tell Ruby that we want the keys to be symbols.
Hmmmm? We just learned that Symbols are defined by prepending a word with a
colon like :one
, right?
Well, yes, correct, but that’s just how it works: If you define a Hash using this syntax, then you’ll get a Hash that has Symbols as keys.
We found the new syntax pretty confusing for beginners: It is slightly less explicit and obvious, and you have to learn another piece of information. That’s why we simply ignore it in our study groups while we learn what Hashes are and how you can use them.
Once you feel comfortable using Hashes, you can make your own decision, and start using the modern syntax if you like it better.
In the meanwhile, whenever you see a Hash using the new syntax somewhere else, you now know what it means, and where to look it up. Simply remember that these two Hashes are exactly the same:
{ :one => "eins", :two => "zwei", :three => "drei" }
{ one: "eins", two: "zwei", three: "drei" }
Objects, Classes, Methods
We’ve talked about objects quite a bit now, merely using that term as a synomym for “things”. You’ve learned to do stuff with simple objects such as numbers and Strings, and the data structures Arrays and Hashes. You’ve also learned what a method is, how to define and use them, and how to pass things to them, and get others back.
We’re also prepared well enough to have a closer look at objects, classes (types of things) and methods, and how they relate to each other.
As a sidenote, you may have heard the following. Rubyist love saying it:
In Ruby everything is an object.
Actually, this is a little bit of a lie. Even though it’s being said so often, it isn’t completely true: not everything is an object. There are a few things that are not objects.
But almost everything is, and that’s good enough to know for now.
In Ruby almost everything is an object :)
(Just in case you want to out-smart people at your local Ruby user group though, remember to ask “Really? Is an if statement an object or a method? How about self?”)
Objects have classes
As mentioned before when you run a Ruby program a little universe (space, scope) is being created, and populated with concrete objects (things) as defined by your program. These things interact, and do useful stuff, using certain methods that you call.
Also, each concrete thing (object) is an instance of a general idea or type, and these ideas are called classes.
You can see that objects have classes when you open IRB in your terminal and ask an object for its class:
$ irb
> "this is a string".class
=> String
Objects are concrete instances (manifestations) of classes.
That means that the following sentence is true: In Ruby, "a string"
is a
String
.
Hmmmm, yeah, we kinda knew that already, right. However, you can also ask the same question in Ruby:
$ irb
> "this is a string".is_a?(String)
=> true
So, the actual string knows that it is a String
, which in Ruby means that
it is an instance of the class String
. You can do this for any object. E.g.
1.is_a?(Numeric)
, also returns true
. This is pretty cool.
We also say that an object is an instance of its class. Let’s see what that means.
Classes create objects
Objects are instances of classes.
Classes are kind of blue prints for the concrete objects. Every time a concrete
object (such as the String "one string"
, the String "another string"
, the
String "yet another string"
, and so on …) is created:
We say that the class is instantiated: an object is created from it.
What does that mean?
Classes have a bunch of characteristics, but most importantly, every class defines a number of methods, which are specific to this type of thing (e.g. a String).
Now, every time a new object is created (“instantiated”) from this class this new object get (“inherits”) all of these methods.
Objects inherit methods from their classes.
That’s right. Objects have their own methods attached to them.
We’ll explain more about methods that belong to objects in the next chapter. And you’ll see how you can define methods to your own classes, so they’re then available on your objects when we start defining our first, own class.
But for now we can already point out that all concrete Strings that you use in your code will all have the same methods defined (attached to them) … because they’re all created (instantiated) from the the same class.
You can have a look at all the methods that the class String
defines on
Ruby’s documentation page for this
class.
Let’s see how we can use these methods.
Objects have methods
Methods are an object’s behaviour
Objects have methods, allowing us to do interesting stuff with them. An object’s methods are things that the object can do.
Think about a person, like, a friend of yours. You can ask this person for their name (call a method), and they’ll tell you (return it to you). Their name is a piece of knowledge that this person has, and the ability to tell it to you (respond to your question) is a piece of behaviour (a method) they have.
We could also ask them to make, and bring a cup of tea for us. Or we could ask them to remember a phone number, or email password.
As Rubyists we actually say that we “talk to objects”, or “send messages” to them: We do so by using (calling) methods that they respond to.
So, what can an object do?
That depends on their class (type). Numbers can do things that are useful for numbers, obviously. You can do math, and ask them about their mathematical properties (e.g. “Are you an odd number?”). Strings (text) come with way more methods, and they’re often related to text transformations.
Methods add behaviour that is useful to have for a particular type of object.
We’ve already used some methods in the previous chapters: E.g. "hello".upcase
calls the method upcase
on the String "hello"
.
Also, class
and is_a?
are methods defined on all objects in Ruby, and
therefore also defined on the String "a string"
: "a string".is_a?(String)
answers with true
.
Some methods, such as class
, is_a?
, are defined on all objects.
Let’s move on to see how we can use (call) these methods though.
Calling methods
In Ruby, methods that belong to (are defined on) objects can be used (called) by adding a dot, and then the method name, like so:
object.method
That’s like saying Hey object, please do [method].
Let’s try that out in IRB.
For example, the class String
defines methods like upcase
(Give me an
uppercase version of yours), downcase
(What’s the downcased version of
yours), and length
(Hey string, what’s your length?).
Here’s how we can call them:
$ irb
> name = "Ruby Monstas"
> name.upcase
=> "RUBY MONSTAS"
> name.downcase
=> "ruby monstas"
> name.length
=> 12
And so on.
In other words, you first address, or mention, the object that you want to talk
to, and then, with the dot .
, “send a message” to the object by specifying
the method name. We also say: “you call the method upcase on the string”.
A dot is used to call a method on an object.
Imagine the string name
is a person you can talk to. You can ask questions by
“sending a message” to them, and they’ll respond by sending (returning)
something back. The term “sending messages” actually is used instead of
“calling a method” in programming, and specifically in Ruby.
So, you can ask the string to hand you an “upcased” version of itself. And
it responds by doing so. Or you can ask it for its length, and it responds
by returning the number 12
to you.
You can have a look at all the methods that the class String
defines
(responds to) on Ruby’s
documentation page for this class.
Most methods in Ruby work this way: You ask an object to return a bit of information about itself (such as a String’s length), or a modified (transformed) version of itself (such as a downcased version of the String).
Most methods are questions, and return a relevant value.
Others modify the object itself, and some have so called side-effects, and
modify something else, e.g. puts
and p
both output something to the
screen. Other methods might save files, send emails, or store things to
a database.
Some methods are commands, and change the object, or the system (e.g. by saving a file).
Passing arguments
Extra information needed
Sometimes an object needs a little bit of extra information in order to do what you ask for.
For example the class String
defines the method delete
which returns
another String with some of the characters deleted. In order to do so, of
course, it needs to know which characters we’d like to remove.
We can pass things by appending parentheses ()
to the method call (the name).
We can then include the extra bit of information needed (in our case another
string) inside the parentheses like so:
$ irb
> name = "Ruby Monstas"
> name.delete("by Mo")
=> "Runstas"
Hm. Not sure what “Runstas” means. Ideas? Let us know.
Anyhow, another example for a method that needs an argument is the method
prepend
on Strings. This method returns a new String with the given String
prepended:
> name = "Ruby Monstas"
> name.prepend("Oh, hello, ")
=> "Oh, hello, Ruby Monstas"
These extra bits of information are called arguments. We’ll discuss them more once we get to define our own methods.
Not all methods need these extra bits of information (arguments) in order to do
their job. E.g. the method length
on Strings knows the length of their
String just so (because it knows its String). Sometimes they need one or more
arguments though.
So how do you know?
You might remember, over time, for some important methods, but most of us also check the documentation quite frequently, too. Some times it’s just fine to try it out quickly, and only check the documentation if it does not work as expected.
Listing methods
As said above, if you are curious what methods are defined on a certain object, then you can check the Ruby documentation for this class. (Usually the right page conveniently shows up at the top when you google for “ruby” and the class name.)
However, you can also quickly pop into IRB and ask the object for its methods.
That’s right, methods
is a method defined on all objects (just like class
,
and is_a?
).
When you call it then it will return (respond with) an Array with all the method names that the object has.
It makes sense to sort the Array, so it is easier to read. Like so:
$ irb
> "Ruby Monstas".methods.sort
=> [:*, :+, :<, :>, :[], :class, :downcase, :delete, :include?, :is_a?, :length, :prepend, :start_with?]
Yep, the method names come as Symbols, because they’re considered code.
If you do this yourself, you’ll see that the String actually has a lot more methods. Many of these actually aren’t used very often, but some are quite useful. We have stripped the Array down a little, because we want to talk about some of these methods more.
Chaining method calls
Btw the code above also demonstrates that methods can be “chained”: When we call a method on an object it will return another object to us. We can then immediately call another method on that new object, and so on.
In our example above the method methods
returns a Array of names. And
Arrays respond to (have) the method sort
, so we can call this method
immediately, by using another dot.
We could chain some of the method calls from our String example above like so:
$ irb
> name = "Ruby Monstas"
> name.prepend("Oh, hello, ").upcase
=> "OH, HELLO, RUBY MONSTAS"
So we have a String "Ruby Monstas"
, prepend another String to it, which
returns a new String "Oh, hello, Ruby Monstas"
, on which we immediately call
the method upcase.
Pretty handy.
As you can see Ruby will first evaluate the bit name.prepend("Oh, hello, ")
.
It needs to do that so it knows the object (the new String) that is going
to be returned from this, so it can then call the method upcase
on it.
Does this make sense?
Predicate methods
If you check the list of methods on our String above you see that in Ruby we
can have methods that end with a question mark ?
. What’s up with that?
By convention, in Ruby, these methods return either true
or false
. For
example, we can ask a number if it is even or odd:
$ irb
> 5.odd?
=> true
> 5.even?
=> false
This makes them read like a question, which is pretty cool.
Or you can ask the number if it’s between two other numbers. Obviously this methods needs us to pass those two other numbers. So now we also have an example of a method that takes two arguments:
> 5.between?(1, 10)
=> true
> 5.between?(11, 20)
=> false
These methods are called predicate methods in Ruby. Not quite sure why, maybe because of the historical math context of programming.
Predicate methods that end with a question mark ?
return either true
or false
.
Strings also define some predicate methods:
> name = "Ruby Monstas"
> name.start_with?("R")
=> true
> name.start_with?("a")
=> false
Do you also think it’s kinda odd that name.start_with?("a")
reads almost like
English, but not quite? Maybe the method could have been named starts_with?
instead, right? That’s true. This is because Matz, the creator of Ruby, is not
a native English speaker, and some names sound right in Japanese when
translated literally.
Also:
> name = "Ruby Monstas"
> name.include?("by")
=> true
> name.include?("r")
=> false
When we check what methods there are defined on a number, we find some with the same name, but also different ones:
$ irb
> 1.methods.sort
=> [:*, :+, :-, :/, :between?, :even?, :odd?, :zero?]
Let’s try zero?
:
> 0.zero?
=> true
> 1.zero?
=> false
Arrays have the methods include?
, and Hashes respond to key?
:
> [1, 2].include?(1)
=> true
> [1, 2].include?(3)
=> false
> { "eins" => "one" }.key?("eins")
=> true
> { "eins" => "one" }.key?("zwei")
=> false
Oh by the way, if you’re curious why operators like *
, +
, -
and so on are
also listed here, check the chapter that explains that operators are methods,
too.
Bang Methods
All of the examples that we’ve discussed before have one thing in common:
They are questions, and do not modify the object they are called on.
For example:
name = "Ruby Monstas"
puts name.downcase
puts name
This will output:
ruby monstas
Ruby Monstas
As you can see the method downcase
has returned a new String, which is the
lowercase version of the String that the method is being called on. When we
output the original String on the next line, we can then see that it’s still
the same: The method downcase
does not modify the String.
However, there also are variants of some of these methods, which end in an
exclamation mark !
. These methods are called “bang methods”, and they usually
modify the object that they’re being called on.
Bang methods end with an exlamation mark, and often modify the object they are called on.
For example, next to the method downcase
Strings also have a method downcase!
.
Let’s try that:
name = "Ruby Monstas"
puts name.downcase!
puts name
This will output:
ruby monstas
ruby monstas
As you can see calling the method downcase!
on the second line has modified
the String itself (the object that name
refers to), and also returned the
new downcased version.
Nowadays programmers have learned that using these methods has a number of disadvantages, and usually should be avoided, unless there are very good reasons for it (usually, there are none).
Use bang methods with caution.
We mostly mention these methods because you might see them used elsewhere.
Writing Methods
A named block of code that takes input and returns output
Everything in programming is about data, and doing interesting things with it. If you think about Twitter, it essentially takes 140 characters of text (which is data) from their users and displays it to others.
You have already learned about the most important primitive (simple) types of data in Ruby: numbers, strings, true, false and nil. And we’ve had a look at the two most common data structures, Arrays and Hashes.
All of this is about the data part. Methods on the other hand are about the doing interesting things part.
In this chapter we’ll have a closer look at the anatomy of a method, and you’ll learn how to implement, and use, your own methods.
Methods define behaviour
Methods are all about defining behaviour, so that they can be applied to different bits of data in different contexts easily.
For example, there are methods that do things like: transforming a String, sorting a list, reading a CSV or Excel file, sending an email, signing in to Facebook, sending a Tweet.
Another way of putting this is: Methods are a way of assigning a name to a certain piece of code. Just like a variable allows to “look up” or refer to the object that the name was assigned to … methods allow to execute their code.
Variables name things, methods name behaviour (code).
Methods make code re-usable, by the way of packaging (“encapsulating”) code and sticking a name on it.
As you have seen Ruby comes with lots of methods predefined - written by
experienced programmers. So, unless you’re studying computer science and you’re
faced with the exercise of implementing your own, complicated sorting
algorithms for data collections, … you’ll just use the method sort
that
already comes defined for Arrays in Ruby out of the box.
Ok, let’s see what makes up a method, and how we can define our own ones.
What makes a method
There are four things that constitute a method:
- a name
- a block of code
- (optionally) accepting input
- returning output
Not all methods actually need input (so they don’t take any), and not always do we care about the output that a method returns.
Imagine a vending machine where you can chip in some money, press some buttons, and the machine will spin a few gears and wheels, and spit out the chocolate bar you were after.
If the vending machine was a method, then your money, as well as the buttons you press are the input. The way the machine internally spins certain mechanics is the block of code, the stuff it does internally. And the product that it dispenses is the return value.
Although this would be an odd thing to mention to a non-programmer, we could say that a vending machine is a way of “transforming” money and data into chocolate.
Methods are a lot like that.
Methods have a name, take some input, do something with it, and return a result.
Programmers usually don’t use the term “input” in this context. Instead we say that a method accepts a number of arguments (pieces of input). And instead of “output” we use the term return value: the thing that we get back from the method.
A method’s input is referred to as “arguments”, while its output is called a “return value”.
This will become more clear in the following chapters. Let’s define a method next.
Defining a method
So far you have seen methods that “belong” to objects, or, in other words,
are defined on objects, and can be called on objects. E.g. you have seen the
method downcase
which is defined on every string.
However, Ruby also knows methods that are not defined on any of these objects. They’re sort of “stand alone” methods.
For example, you can try this in irb
:
$ irb
> is_a?(Object)
true
We’ll use this type of methods in this chapter because we want to focus on the characteristics of methods. If you’re curious what’s up with them have a look at the bonus chapter about the top-level object. Later when you learn how to define your own classes we also look at defining methods for these.
Ok, let’s get started.
Suppose we need to define a simple method that takes a number, adds the number
2
to it, and returns the result. Here’s how we can do that:
def add_two(number)
number + 2
end
This defines a method. It does not use it, yet: You only build and place that vending machine, so it can be used (by yourself, or others) later.
Let’s walk through this method definition step by step:
Ruby will start reading the code at the top, and find the keyword def
. This
tells Ruby that we’re about to define a new method.
Methods need a name, so Ruby looks for it next, and finds the word add_two
.
Ruby then checks if we define anything to “input” to the method (remember, this is optional). She finds the parentheses, and knows that we’re about to define a list of things that can be given to the method. This list is called an argument list.
An argument list defines names for objects passed to the method, enclosed by parentheses right after the method name.
In our case the argument list has one single argument number
, which means
our method can accept one single thing (object).
The next line is the block of code that our method has (“encapsulates”). This
is also referred to as the method body. In our case that’s just one single
line because the operation that our method encapsulates is very simple. Other
methods (think of sort
, defined on Arrays) would require more code, and are
longer.
Inside the method body the arguments are known as local variables: You can see
how the code in our method body uses the variable name number
.
Finally the keyword end
tells Ruby that we’re done with the method body, and
the method definition.
All we’ve done so far is defining the method, we haven’t used it for anything, yet. We’ll do that in the next chapter.
On formatting: Note that the keywords def
and end
sit
on the same level, while the method body is indented by two spaces. Also, there
are no space before or inside the argument list, i.e. the ()
parentheses.
Using (calling) a method
Once defined we can use our method.
As programmers we usually say that we “call” a method. This means we ask Ruby to execute the code that the method body has, with some given arguments (input), if any.
Here’s how that looks:
def add_two(number)
number + 2
end
puts add_two(3)
Let’s inspect what’s happening here under the microscope. If you don’t understand each part of this just yet, don’t worry, we’ll get back to all of this.
On the first three lines Ruby defines the method add_two
as discussed above.
- On the next line Ruby will look at the bit
add_two(3)
first. She recognizes that we are referring to a method defined earlier, and this will tell her we want to call (execute, use) this method. - In order to do so she first needs to look at what’s inside the parantheses
()
so she can pass it on. She finds the3
and creates a new object (number) for it. - Now Ruby is ready to actually call (execute, use) the method, passing the
number
3
.
So Ruby now deviates from the normal flow, which just goes from top to bottom in our file. Instead she now jumps into the method body.
In this moment she now assigns the number 3
to a local variable number
before she starts executing the method body.
That’s right. This is how method arguments work:
When a method is called and objects are passed as arguments, then Ruby implicitely defines local variables with the argument names. She assigns the passed objects to the variable names that are in the argument list. These local variables are then available in the method body.
In our case we have just one argument number
. So we get one local variable
number
with the object 3
assigned (because that’s the object passed when we
called the method).
You can imagine the method body now reads like this:
number = 3
number + 2
Ok, so we’re inside the method body now:
- Ruby will now execute (evaluate, run) the method body (again, going from top
to bottom), which in our case is just a single line with the expression
number + 2
. - Because
number
is assigned3
this expression will evaluate to5
. - Since this line of code is the last line in the method body, the value
5
also is the value returned from the method call.
So Ruby now jumps back out of the method. The expression add_two(3)
has just
returned the object 5
. Imagine the last line now reads like this instead:
puts 5
And that will now print the number 5
to the screen.
Let’s have a closer look at that thing with the return value (“output”), as we just rushed over that a little.
Return values
In Ruby, a method always return exactly one single thing (an object).
The returned object can be anything, but a method can only return one thing, and it also always returns something.
Every method always returns exactly one object.
The object returned could be the object nil
, meaning “nothing”, but it still
is an object. Also, in order to return a bunch of things at once we could
return an Array that holds the things that we are interested in, but the Array
itself is just one object.
Also note that in Ruby we do not have to use the statement return
, as in
other languages. In fact, most Ruby code does not use the keyword return
at
all.
This is extremely convenient, but it is also something we need to learn:
If we don’t do anything else, then a method will return the value that was returned from the last evaluated statement. Most often, this is the last line in the method body.
This is important to understand. Please read that sentence again:
If we don’t do anything else, then a method will return the return value of the last evaluated statement.
In our example method …
def add_two(number)
number + 2
end
p add_two(3)
… the last evaluated statement is the expression number + 2
. Since in our
example number
is assigned 3
this expression returns the number 5
, and
that is why the value returned by our method also is 5
.
If, in certain cases, we do want to “return” from the method early, then we can
still do this using the return
statement. For now, you don’t need to worry
about this case.
So, let’s move on :)
Scopes
Spheres of visibility, like rooms in a house
When we talked about variables we mentioned that the most common type of variables is the “local variable”, without explaining any further why they are local. Local to what? Where? We’re finally ready to explain that more.
Since we have now talked about methods, we can also discuss another important concept: scopes.
Wikipedia says: “In programming, the scope of a name is the part of a program where the name is valid: where the name can be used to refer to something else.” (slightly modified to match our own terminology)
Names are known (or defined) in a certain scope, and unknown (or undefined) outside of this scope.
You can think about a scope as of a sphere, a bubble, or a room (with no windows, and doors closed): Certain names, like variable names, are “known” and “visible” inside of the room. Other names, known and visible in another room are not known in this room, but only in the other room.
Every time there is a method call, and the flow of execution enters the method’s body, it enters a new scope, or “room”. Things that are “local” to this method’s scope (i.e. things that are “inside” of the room), are only visible in this scope. Outside of it, they are unknown.
Undefined local variable or method
This is also a good opportunity to talk about an error message that you might see most often. E.g. you’ll see it every time when you made a typo and misspelled a variable or method name.
Consider this code:
def add_two(number)
number + 2
end
puts add_two(3)
puts number
The line puts add_two(2)
will output 5
, but the line puts number
will
then raise an error.
This is because the variable number
that is assigned the number 3
when we
call the method is a local variable. It is local to the method’s scope. It
is created when the flow of execution enters the method.
Outside of this scope, when the flow of execution has returned from the method
the method’s scope has been destroyed, and all local variables are gone. The
local variable number
is therefore not known, and Ruby raises an error saying
undefined local variable or method 'number'
.
If you think about this error message for little bit, does it make sense to you?
We have silently skipped over the fact that, in Ruby, both local variable names and method names, are written the same way: they’re just plain words. For example, here:
number = 2
puts number
number
is a local variable, and it is used in the line puts number
.
However, here:
def number
2
end
puts number
number
is the name of a method. And it can be used (called) in the exact same
way: puts number
This is because Ruby, when it executes a program, evaluates one statement after
another. And when it encounters a plain word like number
then it will first
check if, within the current scope, it knows a local variable with the same
name. If so, it will use the value that is associated to this variable. If
there’s no local variable with this name, then it will look for a method. If
there’s also no method with this name it will then raise the error message
undefined local variable or method 'number'
.
So the error message is pretty precise, but also sounds kind of convoluted. What it basically tries to say is:
You’ve used the word “number” here, and i don’t know it (in this scope). Did you mean to use a local variable? Or a method?
Does that make sense?
Back to the topic of local scopes. Let’s look at another example:
number = 1
def add_to(number)
number + 2
end
puts add_to(3)
What do you think the output will be? Will it be 3
, or 5
? Something else?
If you run the code you will see that it’s 5
.
The reason for that is that we do assign the number 1
in the outer scope to a
variable number
, but this variable is then never used: the only other line in
the outer scope is the last line puts add_to(3)
, and this line does not use
the variable number
.
Instead, when the control flow enters the method add_to
Ruby will create a
new local scope, and it will define a new local variable number
which is
assigned the number 3
that was passed to the method. This new variable
number
is local to the method’s scope, and therefore this is a different
variable than the one on the very first line, in the outer scope.
We found the following a good metaphor for scopes:
When Ruby enters a method, it’s like she enters a shiny new room in a
house. With her, she brings the objects that are passed as arguments to the
method call. In the example above she brings an object that is the number 3
.
Now, as soon as Ruby enters the method, she sticks post-it notes on the
objects, according to the argument list from the method definition. In our
example that is the name number
. So from now on, in this room, there’s a
known local variable that has a value assigned: The object (number) 3
with
the post-it note number
on it.
In our example, the outer scope and the scope of the method add_two
, are
two different rooms, and there are two different post-it notes stuck on
two different numbers, which just happen to have the same name on them.
Combining Methods
We’ve discussed how to define a method, and how to call (use) it.
What if one method is not enough? What if methods need to do more complicated things?
Easy. We can call methods from other methods.
For example, we could re-write (“re-implement”) our method add_two
using
another method add_one
, and simply call it twice:
def add_one(number)
number + 1
end
def add_two(number)
number = add_one(number)
add_one(number)
end
puts add_two(3)
This would output 5
just like our previous examples. Do you understand how it
works?
Of course, in Ruby we could also just solve this whole thing with simply using
the +
operator.
However, for the sake of the example, let’s have a look how we could add a method
that does the exact same thing as the +
operator, too:
def sum(number, other)
number + other
end
We can now use that method like so:
puts sum(3, 2)
Which, again, would output 5
.
Note that in this example our method sum
now takes two arguments, and so,
when we call (use) it, we also need to pass two numbers (i.e. add them inside
the parentheses on the last line).
Now, with this method in place we could change (“refactor”) our previous methods to use it:
def sum(number, other)
number + other
end
def add_one(number)
sum(number, 1)
end
def add_two(number)
sum(number, 2)
end
puts add_one(3)
puts add_two(3)
Again, these examples are not super realistic, as we’d probably just use the
+
operator in the first place, in practice.
However, we think this nicely demostrates how you can use one method from another … and how different methods require different numbers of arguments.
We’ll look at a more realistic example in the next chapter.
Printing things
Many of the exercises that you do while doing your first steps with Ruby basics include running a short Ruby program that outputs something to the terminal.
So far, we have mostly used the method puts
to do that.
However, there’s another method that is even more useful when we are trying to figure out what a program is doing, why it is doing it, and what a certain error might be about.
This method is the method p
. In order to understand p
better, we want to
look at another method first, which is the method inspect
.
The method inspect
is available on any object in Ruby. It returns a string
that is a representation of the object itself: a representation that is as
close as possible to the code that you use to create the object. So inspect
is useful to inspect objects, duh :)
This becomes more clear when you try it in IRB:
$ irb
> puts 5.inspect
5
> puts "A string".inspect
"A string"
> puts [1, 2, 3].inspect
[1, 2, 3]
As you can see the string returned from inspect
is exactly the same as the
Ruby code that we used to create the object. That is really convenient.
However, typing puts something.inspect
is quite a bit of work to do. That’s
12 characters to type next to the object itself!
Therefore Ruby has a method to make our lifes easier, and does this work for us.
That’s the method p
.
This method is implemented like so:
def p(object)
puts object.inspect
end
Whenever you are trying to figure out what a certain line of code does, what’s
assigned to a variable, or what a method call returns, we recommend to use p
because it tells you exactly what the thing that you are looking at is.
puts
on the other hand tries to be smart.
For example when you pass an array to puts
then it will output each of the
objects on a separate line:
$ irb
> something = [1, 2, 3]
> puts something
1
2
3
Also, the output for numbers and strings that contain numbers is exactly the
same when you use puts
:
$ irb
> puts 123
123
> puts "123"
123
From the output of puts
it often is not clear whether the object that you are
looking it is an array that contains numbers, or an array that contains
strings, or just a long string that contains linebreaks.
In short, puts
is useful when you are writing a program that is supposed to
actually output something to the screen. Like, this could be a command line
tool that you write in order to make your own life easier at your job, and that
is helpful at automating some repetitive work. Or it is useful in Ruby
programming exercises :)
On the other hand p
is useful when you are trying to understand what your
code does, e.g. when you are trying to figure out a certain error.
Writing classes
Finally, our first own class
Since in Ruby “everything is an object”, we have worked with objects quite a bit already.
We’ve created numbers, Strings, and seen objects like true
, false
, and
nil
. We have also looked at their class names by calling, e.g. "a
string".class
, and we have explored some other methods that these objects
have. We have also talked about how you can define your own methods, and how
you can call them, passing arguments as required.
That means we now have all the tools that we need to finally learn how to define and use your own classes. And this is where things suddenly become even more fun!
You can think of objects as having two things: They know stuff, and they can do something with stuff (their own stuff, as well as the stuff that gets passed to them).
Imagine you were an object that is an instance of the class Person
. Well, in
a certain way, you actually are :)
Since you are a Person
, you are able to remember your own name. And you are
able to do something with it: When asked, you can tell your name to others,
that is, you can return it to the “caller”, to whoever asked.
We’ll define this exact class in just a few chapters. However, before we do
that, we’ll first look at how to define the method add_two
to a class
Calculator
… just because we can, and because you’re already familiar with
these methods.
Defining classes
Let’s start by creating a class Calculator
, and adding some methods to it,
step by step.
In Ruby, you define a class like this:
class Calculator
end
That’s all. It’s not a very useful class, since it’s completely empty, but it’s a class.
A class is defined using the keyword class
, a name, and the
keyword end
.
Also, you see that the class has the name Calculator
, which starts with an
uppercase letter. In Ruby, this is required, and you’d get an error if you
tried to define a class calculator
.
Also, for class names that consist of several words the Ruby community has the
convention to separate these words by uppercase letters, as in
RubyStudyGroup
. This is called CamelCase, because of the humps. Whereas for
variable and method names we use underscores, and keep everything lowercase:
local_variable
and method_name
. This is called snake_case.
Class names must start with an uppercase letter, and should use CamelCase. Variable and methods names should use snake_case.
Ok, back to our class Calculator
.
Since we’ve defined a full, valid class, we can now already use it to create a new, concrete calculator instance, an object from it.
You can think about the instance as the concrete calculator object that you can hold in your hands, and use to do actual calculations. The class on the other hand is more like the idea or concept of a calculator, like the idea of it that you have when you order a calculator online.
Ok, here’s how to do create a new, concrete instance from our class:
Calculator.new
That’s right. new
is a method, and it is defined on the class itself (which,
as you might remember, is also an object, so it can have methods). This method
creates a new instance of the class, and returns it.
The method new
is defined on every class, and returns a new
instance of the class.
Cool. Let’s have a look at that object:
p Calculator.new
The output will seem a little bit weird, and technical at first:
#<Calculator:0x007fb2fbe50910>
The format #<...>
tells you that this object is not a simple thing like a
number, string, or array. Instead, it just tells you the name of the class,
Calculator
, and the internal id that Ruby has assigned to this object.
Every object has its own, unique, internal object id, and when I ran this code
on my computer, Ruby assigned the id 0x007fb2fbe50910
. If you run it,
you’ll get a different one. In practice, most of the time, you can simply ignore
this id.
Also, we can check that our new calculator instance indeed is an instance of
the class Calculator
:
$ irb
> class Calculator
> end
> calculator = Calculator.new
> calculator.class
=> Calculator
> calculator.is_a?(Calculator)
=> true
Defining instance methods
If you have read closely methods can be defined and called on objects (i.e.
instances), e.g. 1.odd?
. And you have just seen that they can also be
defined on classes, e.g. Calculator.new
.
Fairly straightforward, methods that are available on classes are called class methods, and methods that are available on instances are called instance methods.
In this chapter we want to add the “stand alone” methods that we’ve learned to
port (move) in the previous chapters Writing Methods
to our class Calculator
so that they’ll end up as instance methods.
Here’s how we can do that.
class Calculator
def sum(number, other)
number + other
end
end
That’s right. You simply move the method into the class body, so that it’s enclosed by it.
Instance methods are defined inside the class body.
Also note that the method definition is indented by one level, that is, 2
spaces. This signals that the method sum
belongs to the class Calculator
.
Everything between the line class Calculator
and the final line end
is
called the “class body”, and just like method bodies we indent them by one more
level.
On formatting: the keywords class
and end
sit on
the same level. The class body is indented by one level.
Cool.
So, how can we use the new method? That is, how can we call the method sum
on a calculator?
Before you read more, think about this a little. In theory you have all the bits and pieces to answer this question:
- You know how to create a new calculator instance (object).
- You know how to call a method on an object.
- You know how to pass arguments (extra bits of information) to the method call.
We can instantiate (“order”) a new instance from our Calculator
class, and
call the method sum
on it like so:
calculator = Calculator.new
puts calculator.sum(2, 3)
This will output 5
. Success :)
A calculator that cannot do anything but additions is pretty useless. Let’s
spice our class up by adding more operations, i.e. methods. Also, let’s rename
sum
to plus
so it matches the operator:
class Calculator
def plus(number, other)
number + other
end
def minus(number, other)
number - other
end
def multiply(number, other)
number * other
end
def divide(number, other)
number / other
end
end
Now our calculator already is slightly more useful: We can do the most basic math operations with it.
calculator = Calculator.new
puts calculator.plus(2, 3)
puts calculator.minus(2, 3)
puts calculator.multiply(2, 3)
puts calculator.divide(2, 3)
This will output:
5
-1
6
0
Whoops!
This looks all good except for the last line, right. Remember why that is? Right, dividing one integer number by another returns, again, an integer number. And these aren’t quite great for calculating divisions.
One thing we could do about this is require the users of our calculator to pass (provide) the appropriate types of numbers themselves. So it would be their own responsibility if something goes wrong with the division.
However, we could also think that a calculator should be smart enough to notice
this. So we could improve our calculator to always change the type of the
numbers passed to floating point numbers as part of the method divide
. In
other words, the method divide
would take the numbers, makes sure at least
one of them is a Float
(floating point number), and only then do the
calculation.
The method used to convert an integer to a float is to_f
. When called on a
float it will just return the float:
$ irb
> 1.to_f
1.0
> 1.0.to_f
1.0
Alright, lets use that knowledge to improve our divide
method. It will be
sufficient to call the method to_f
on one of the numbers, because that will
make sure we get a Float
back:
class Calculator
# ...
def divide(number, other)
number.to_f / other
end
end
With that in place our calculator always returns a Float
from the method
divide
:
calculator = Calculator.new
puts calculator.plus(2, 3)
puts calculator.minus(2, 3)
puts calculator.multiply(2, 3)
puts calculator.divide(2, 3)
This will output:
5
-1
6
0.666666666666
Perfect.
Let’s move on, and define a class Person
, which should be even more fun.
Initializing objects
In the moment of birth
Let’s start over, and define a new class.
Remember how we said that objects can be thought of as two things: They know stuff, and they can do things.
Let’s define a class Person
. People obviously also know things, and can do things.
Here’s how to define a shiny, new, empty class Person
:
class Person
end
Again, that’s not a very useful class, but we can instantiate it, and create an actual, concrete person instance (object) from it:
p Person.new
Now, before we add any behaviour (methods) to our class, we want to be able to give it some initial data: In our case, we want the person to know its own name.
We can do this like so:
class Person
def initialize(name)
end
end
You see that we add a method called initialize
to the class, and this method
accepts a single argument, which is called name
. At the moment, this method
is still empty. We’ll add some code to it in a bit.
The important bit to learn for you is: the method initialize
is a special
method with a special meaning in Ruby:
Whenever you call the method new
on a class, as in Person.new
, the class
will create a new instance of itself. It will then, internally, call the method
initialize
on the new object. Doing so it will simply pass all the
arguments that you passed to new
on to the method initialize
.
So we can now create a new person instance by calling …
Person.new("Ada")
… and the string "Ada"
will be passed on to our initialize
method, and
end up being assigned to the local variable name
.
The special method initialize
is called under the hood when the
object has been created by the class method new
.
Obviously, our initialize
method does not do anything with the String
passed, yet. That’s right. We’ll get to that in the next chapter.
To recap, when you call new
on the class Person
, and pass the string
"Ada"
then the method new
will create a new instance of the class, and call
initialize
on it, passing the same argument list, which in our case is the
single string "Ada"
.
When we create a new instance of a class by the way of calling the method new
on that class, we also say that we “instantiate” that object: By calling
Person.new
we instantiate a new person object.
Instance variables
An object’s own knowledge
Now that you understand how the string that we pass to the method new
ends up being passed to the new object’s initialize
method, we can start
improving initialize
, so it does something with the string, and actually
initializes our new object:
class Person
def initialize(name)
@name = name
end
end
This introduces another new concept: @name
is a new type of variable, called
an “instance variable”.
The body of the initialize
method now does nothing else but assign the value
of the local variable name
to an instance variable @name
.
You remember how we said that each method has its own local scope, which is created when the method is called, and populated with local variables from the arguments list. You have also learned that this scope is erased, and thrown away when Ruby exits the method body and returns from the method. And that local variables that are visible in one method are not visible in other methods: that’s why they are called local.
Now, the thing is: Every object also has its own scope.
An object’s scope is populated with instance variables, in the moment we assign something to them. And they are visible everywhere in the object, that is, in every method that the object has.
Instance variables live in, and are visible everywhere in the object’s scope.
You can think of the object’s scope as your own knowledge, or memories.
For example, you know your name, your email address, and your email password. You keep this knowledge around, and you can use it when you do things (such as responding to another person). Likewise, an object keeps its instance variables around, as long as the object exists.
Ok, let’s see how that works in practise.
If you create, and output an instance of our class Person
, you’ll see that
Ruby now prints out the instance variable, too:
person = Person.new("Ada")
p person
The first line creates a new instance of the class Person
, passing the string
"Ada"
, and assign this new object to the variable person
. The second line
will then print it out:
#<Person:0x007fd8947aa868 @name="Ada">
As you can see this includes the instance variable @name
with the value
"Ada"
: This specific, concrete instance of the class Person
knows their
name.
You can think of this as you, as a programmer, creating this new person, and in the moment of its creation, its birth, you also give it a name. Which kind of how it works with real people, too, isn’t it?
Hmm, well … Yeah, sort of. Anyhow, let’s move on.
Attribute readers
Asking for information
Remember how we initially said that people have the ability to remember their name, and tell it, when asked?
We’ve already implemented the first part of this. Our person instance now knows her name “Ada”.
Let’s look at the second part. You also remember that methods are either questions or commands. We want to add a method that implements answering the question “What’s your name?”.
And it is as simple as this:
class Person
def initialize(name)
@name = name
end
def name
@name
end
end
Before we discuss what this does, let’s look at how we can use our new method. We can now call the method on the person object, like this:
person = Person.new("Ada")
puts person.name
So this prints the name Ada
, and that’s what we want: we can create a new
person object, passing a name to it. Once that person has been created we are
able to ask for its name, and we’ll get the name back.
How does this work, exactly, under the microscope?
Let’s walk through it, step by step:
On the first line, the object that ends up being assigned to the variable
person
is an initialized, new instance of the classPerson
. By “intialized” we mean that the methodinitialize
has already been called, and it already has assigned the string"Ada"
to the instance variable@name
.Now this person object has the method
name
, as defined in the class definition ofPerson
above, and in the second line we call this method:person.name
.When the method
name
is called, it does nothing else but evaluate the instance variable@name
from the object scope. Because this has previously been set to"Ada"
it will return this string. And because this is the last line in the methodname
the method also will return this string.For that reason the method call
person.name
returns the string"Ada"
, which is then passed toputs
, which prints it out.
Methods that do nothing else but return a value assigned to an instance variable with the same name are very common.
In fact they are so common that there’s a word for them: they are called “attribute readers”. By “attribute” the Ruby community means an instance variable, so an attribute reader is a method that reads an instance variable.
An attribute reader returns the value of an instance variable.
Another way of looking at this is that an attribute reader is a method that “exposes” an instance variable. It makes it accessible for others. I.e. it allows you to ask for the value of an instance variable with the same name, and does nothing but return its value. Once defined others can ask this object for knowledge that otherwise would be private, and unaccessible.
In our case the attribute reader name
exposes the instance variable @name
,
so others can ask for it.
An attribute reader exposes an instance variable.
We don’t know why the community has chosen to use the term “attribute” here: It would be much less confusing to use the term “instance variable reader” instead. Maybe the simple reason is that programmers don’t like to type more than necessary, and this saves 8 characters. Who knows :)
Attribute writers
Setting information
Imagine in our application a person not only needs a name, but also a password. However, let’s also imagine that, at the time of the creation of a new person instance, this password is not yet known. (Who would give a toddler an email password anyway?)
Instead we want to be able to tell the person object about its email password later.
We can do this like so:
class Person
def initialize(name)
@name = name
end
def name
@name
end
def password=(password)
@password = password
end
end
As you can see, the method password=
does nothing else but take a single
argument (called password
) and assign the value of this local variable to the
instance variable @password
.
This method’s structure looks exactly the same as the method initialize
,
doesn’t it? Execpt that initialize
is called whenever you call new
on the
class. Our new method password=
needs to be called on the object itself,
once it has been created.
Again, because this kind of method is used so often, there’s another name for it: it’s an attribute writer. (And again, we think it should have been called an “instance variable writer” instead.)
Now, we can use the attribute writer like so:
person = Person.new("Ada")
person.password=("super secret")
p person
If you execute this, then it will print out:
#<Person:0x007fb61c1edcf8 @name="Ada", @password="super secret">
So, yeah, we can see that, after calling person.password=("super secret")
the object now has an instance variable defined, i.e., the person now knows
their password, too.
An attribute writer allows setting an instance variable.
That method call looks a little odd though, doesn’t it?
Remember what we’ve said above about the syntax sugar that Ruby adds for the
assignment operator =
?
Exactly the same works for attribute writers, that is, methods that end with an
equals sign =
.
That’s right. So we can also write this instead:
person = Person.new("Ada")
person.password = "super secret"
And this reads just so much better, doesn’t it?
Just remember that, under the hood, when running your code, Ruby translates the
line person.password = "something"
to person.password=("something")
, and
this simply calls the method password=
, passing the value on the right hand
side as an argument: it’s just another method :)
We think this is pretty cool.
State and behaviour
Data and methods
Lets have another look our class definition for Person
:
class Person
def initialize(name)
@name = name
end
def name
@name
end
def password=(password)
@password = password
end
end
Do you notice something?
Our class demonstrates an important thing about objects:
There’s a way to ask a person for their name
, but no way to set a new name.
On the other hand there’s a way to set a new password to the person, but no way
to ask for it.
If you think about it, that makes sense, doesn’t it?
If you join our Ruby beginners study group for the first time, and we ask you for your name, you’ll happily tell it. But if we ask for your Gmail password, you will probably just laugh at us, or stare at us, or show some other error message. ;) In any case, you won’t tell us your email password, because that’s private information.
The same is also true for objects.
Every object has its own object scope that might hold a bunch of instance
variables. These are private to the object. Our person object knows their
password, once it has been given to them. But from then on, they won’t tell
anyone the password, because there’s no method for that. On the other hand,
there’s a method name
, which is an attribute reader, so we can ask our person
object for their name. But there’s no way for others to give a new name to the
person, because there’s no method for that, no attribute writer name=
.
This concept is called encapsulation, and it is one of the main motivations behind the whole paradigm of object-oriented programing:
We can say that an object encapsulates state (data, knowledge), which is private to the object, and exposes behaviour by the way of having publicly accessible methods.
Objects have state (instance variables) and behaviour (methods).
So, we have now created our first little class, and it’s one that you could actually see in real applications.
Interacting Objects
We’re now able to create our own objects. However, they don’t do a whole lot, yet, right? Why not create two people, and let them greet each other.
Let’s see.
Here’s what we’d like to achieve:
class Person
def initialize(name)
@name = name
end
def name
@name
end
end
person = Person.new("Anja")
friend = Person.new("Carla")
person.greet(friend)
We’d like this to print out the following for starters:
Hi Carla!
If we run the code above we’ll get an error message that tells us what to do next:
NoMethodError: undefined method `greet' for #<Person:0x007fbb5e9c88c8 @name="Anja">
Right, we need to define a method greet
. Let’s do that:
class Person
# methods from above ...
def greet
end
end
If we run this code, we’ll now get a new error message. Great, that’s progress:
ArgumentError: wrong number of arguments (1 for 0)
Right, how would one person greet another without knowing who that other person is? So we need to make our method accept an argument:
class Person
# methods from above ...
def greet(other)
end
end
If we run this we won’t see an error message any more. Yay! However, it also does not print anything so far - our method does not do anything, yet.
Alright, let’s add some actual behaviour, and print out “Hi!” for starters.
class Person
# methods from above ...
def greet(other)
puts "Hi!"
end
end
What happens when you run that? It works, right. This should output Hi!
to the screen.
However, how can Anja greet Carla, specifically? How can any person greet any other person? Well, the method knows the person to greet (she has been passed as an argument to the method) so she can ask her for her name:
class Person
# methods from above ...
def greet(other)
puts "Hi " + other.name + "!"
end
end
Does this make sense?
Let’s have another look at the full code:
class Person
def initialize(name)
@name = name
end
def name
@name
end
def greet(other)
puts "Hi " + other.name + "!"
end
end
person = Person.new("Anja")
friend = Person.new("Carla")
person.greet(friend)
So we instantiate two Person
objects, and assign them two variables person
and friend
. Then we call the method greet
on the first one (Anja), and
pass the second one (friend
, which is Carla) as an argument.
Now Ruby jumps into the method body of the method greet
, and assigns the
local variable name other
to the Person
instance passed (i.e. Carla).
It then asks the Person
instance other
for their name (which will return
the String "Carla"
, and concatenates it to (glues it together with) two other
strings "Hi "
and "!"
. So this results in a new String Hi Carla!
which it
then passes to puts
.
Ok, let’s spice that up a little, and let Anja add her own name to this String, too:
class Person
# methods from above ...
def greet(other)
puts "Hi " + other.name + "! My name is " + name + "."
end
end
Nice. This now outputs:
Hi Carla! My name is Anja.
Remember how you could call the method add_one
from another method add_two
before? The same works here, too: We can
call the person’s own method name
by just using it, since this method is
defined on the same class, and thus every instance of Person
. We’ll look
at this a little more in the next chapter.
For now let’s add another line at the end, and let Carla greet Anja back.
Again, here’s the full code:
class Person
def initialize(name)
@name = name
end
def name
@name
end
def greet(other)
puts "Hi " + other.name + "! My name is " + name + "."
end
end
person = Person.new("Anja")
friend = Person.new("Carla")
person.greet(friend)
friend.greet(person)
And this outputs:
Hi Carla! My name is Anja.
Hi Anja! My name is Carla.
What do you think?
Of course this is still quite a simplistic example. It already shows how you can “model” a certain “real-world” concept in terms of Ruby code, instantiate it, and let it interact with another thing.
By the way if you’d like to see how to make the method greet
more pretty,
and a little bit easier to read, check out the chapter on
String interpolation.
Object Scope and Self
Remember how we said that when Ruby finds a method call she then deviates from the normal control flow which goes from top to bottom? Instead she jumps into the method body.
We also said that this method body has its own scope, like a shiny, new room where local variables from other scopes are not visible. Instead it has its own local variables, some of which might be defined via the method’s arguments.
And we also said that inside any object’s method all instance variables of this object, and all other methods of this object are also visible.
We also may have a look at that mysterious top-level object that Ruby enters when she starts executing a program, or IRB.
We are now finally ready to put all these things together a little more, and
introduce a new keyword: self
.
The Object Scope
In fact, in Ruby, there are even more scopes than just the local method’s scope:
There’s the method’s local scope, which holds all local variables. And there’s the object’s scope which holds all instance variables and method names.
When Ruby’s control flow jumps into a method then both of these scopes are visible at the same time. For any given name Ruby will first check the local scope, and then the object scope.
In reality this most importantly means that from any method on an object, you can access:
- all local variables
- all instance variables
- all of the object’s methods
It also means that one can overwrite, sometimes accidentally, method names with variable names. Remember they read just the same in Ruby? While this is a really cool feature, it also means we need to be aware of it.
Consider this code:
class Person
# ...
def name
@name
end
def greet(other)
name = other.name
puts "Hi " + name + "! My name is " + name + "."
end
end
As you can see this defines a new local variable name
. The code is very
similar to what we had before, except
that we store the other
person’s name to a local variable name
first,
and then use this variable on the next line when we put together our String.
This breaks our code, of course, in the sense that we don’t get the expected greeting any more. Instead it contains the same name twice:
Hi Carla! My name is Carla.
That is because, on the last line of the method greet
, when Ruby looks at the
word (identifier) name
she first checks the local scope of the method, and
finds a local variable defined, so she uses it. Only if there was no local
variable defined she would check the object’s scope, and find the method with
the same name name
, and call it.
When she finds an identifier, Ruby looks for a local variable first, and then for a method.
Luckily, there’s a way to still access the object’s scope:
Self
Every object knows itself, in every method, by the way of calling self
.
This is a special keyword in Ruby, that means just that: The object itself.
Let’s try that, and output self
. In order to do that we need to add it
somewhere inside the object. Any method would be good for that, but let’s
just use the initialize
method:
class Person
def initialize(name)
@name = name
p self
end
end
person = Person.new("Anja")
p person
This should output something like this:
#<Person:0x007f9994972428 @name="Anja">
#<Person:0x007f9994972428 @name="Anja">
As you can see we output the same object twice. Once in the initialize
method
using p self
, and once in the outer scope using p person
. You can also see
that the cryptic looking object id is the same for both instances. So we can
know it’s indeed the very same object.
Inside any method the object can be referred to using the keyword self
.
So we can fix our code from above like this:
class Person
def name
@name
end
def greet(other)
name = other.name
puts "Hi " + name + "! My name is " + self.name + "."
end
end
Now we call the method name
on two different objects again. When Ruby sees
self
she knows that we’re referring to the person object, and she calls the
method name
on it.
This fixes our greeting:
Hi Carla! My name is Anja.
Keywords
Why do we keep saying that self
is a keyword? Well, because is not a method.
It’s a “special thing” in Ruby. For example this means that the following will
raise an error:
person = Person.new("Anja")
p person.self
This raises the error:
NoMethodError: undefined method `self' for #<Person:0x007f9994972428 @name="Anja">
It’s not a method, it’s a special thing.
Other keywords that also aren’t methods (or objects, or classes) are, for
example, def
, class
, and end
. You’ll also learn about do
, if
, elsif
and else
a little later.
Keywords are words that have a special meaning in Ruby, such as
class
, def
, end
, and self
.
Blocks
Like methods, but without a name
Blocks are one of the things programmers absolutely love about Ruby. They are an extremely powerful feature that allows us to write very flexible code. At the same time they read very well, and they are used all over the place.
So, what is a block?
A block, essentially, is the same thing as a method, except it does not have a name, and does not belong to an object.
I.e. a block is an anonymous piece of code, it can accept input in form of arguments (if it needs any), and it will return a value, but it does not have a name.
Moreover, blocks can only be created by the way of passing them to a method when the method is called.
A block is a piece of code that accepts arguments, and returns a value. A block is always passed to a method call.
Let’s jump right in:
5.times do
puts "Oh, hello from inside a block!"
end
As you can see times
is a method that is defined on numbers: 5.times
calls
the method times
on the number 5
.
Now, when this method is called the only thing passed is a block: that is the
anonymous piece of code between do
and end
. There are no objects passed as
arguments to the method times
, instead it just passes a block.
The method times
is implemented in such a way that it simply calls (executes)
the block 5 times, and thus, when you run the code, it will print out the
message "Oh, hello from inside a block!"
5 times.
The code almost reads like an English sentence Five times do output this message, right? It does, and that’s one of the reasons why Rubyists love using blocks.
One of the things that seem rather hard to grasp about blocks is that
- First, they are anonymous chunks of code.
- Second, they are passed to methods just like other objects.
- And third, they still can be called (just like methods), from inside the method that it was passed to.
Does that make sense?
Imagine you are the object that represents the number 5
. You are a number and
you do know your own value.
Now I hand you a piece of paper saying: Please print the following on the
screen: "Oh, hello!"
, and I ask you to execute this instruction as many times
as the value that you know.
You’d go ahead and follow the instructions on the paper, and thus print out the
message. You repeat this 5 times, because 5
is the value that you know.
This is pretty much how the method times
on numbers works, and how blocks
work: times
takes the block (the instructions), and runs it as many times
as the value of the number.
To summarize: Methods can not only accept input in the form of objects passed as arguments. They can also accept this one special piece of input, which is an anonymous block of code. And they can then call (execute) this block of code in order to do useful things with it.
Let’s look at some other aspects of how blocks work next.
Alternative block syntaxes
Next to the syntax shown before, using do
and end
, Ruby comes with an
alternative syntax, which uses curly braces for defining a block.
These two statements do exactly the same:
5.times do
puts "Oh, hello!"
end
5.times { puts "hello!" }
Both statements define a block, which is passed to the method times
. And
both blocks contain a single line of code.
Blocks can be defined enclosing code in do
and end
,
or curly braces {}
.
So, when do you use one or the other syntax?
In the Ruby community there’s the convention to use curly braces if you have a single line block and it fits nicely on the same line (as, in our example, it does).
Whenever you need to have more than one line in your block, then you use the
syntax using do
and end
. Sometimes people also use the do
and end
syntax when they feel it makes the code more readable.
Use curly braces {}
for blocks, when the code fits on one line.
Block arguments
Blocks make a lot of sense on methods that are defined on collections like arrays and hashes.
Let’s have a look at some examples with arrays.
In our previous example that used the method times
our block did not accept
an argument. A block that accepts an argument looks like this:
[1, 2, 3, 4, 5].each do |number|
puts "#{number} was passed to the block"
end
And, again, this is the same as:
[1, 2, 3, 4, 5].each { |number| puts "#{number} was passed to the block" }
It is unknown to us why Matz has chosen to not enclose the argument list of a block with round parentheses just like method argument lists. Instead, Ruby wants us to use vertical bars (we call them “pipes”).
So, for blocks, do |number|
is the same that is def add_two (number)
for a
method definition, except that the method wants a name while a block is
anonymous: |number|
and (number)
both are argument lists. The first one
is used for blocks, the second one for methods.
Block arguments are listed between pipes |
, instead of parentheses.
Now, when you run the code example above, you’ll see the message printed out for each of the numbers contained in the array.
Does that make sense? Again, our code almost reads like an English sentence:
With this array for each of its elements, naming it number
, output the
following message.
The method each
is defined on arrays, and it does just this:
It takes each of the elements in the array and calls the block, passing the element as an argument. The block can then do whatever you want it to do with the element. In our case we interpolate it into a string and print it out to the screen.
Block return values
Remember that we said a block returns a value just like methods do? So far, in our two examples above, we did not do anything with the return values of the block.
Here’s an example that does that:
p [1, 2, 3, 4, 5].collect { |number| number + 1 }
This will take the array of numbers, and transform it into another array.
It does this by calling the method collect
on the original array, which calls
the given block for each of the elements, and collects each of the return
values returned by the block. The resulting array is then returned by the
method collect
, and printed to the screen.
In other words, the method collect
uses the block as a transformer. It takes
each element of the array, passes it to the block in order to transform it
to something else, and then keeps all the transformed values in a new array
that the method collect
then eventually returns.
Note that the method collect
has an alias, which is map
. These are exactly
the same methods. Many programmers prefer map
over collect
because it is
shorter, and also more commonly used in other languages. However, in our study
groups we use collect
more often, because it simply expresses more clearly
what the method does.
Use the method collect
to transform an array into another array.
Here’s another example that uses the return value of the block, can you guess what it does?
p [1, 2, 3, 4, 5].select { |number| number.odd? }
In this case, the method select
uses the block in a different way: as a
filter, or criterion, to select values out of the array, and then return a new
array with the selected values.
Let’s walk through this step by step, under the microscope:
- We create an array
[1, 2, 3, 4, 5]
. - We then call the method
select
on it, and pass our block as a filter criterion. - Now, the method
select
calls our block for each of the numbers. - It first calls the block passing the number
1
. - We now are inside the block, and a local variable
number
is assigned the object that was passed as an argument, which is the number1
. - Inside our block we now call the method
odd?
on this number, and of course, because1
is odd, this will returntrue
. - Since this is the only, and thus, last statement in the body of our block,
our block also returns
true
to the methodselect
.select
therefore will keep (“select”) the number1
. - It then calls the block again, this time passing the number
2
. Of course, because this is not an odd number, the methododd?
and therfore our block will returnfalse
back the the methodselect
. Therefore it discards this element. - It keeps doing this for each of the remaining elements in the array, and
eventually has this array:
[1, 3, 5]
- The method
select
then returns this array and Ruby will pass it to the methodp
, which prints the array out to the screen.
Thus, the code above prints out [1, 3, 5]
.
Use the method select
to select a new array with values that match a criteria defined by the block.
Here’s another example of a method that uses the block as a criterion:
p [1, 2, 3, 4, 5].detect { |number| number.even? }
Again, detect
will pass each of the elements of the array to the block, one
by one, and check the return value of the block. However, as soon as the block
returns something truthy (something that is “equivalent to true”), the method
detect
will return the current object itself. Therefore, this will print out
2
: the first number in the array that is even.
Inversion of control
In Ruby there are a lot more methods that accept blocks, and they do very different things. However, they have one thing in common:
By accepting a block, from you as a programmer, the method can pass control to you.
This design is an example for the principle of inversion of control, and it’s the real reason why Rubyists love blocks so much.
What does that mean?
In short it means that Matz, the creator of Ruby, put a tool in place that can be used to allow methods to pass control to its users (i.e. you as a programmer).
“Control” in this context refers to the question who gets to decide how things work.
In older languages, where there was no such tool, people either had to implement lots of very specific methods, and guess what users might need in the future. Or they’d decide to just not implement any of these methods at all.
For example, in Ruby, we don’t have to define lots of methods like select_odd
,
select_even
, select_lesser_than
, select_greater_than
and so on, … where
each of these methods would be useful for one very specific usecase.
Instead, the class Array
only has to implement one single, very generic
method for arrays: select
. Since Ruby has blocks, the method can allow you
(as a programmer) to specify the criterion yourself: by passing a piece of
code, in the form of block to the method.
That way Ruby lets you, as a programmer, take over control, and specify what is used as a criterion to select elements.
One of the reasons we mention this is because we think this is a nice example of a pretty abstract principle applied to software design. There are lots of other principles like these, and they’ll make more and more sense to you over time. Programming languages and code, from this perspective, is a subject of design, and thus art, as well as social and cultural questions … much rather than strictly logical or technical ones.
Iterators
Methods on arrays and hashes that take a block are also called iterators.
We say they iterate over the array, meaning that these methods take each element of the array and do something with it.
In Ruby iterators are “chainable”, adding functionality on top of each other.
That means that, if you do not pass a block to an iterator method, such as
each
, collect
, select
, then you’ll get an iterator object back. You can
then call more methods on these iterator objects, and finally pass a block.
Like so:
numbers = [1, 2, 3, 4, 5].collect.with_index do |number, index|
number + index
end
p numbers
This will print out:
[1, 3, 5, 7, 9]
What’s going on here?
The method with_index
can be called on any iterator object. All it does is
pass the index of the element within the array to the block, as a second block
argument, in addition to the element itself.
Inside of the block we can then use it, and add the index to the number itself.
So for the first iteration it will call the block with 1
and 0
, since 0
is
the first “position”, that is, index. It therefore returns 1
. For the second
iteration it calls the block with 2
and 1
, and returns 3
, and so on.
Therefore the method call eventually returns the array [1, 3, 5, 7, 9]
.
Iterators in Ruby are chainable.
Conditionals
If this is true, then do that. Otherwise do something else.
Often we want to check for a certain condition, and then based on it, do either one thing or another. Or we want to do something only if a condition is true.
All practical programming languages have some way of expressing this, and in Ruby it looks like so:
number = 5
if number.between?(1, 10)
puts "The number is between 1 and 10"
elsif number.between?(11, 20)
puts "The number is between 11 and 20"
else
puts "The number is bigger than 20"
end
You can probably guess what it does, and how it works, can’t you?
Let’s walk through it one by one:
If you run this code it will print out
The number is between 1 and 10
, because the number assigned to the variablenumber
on the first line is5
, and for this number the method callnumber.between?(1, 10)
returnstrue
. Ruby will therefore execute the code in theif
branch: Theif
branch is the block of code that comes after the line with theif
statement, and that is indented by two spaces. Once it is done executing theif
branch Ruby will simply ignore the rest of the statement.If you change the number
5
on the first line to15
, and run the code again, then it will print outThe number is between 11 and 20
. In this case Ruby will, again, first check the first conditionnumber.between?(1, 10)
, but this time this method call returnsfalse
. Therefore, Ruby will ignore theif
branch, and check the next condition on theelsif
line:number.between?(11, 20)
. Now, this method call returns true, because5
is between11
and20
. Ruby will therefore execute theelsif
branch, and print out this message. Again, once it is done executing theelsif
branch Ruby will ignore the rest of the statement.If you now change the number
15
to25
, and run the code again, then it will print outThe number is bigger than 20
. Again, Ruby will first check the first condition, and find it returnsfalse
. It will check the second condition, which now also returnsfalse
. Therefore Ruby will then execute theelse
branch, and print out that message.
The elsif
and else
statements and branches are optional: you don’t need to
have them. You can have an if
statement without elsif
or else
branches,
an if
statement only with an else
, or you could have an if
statement with
one or more elsif
statements. Or combine all of them together:
- There must be an
if
statement and branch. - There can be many
elsif
statements and branches. - There can be one
else
statement and branch.
Nothingness and the truth
Now is a good time to talk about the concepts of nothingness and truth in Ruby.
Nil
We have briefly mentioned that in Ruby there is an object that represents
“nothing”: the object nil
.
That’s right. “Nothing” is a thing in Ruby (as well as in many other languages), albeit a very special one. We could ramble on the philosophical implications of this, but instead we’ll just look at how this is used in practice:
Remember how we can receive a value associated with a key from a hash?
dictionary = { :one => "eins", :two => "zwei", :three => "drei" }
p dictionary[:one]
This will print out "eins"
. However, what happens if we try to receive
the value for a key that has not been defined on the hash?
dictionary = { :one => "eins", :two => "zwei", :three => "drei" }
p dictionary[:four]
This will print out nil
. Remember that every method call always will return
some value? In cases where there’s nothing to return, it will return nil
,
which represents nothing :)
In Ruby, nil
, nothing, is something else than, for example, 0
, which
represents something. An empty string ""
, an empty array []
, or empty
Hash {}
also all represent something. So they’re not nil
.
True and false
We have also discussed that in order to represent truth, and the opposite of
it, Ruby also knows the values true
and false
.
You have seen them when we tried some of the methods on numbers, strings and
arrays, like 3.odd?
, "a string".start_with?("a")
, or [1, 2, 3].include?(2)
.
These so called predicate methods always return either true
or false
.
Predicate methods end with a question mark, and return true
or
false
.
The objects nil
, true
, and false
also have classes, and you can check
that in IRB yourself: nil.class
, true.class
and false.class
.
For a reason that we don’t know Matz (the creator of Ruby) has decided to call
these classes NilClass
, TrueClass
and FalseClass
, instead of just Nil
,
True
and False
. If you ever meet him at a conference you can ask him :)
Truthiness and falsiness
Now, when we talked about if
statements we used methods that actually return
true
and false
values, like the odd?
method on numbers does. Comparison
operators like ==
, <
and >
also return true
and false
, as in:
number = 3
if number >= 5
puts "The number #{number} is greater than 5, or equal to 5"
else
puts "The number #{number} is less than 5"
end
However, what happens when we use methods as conditions that do not return
true
or false
, but something else, for example a string, number, or nil
?
For example, consider the following:
dictionary = { :one => "eins", :two => "zwei", :three => "drei" }
key = :four
if dictionary[key]
puts "The dictionary defines a value for the key #{key}."
else
puts "The dictionary does not define a value for the key #{key}."
end
As we saw before dictionary[:four]
will return nil
because this key is
not defined. Is nil
equivalent to true
or false
? Or will this raise
an error?
If you run the code above then you see that Ruby will execute the else
branch. That means Ruby actually considers nil
to be equivalent to not
true
, that is, false
.
Now, lets use a key that actually is defined:
dictionary = { :one => "eins", :two => "zwei", :three => "drei" }
key = :one
if dictionary[key]
puts "The dictionary defines a value for the key #{key}."
else
puts "The dictionary does not define a value for the key #{key}."
end
As you saw before dictionary[:one]
will return "eins"
, because that’s the
value associated with the key :one
. Again, do you think "eins"
is
equivalent to true
or false
? Or will this raise an error instead?
If you run the code then you’ll see that Ruby now executes the if
branch, and
considers the condition (i.e. the string "eins"
) to be equivalent to true
.
Think about this for second: Ruby considers everything to be equivalent to
true
that is not false
or nil
. Or put the other way around, Ruby only
considers nil
to be equivalent to false
.
Because “is equivalent to true” or “is equivalent to false” is quite a clunky
thing to repeat so often the programming community has come up with terms for
this: truthiness and falsiness. So we can say that the string "eins"
is
truthy. And nil
is the only other thing that is falsy in Ruby, except
false
itself.
To sum this up, if
and unless
look at the truthiness of a the value
that is returned by the condition. Everything except false
and nil
is
truthy, including strings, numbers, arrays, hashes, and every other object.
This also includes the number 0
, empty strings ""
, arrays []
and hashes
{}
. These aren’t nothing (nil
), instead they’re something, and therefore,
in Ruby, truthy.
Here’s a pattern that you might see used when you look at other people’s code:
dictionary = { :one => "eins", :two => "zwei", :three => "drei" }
key = :one
translation = dictionary[key]
if translation
puts "The translation for #{key} is: #{translation}."
else
puts "The dictionary does not define a translation for the key #{key}."
end
If you read this out loud, does it make sense to you?
The code first looks up the translation from the dictionary
hash. And then it
says If there’s a translation, then use it. Otherwise do something else.
Ruby’s concept of truthiness allows us to write our code in this concise way that almost reads like English. And that’s one reason why many people love Ruby so much.
Operators
We’ve already used some arithmetic operators (+
and *
) above. You have
also learned that operators, under the hood, are just methods. Ruby just
adds a little bit of syntax sugar on top of these methods, so they’re sweeter
to read, and write.
There also are operators for comparing things, for logical calculations, and other operations. Let talk about some more of them.
Arithmetical operators
For numbers, the operators +
and *
obviously mean the mathematical
operations of adding and multiplying two numbers. Of course there are other
arithmetical operators. Here’s a full list:
+
- addition-
- subtraction*
- multiplication/
- division**
- exponentiation%
- modulus (the rest of a division, e.g.5 % 2
returns1
)
However, some of these methods are also defined on other objects, like strings and arrays.
Try it yourself in IRB:
$ irb
> "<3" + "!"
=> "<3!"
> "<3" * 3
=> "<3<3<3"
As you can see, these operators mean something different for strings. But they also make sense, don’t they?
Adding one string to another just means that they will be concatenated into one longer string. And multiplying a string by a number means repeating it as many times.
The same works for arrays:
$ irb
> [1, 2] + [3, 4]
=> [1, 2, 3, 4]
> [1, 2] * 3
=> [1, 2, 1, 2, 1, 2]
Again, adding two arrays means combining them into one big array. And multiplying an array with a number means getting a big array with the original elements repeated.
Logical operators
Logical operators are also, maybe more commonly, called boolean operators.
The term “boolean” originates from the book “The Mathemetical Analysis of Logic” written by George Boole in 1847. Boolean logic has been fundamental in the development of computers, and programming, since at their core, computers are all about processing whether or not there is current flow: on vs off (true vs false).
If you are curious, feel encouraged to google and read up on this online, but
for now, we can simply look at the 3 fundamental boolean operators and
what they do: and
, or
, and not
.
The operator and
returns true
if, and only if, both values also are true
.
So, only the expression true and true
is also true
. All of the expressions
true and false
, false and true
, false and false
evaluate to false
.
If you think about this, and come up with English sentences, then this will make a lot of sense: At the restaurant I’ll have a tomato soup IF it is vegan AND they still have some.
The operator or
on the other hand returns true
if at least one of the
values is true
. So, only if both values are false
, the operator returns
false
.
That’s why it is logically correct to answer the question Would you like tea or coffee for breakfeast? with Yes, please. IF you’d like either tea, or coffee, or both. You’d only say Hell, no! if you’d like an orange juice instead :)
The operator not
simply returns the negated, opposite value. not true
returns false
, and not false
returns true
. Therefore, the following
lines of code are the same:
puts "Always true" if not false
puts "Always true" unless false
Each of these three operators comes in two versions:
and
and&&
or
and||
not
and!
The difference between them has to do with what is called “operator precedence”.
From math you know that 1 + 2 * 3
evaluates to 7
, not 9
. This is
because the multiplication *
operator binds stronger, and precedes the
addition operator +
. In other words 1 + 2 * 3
is same as 1 + (2 * 3)
, and
not the same as (1 + 2) * 3
.
In Ruby, the operators &&
, ||
, and !
bind stronger than, and thus precede
their fellows and
, or
, and not
.
Comparison operators
In order to compare things Ruby has a bunch of comparison operators.
The operator ==
returns true if both objects can be considered the same. For
example 1 == 1 * 1
will return true
, because the numbers on both sides
represent the same value. The expression "A" == "A"
also returns true
because both strings have the same value.
Likewise, two arrays are equivalent when they contain the same elements, in the
same order. For example [1, 2] == [1, 2]
will return true
, but [1, 2] ==
[2, 3]
and [1, 2] == [2, 1]
both will return false
.
Note that we say “considered the same” and “equivalent” because technically the
two objects do not have to be (and most often, as in our examples) are not the
same objects. E.g. while evaluating the expression "A" == "A"
Ruby will
actually create two different string objects which both contain a single
character A
.
In practice this is almost always what you want. For the rare case when you
actually need to check if two objects are the same object there’s the method
equal?
. E.g., "A".equal?("A")
returns false
.
Other comparison operators are: less than <
, less than or equal <=
, greater than >
,
and greater than or equal >=
. They also work on numbers and strings, in the way
you’ll expect it. Open IRB and try a few combinations on numbers and strings.
Comparison operators most often are used in order to formulate conditions
in if
statements. Like so:
number = 20
puts "#{number} is greater than 10." if number > 10
The most funny operator in Ruby is <=>
, because it’s called the spaceship
operator. No kidding :) It is rather rarely used, and it is useful for
implementing custom ways of sorting things.
Operators are methods
As you have seen a number has methods named like the arithmetic operators +
,
-
, *
, and /
. That’s right! Interesting.
If you think about this, it makes sense: If everything is an object then
numbers are objects. If “doing things” means operating with methods by the way
of calling them, then what would +
be? A method of course.
But if we call methods on objects using that dot .
notation, then where are
the dots in 2 + 3 * 4
?
The trick is: Ruby adds them for you, silently. If you write the following code:
number = 2 + 3 * 4
Then Ruby will translate this to the following:
number = 2.+(3.*(4))
Fun, isn’t it? These operators are all methods on numbers, and they can be
called just like any other method. (The same is true for lots of other
operators, as you can see in IRB, when you run 1.methods.sort
.) The code
above is valid Ruby code, and both lines do exactly the same.
Ruby just adds a little bit of syntax in order to make it easier to read and
write for us: It allows us to write number = 2 + 3 * 4
instead of number =
2.+(3.*(4))
, which is a pretty nasty thing to type.
This is something called “syntax sugar”, because it makes the language more sweet (no kidding).
By the way, this works the same way for other things too.
For example, you have learned about the array and hash syntax that uses square
brackets []
for reading and writing.
Ruby translates this code:
array = [1, 2, 3]
array[3] = 4
puts array[3]
hash = { :one => 'eins', :two => 'zwei' }
hash[:three] = 'drei'
puts hash[:three]
to these method calls:
array = [1, 2, 3]
array.[]=(3, 4)
puts(array.[](3))
hash = { :one => 'eins', :two => 'zwei' }
hash.[]=(:three, 'drei')
puts(hash.[](:three))
Knowing this can be useful when you want to write classes that look and feel similar to arrays or hashes, but behave differently.
Bonus Chapters
String interpolation
As mentioned above you can stick strings together by using the +
operator.
Consider the following code:
name = "Ada"
puts "Hello, " + name + "!"
This, of course, will output the message Hello, Ada!
.
Glueing strings together like this works, and you can do it. However, there is
another method of accomplishing the same, and it is widely used, and usually
preferred over concatenating strings with +
.
This method is called “string interpolation”, and this is how it looks:
name = "Ada"
puts "Hello, #{name}!"
Using this syntax everything between the opening #{
and closing }
bits is
evaluated as Ruby code, and the result of this evaluation will be embedded
into the string surrounding it.
In other words, when Ruby finds #{name}
in this string, then it will
evaluate the piece of Ruby code name
. It finds that this is a variable, so it
returns the value of the variable, which is the string "Ada"
. So it embeds it
into the surrounding string "Hello, #{name}!"
, by replacing #{name}
.
Now we can also finally explain the difference between strings created with single and double quotes:
String interpolation only works with double quotes.
That means that:
puts "Interpolation works in double quoted strings: #{1 + 2}."
puts 'And it does not work in single quoted strings: #{1 + 2}.'
will print out:
Interpolation works in double quoted strings: 3.
And it does not work in single quoted strings: #{1 + 2}.
If you type the code above in your editor, and syntax highlighting for Ruby code is used, it should highlight the code in the double quoted string, so it gives you a visual clue about the interpolation.
So, why do people prefer string interpolation?
First of all, again, it’s slightly fewer letters to type. In our example, that’s just 5 characters, no big deal. However, consider a longer string, which is constructed using three, four, or more variables. Now this extra space quickly adds up, and things wouldn’t fit nicely on a single line anymore.
Also, many people find that the syntax reads a bit better. There’s a little bit less clutter, making it a little bit easier to see what’s going on.
One other, albeit pretty negligible reason is, that string interpolation actually uses less resources:
The code
"Hello, #{name}!"
creates one single new string object, and then embeds the existing string"Ada"
into it.The code
"Hello, " + name + "!"
on the other hand creates 3 new string objects: first it creates the string"!"
, and passes it to the method+
on the existing string"Ada"
. The operator+
returns a new string, which now is"Ada!"
. Now this string is passed to the method+
on"Hello, "
, which again, creates a new string,"Hello, Ada!"
.
So, string concatenation creates 2 more string objects even in our simple
example. These intermediate objects are immediately discarded, because they’re
not used any more. We’re only interested in the final result "Hello, Ada!"
.
We recommend you get used to using string interpolation, just because this is what most developers use.
Escape sequences
There’s one other little difference between single and double-quoted string that we should mention while we’re at it.
In programming, strings can not only contain text as we normally think about it. They can also contain control characters. Control characters are also called “non-printing” characters, even though they can have visual effects.
Control characters can represent all sorts of things, such as removing the last character (“backspace”), the next one (“delete”), or even causing an auditable alert (“bell”).
The one most typically used in Ruby programs is the “newline” character.
Because there’s no way to represent a “newline” character using any of the keys on your keyboard programmers have come up with the idea of escape sequences: An escape sequence is a code that consists of a backslash and another character, and this combination is used in place of control characters.
For example, \n
is the escape sequence that stands for the “newline”
character.
So this code:
puts "one\ntwo\nthree"
Will print out:
one
two
three
Now, escape sequences also only work in double quoted strings. If you try to use them in a single quoted string like so:
puts 'one\ntwo\nthree'
then that will print out the string literally:
one\ntwo\nthree
Which means that escape sequences are not replaced in single quoted strings.
Top-level object
So, we’ve learned that objects come with lots of methods attached to them. And we’ve seen how we can use them to do interesting things with the object, by the way of calling these methods.
Now, if you pop into irb
, or if you write the following code into an otherwise
empty Ruby file, this works:
$ irb
> is_a?(Object)
true
> methods
[:to_s, :inspect, ... ]
So, as you can see we can use some methods in Ruby without even defining an
object at all: They’re already there. And the method is_a?
tells us that this
already is an object … whatever “this” is.
What’s up with that?
This is something that pretty much blows some people’s minds, confuses the heck out of others, while yet others don’t even notice or think about it at all. It is also something, that, when you ask around on your local Ruby meetup, very few people can explain in a satisfying, and clear way. And neither do we, yet.
Let’s still try.
Whenever you open IRB and it presents that prompt to you, or when you create a new, empty Ruby file … Ruby will not only execute your code: Before she does she first creates an empty, anonymous Object, and kind of places you inside of it.
This empty Object kind of seems invisible, and it seems that those methods that we can define in this scope (space) were somehow “standing alone”. In fact they aren’t. They’re defined on this empty, anonymous Object that we usually don’t see.
This Object often is referred to as the top-level scope in Ruby.
The top-level scope is an empty, anonymous object. All Ruby code starts in here.
Confusing? Yeah. Don’t worry. This is something many Ruby programmers do and use everyday without ever asking the question why it works, exactly.
Hopefully some of this will become more clear once you’ve written your own methods and classes.
Lots of other methods
If you look at the methods that are defined on strings and arrays (e.g. run
[].methods.sort
or {}.methods.sort
in IRB), then you’ll find quite a bunch
of method names that look like they are doing exactly what their names
describe.
For example, some of the things you can do with strings are:
"a string".capitalize
returns"A string"
, with the first letter uppercased."a string".length
returns8
, which is the length of the string."a string".start_with?("a")
returnstrue
, because the string starts with an"a"
."a string".include?("s")
returnstrue
, because the string contains the character"s"
.
Some examples for useful methods on arrays are:
[5, 1, 3].sort
returns another array, with the elements sorted:[1, 3, 5]
.[5, 1, 3].size
returns3
, the number of elements in the array.[1, 1, 1, 2].uniq
returns a new array with duplicate elements removed:[1, 2]
.[1, 2, 3].join(", ")
returns a string"1, 2, 3"
.[1, 2, 3].include?(2)
returns true because the array contains the number2
.
How do you find all these methods?
The quickest way to find a certain method for an object often is to just ask
Google: “ruby array sort”. That will point you to the Ruby documentation.
Another way is to read through all the methods for the class on the respective
Ruby documentation page. And of course, you can also read through the method
names returned by [1, 2, 3].methods.sort
.
Questions and commands
Generally speaking, methods play one of two roles: They either answer a question, or they perform a command.
For example, if we have a user
object, and users have a name, then we would
ask the user object for its name by calling the method name
: user.name
would return the user’s name. Arrays know their size (how many elements they
have), so we can ask an array: [1, 2, 3].size
will return 3
.
Another example is the method sort
on arrays. This method does not actually
sort the array that it is called on. Instead it returns a new array with the
same values, but in a sorted order:
array = [3, 2, 1]
p array.sort
p array
This will output [1, 2, 3]
and then [3, 2, 1]
. So we see that the original
array is still the same.
Often questions need another bit of information passed. E.g., we can ask
a string Do you start with this character?, and we’ll need to pass the
character that we are talking about: "a string".start_with?("a")
. The answer
to this question will be true
. Or if we ask an array if it includes a certain
element, then of course we need to pass that element, as in [1, 2,
3].include?(1)
The other role that a method can play is being responsible for executing a certain command.
For example in Rails objects that can be stored to the database have a method
save
. Of course, the purpose of this method is to save the object to the
database. E.g. user.save
would save some changes that we’ve made to the user
before, like, maybe we have given them a new password.
Another example is the method sort!
on arrays. Different from the method
sort
(without an exclamation mark), this method tells the array to sort
itself:
array = [3, 2, 1]
array.sort!
p array
If you run this code, then it will print out [1, 2, 3]
: the array is now
sorted.
Another example for a method that is a command is the method puts
. All it
does is print something to the screen, and it always returns nil
.
Whenever you think about adding a new method to your code it makes sense to think about the role the method should have. Is it a question? Or a command?
Alternative Syntax
You have, so far, learned that Strings are defined using single or double quotes, like so:
"A String"
'Another String'
And Arrays are defined using square brackes, with a comma separated list of objects, like so:
["One", "Two", "Three"]
We’d like to quickly mention two alternative syntaxes for defining the same objects, even though they’re not used very often. However, you may sometimes find them in other people’s code, so it’s useful to know they exist.
Strings
First, Ruby has an alternative syntax for defining strings that goes like so:
%[any-character]The actual string[the same character]
Meaning, that when there’s a percentage character %
followed by any other
character, which also closes the whole thing, then this defines a string, too.
For examples these definitions all mean exactly the same:
"A String"
'A String'
%(A String)
%{A String}
%|A String|
Which character is used to open and close the string is a matter of style, and preference. The author of this text prefers round paratheses for readability, but others prefer curly braces or pipes. There’s no very clear convention in the Ruby community.
When would you use this alternative syntax?
Sometimes you need to define a string that itself contains (or may contain, in
future versions) the same quote character that you’ve used to define the string.
For example, if you have a string that contains the string "Name"
, including
the quotes, but in the same string you’d also like to use
String interpolation, so you’d normally
use double quotes to define the string. In that case you’d need to escape the
double quotes that are contained in your string.
It probably helps to look at an example. Imagine you’re working on an application that asks the user for an email address, validates the format of the given address, and then displays an error message if the format looks invalid:
The given email address "ruby@monstas" does not look like a valid email address.
Using double quotes your code might look like this:
address = "ruby@monstas"
message = "The given email address \"#{address}\" does not look like a valid email address."
puts message
Try running this code, and then also try removing the backslashes, and running
it again. You’ll get a syntax error message because Ruby thinks the second
double quote (after address
) closes the first one, so it would look at
#{...}
outside of a string, which is not valid Ruby.
While the above code, including the backslash characters, is valid Ruby code, it looks a little ugly. Typing this stuff is kinda cumbersome, and programmers usually hate it.
So Ruby gives us another, nicer way to express the same:
message = %(The given email address "#{address}" does not look like a valid email address.)
Also, this allows us to freely change the message later, without having to change the quotes. For example, if our original message would have looked like so:
message = %(The given email address does not look like a valid email address.)
We could now change it to say the following without changing the quotes:
message = %(The given email address doesn't look like a valid email address.)
Arrays
Imagine you are running a programming study group, and you want to quickly write a piece of code that runs a little raffle to assign people to pairs randomly.
people = [
"Anne",
"Elizabeth",
"Erica",
"Iryna",
"Johanna",
"Juliane",
"Katja",
"Katrin",
"Maria",
"Renate",
"Sureka",
"Miriam",
"Zazie",
"Anja"
]
people.shuffle.each_slice(2) do |pair|
puts pair.join(', ')
end
Well, this code works. But wouldn’t it be cool to be able to define the array without having to write out all those quotes and commas? They’re quite cumbersome to type, aren’t they? Also, if we could omit them then we could just copy and paste that list of names from some where else, without modifying it further.
Luckily, Ruby provides us with a piece of syntax that does exactly this:
people = %w(
Anne
Elizabeth
Erica
Iryna
Johanna
Juliane
Katja
Katrin
Maria
Renate
Sureka
Miriam
Zazie
Anja
)
people.shuffle.each_slice(2) do |pair|
puts pair.join(', ')
end
The w
in %w(...)
stands for “words”, which means an array defined like this
will always only contain strings. So, %w(1 2 3 4)
would result in the same
array as ["1", "2", "3", "4"]
.
Using the right words
Naming things is hard
Just like programmers are obsessed about formatting, they also care a lot about how to name the things they create.
Choosing good names for your variables, methods, and classes is important, because this makes your code more expressive and easy to read. In fact, once you’ve learned the basic concepts of Ruby, well crafted code will read almost like a prose text to you. Not necessarily like your favorite novel, but maybe like a recipe or other instructional prose. Ruby is particularly great for writing expressive, readable code.
Here are a few examples of great, and rather bad names to pick.
Consider this code:
class Email
def initialize(str, string2, headers_hash)
# ...
end
# more methods ...
end
an_email = Email.new("Hi there, Ruby Monstas!", "2015-01-02", { :from => "Ferdous" })
From looking at the first two lines of this code you can figure out that the author defines an Email class, and it takes 3 arguments, two of which probably are supposed to be strings, and one is a hash containing some headers.
But what’s the purpose of the first two arguments? All we know the author wants them to be strings. So we might have to consult the documentation, or look at examples using the class. Luckily we can find an example at the very bottom of the file, and see that the first argument is supposed to be the subject, while the second one is a date.
Using the “type” of an object as a variable name, or part of a variable name,
usually is not a very good idea: string
, array
, hash
often are bad names,
except in contexts where the type of the object is all that matters. One example
for such a context is the method definition def encrypt(string)
in the
Modules chapter.
In our case it makes sense to name these arguments subject
and date
in the
first place, in order to make it easier for others to understand the purpose of
these arguments:
When you tell your non-programmer friends that “An email requires two strings and a hash of headers.”, they’d probably stare at you weird and switch to another topic. When you instead say “An email requires a subject, a date, and some headers” then they might roughly understand what you’re saying.
So let’s rename them:
class Email
def initialize(subject, date, headers)
# ...
end
end
email = Email.new("Hi there, Ruby Monstas!", "2015-01-02", { :from => "Ferdous" })
Also, the first argument name str
in the first example is an abbreviation,
which is something that isn’t very common in the Ruby world. People used to use
abbreviated variable names back when memory was extremely sparse, and
expensive: longer variable names would consume more memory. Nowadays there’s
just no reason any more to make your fellow developers puzzle over abbreviated
names. Consider var
, var1
, var_2
bad names, and instead use names that
reveal (talk about) your intentions, and their purpose.
Did you notice the local variable name an_email
in the first example? The
name email
in the second example also is much better in most situations. The
prefix an_
doesn’t add any kind of information. It’s just noisy, and adds
clutter.
Another example:
emailslist = [
Email.new("Hi there, Ruby Monstas!", "2015-01-02", { :from => "Ferdous" }),
Email.new("Keep on coding! :)", "2015-01-03", { :from => "Dajana" })
]
emailslist.each do |mail|
puts mail.subject
end
Again, list
says something about the type. Why not just call it emails
. The
plural already says that it’s some kind of list.
Also, the block argument mail
deviates from the class name Email
, and the
variable name emails
… and thus might raise a question “Wait, is this a
mail
maybe something different from an email
in this code?”. So why not
avoid confusion like that in the first place and call it email
… simply
the singular form of the name of the collection emails
:
emails = [
Email.new("Hi there, Ruby Monstas!", "2015-01-02", { :from => "Ferdous" }),
Email.new("Keep on coding! :)", "2015-01-03", { :from => "Dajana" })
]
emails.each do |email|
puts email.subject
end
The section “Naming variables” in this chapter of Ruby in 100 Minutes also has a couple great examples of good vs bad names.
Also, even though these examples are talking about local variables, the same reasoning applies to method names, class names, … basically any name that you pick.
Great names reveal your intention while you write this code. They talk about the purpose of your code, and explain things to your fellow developers.
Oh, and if you’re curious to read more about this, here is an interesting presentation on How to name things as well as a blog post on what programmers find the hardest tasks they face (guess what, it’s naming things …).
Arguments and parentheses
If you have read carefully, you may have noticed that we said about the code
puts 5
that puts
is a method call. And then later we’ve enclosed the value
3
in parentheses when calling the method: add_two(3)
.
That’s right:
In Ruby, when you define or call (execute, use) a method, you can omit the parentheses.
So these lines mean exactly the same:
puts "Hello!"
puts("Hello!")
And so do these:
add_two 2
add_two(2)
And all of these:
puts add_two 2
puts add_two(2)
puts(add_two 2)
puts(add_two(2))
So when do you use parentheses, and when do you omit them?
There is no clear rule about this, but there are some conventions. For now, you can just stick with the convention we are using at our study groups, which is:
- Use parentheses for all method calls that take arguments, except for the
methods
puts
andp
(and later:require
andinclude
). - If a method does not take any arguments, then do not add empty parentheses, omit them.
So the idiomatic way to write the line above is:
puts add_two(2)
Methods without arguments
Also, so far we’ve only mentioned that sometimes methods do not take any arguments. But we haven’t looked at an example so far.
Here’s one:
def greet
puts "Oh, hello!"
end
greet
The first three lines define a method, and we’ve picked the name greet
for it.
Again, the method body contains just a single line.
The last line consists of nothing but the word greet
. When Ruby runs this code,
and it finds the word greet
it will know that this refers to the method defined
earlier, and call it.
So Ruby jumps into the method body (this time it does not bring any objects with
it as arguments, because our method does not need any). It then executes the line
puts "Oh, hello!"
which prints a greeting to the screen.
As you can see we do not use any parentheses here. We could do that. The following code would be perfectly valid, and do exactly the same:
def greet()
puts "Oh, hello!"
end
greet()
However since we can omit the parentheses in this case we do just that. They just add visual noise and make the code slightly less readable.
Returning nil
Also, you might wonder what’s going on with the return value our our greet
method. We just said that every method call always returns “something” (an
object).
That’s right. The method call greet
will return the object returned from the
last evaluated statement (which, in our case, is puts "Oh, hello!"
).
What do you think that is?
The method puts
always returns nil
(because it was written that way): Its
purpose is to print something to the screen, not return something interesting.
It’s a command, not a question. So the most
sensible choice for a return value is nil
.
So where does the nil
object go?
If you look at our example code you notice that we don’t do anything with the
return value of the method call greet
: We don’t assign it to a variable. And
we don’t pass it to another method call. In fact, we simply discard it since
we’re not interested it in.
In our example that’s just fine. In practice you should always be aware why you are calling a certain method. Is it a command like Please print this? Then you probably aren’t interested in the return value and you can discard it as we’ve done above. Or is it a more like a question What’s the result when you add 2 to this number? In that case obviously you want to use the return value later.
For the sake of demonstrating that puts
and greet
indeed return nil
we can use p
to inspect the return value like so:
$ irb
> def greet
> puts "Oh, hello!"
> end
> p greet
Oh, hello!
=> nil
Here we go. The =>
thing in IRB signals that this is the return value. As you
can see it first prints the greeting (using puts
) and then, after returning
from the method, outputs the nil
value.
Terminology: Arguments vs Parameters
We should mention that we are slightly simplifying terminology here. We conflate two terms that normally would be defined separately: We simply use the term “argument” for both the variable names that are defined in the arguments list of the method definition, and the value that is passed as part of the method call.
In programming, normally the “argument list” is called a “parameter list” instead, and a single name on it is called a “parameter”. On the other hand, only the objects passed when calling the method are referred to as “arguments”.
E.g. in the code …
def add_two(number)
number + 2
end
puts add_two(3)
… the word number
in the first line is a “parameter”, whereas 3
in the
last line is an “argument”.
We found making this distinction in our beginners classes unnecessarily confusing, and thus ignore it. We simply call both these things “arguments”, and point out that they create a local variable inside of the method body.
So, now you know :)
Writing a new method
As programmers we like to split up our tasks, and do one thing after another. This allows us to focus on one small task, and once we’ve solved it, we move on to the next one.
When you need to add some new functionality to your program you’ll often find yourself thinking “I should add a method for this”: methods add behaviour.
Now, the first thing you should ask yourself is: “What is it that this method should do?” The answer to this gives you a hint for a good method name.
Let’s say you are working on an application that deals with emails, and the
thing you are trying to accomplish is formatting an email. So your method name
can be format_email
.
With this first task solved, knowing the method name, you can already go ahead and write down the method definition:
def format_email
end
While this is a pretty useless method, since it does nothing at all, it already is a valid method. So that’s good progress already: you’ve made the first step.
The next question to ask is: “Does this method need to be given any information in order to do its thing?” The answer to this question specifies the method’s arguments list.
In our example, the answer probably is that, in order to format an email, it needs the email.
So you can now add the argument list to the method:
def format_email(email)
end
With these two things solved you can now start thinking about implementing the method body. How can you transform the email into some formatted text?
You’d add a new line between def
and end
, and make sure it’s indented by
two spaces (hit tab
, unless your editor does it for you), and start focussing
on the code that makes up the method body:
def format_email(email)
...
end
You see how we’ve split up the task of writing a new method into three smaller tasks, and worked on each one of them after another.
Even though this might seem trivial at first, we recommend you get into this habit, too.
Btw, good editors help you format things. For example, when you are on a
line that starts with def something
, and at the end of the line hit return,
Sublime will already indent the next line for you by 2 spaces. If you now type
end
, then Sublime will notice that you are closing the method, and outdent
it again, so def
and end
sit on the same level. Smart, isn’t it?
Also, you’ll notice that when you type an opening parenthesis (
, then Sublime
will add a closing one )
too, but keep your cursor placed between them, so
you can type the argument list where it belongs.
Exercises: Now would be a good time to do some of the exercises on methods.
Advanced Topics
Using Libraries (1)
Our Person
class does not define an attribute accessor method for its
password, and thus, others cannot ask for and retrieve it.
However, our person object could freely give them an encrypted version of it.
Actually, this is pretty similar to how authentication often works in real web applications:
Applications do not store your actual password in plain text (hopefully!) That way if, for some reason, they get hacked, attackers wouldn’t have your actual password. Instead they store an encrypted version of the password.
Anyhow, we now want to add a method encrypted_password
to the Person
class,
which should return an encrypted version of the password that is stored in
the instance variable @password
.
Encryption is one of the things in programming that require very deep expert knowledge, and it is one of the things we definitely wouldn’t want to implement ourselves.
So far, all the Ruby features and methods that we have used are available right
away when the Ruby runtime ruby
executes your code. However, Ruby also comes
with a ton of functionality that is not available (loaded) right away. Instead
it is stored in so called libraries (which are just Ruby files, too), and we
have to load them manually, in order to make them available.
To do this, we use the method require
, and pass it the name of the library:
require 'digest'
Normally require
statements should be placed at the very top of the file, so
it is easy to see what libraries a particular piece of code (class) uses.
We are going to omit the initialize
and name
methods here, indicate the
omission with the comment # ...
, and just keep the password=
attribute
writer. In order to run this code make sure you keep all the methods.
require 'digest'
class Person
# ...
def password=(password)
@password = password
end
def encrypted_password
Digest::SHA2.hexdigest(@password)
end
end
The library digest
that we required includes a something called
Digest::SHA2
.
In programming a “digest” is an algorithm to convert one string into another in a way that the original string cannot be recovered later. However digesting the same string will always result in the same other, unique string. There are a good bunch of algorithms that do this, and “sha2” is the name of one of them.
Ok. For our example here we only need to understand that, once we have
required the library digest
, we can use the method Digest::SHA2.hexdigest
,
and it will encrypt (“digest”) the string that we pass to it.
If now run the following code:
person = Person.new("Ada")
person.password = "super secret"
puts person.encrypted_password
it will output
eabd522910ccdd77aef079feff0c7bb6486f6ab207ae6d3ed9e671208c92ab0f
which is the digested form of the string "super secret"
. Every time you run
the program you will see the same, unique string.
Modules
It now makes sense to introduce another language feature in Ruby: modules.
In Ruby, modules are somewhat similar to classes: they are things that hold
methods, just like classes do. However, modules can not be instantiated. I.e.,
it is not possible to create objects from a module. And modules, unlike classes,
therefore do not have a method new
.
So, what are modules useful for?
With modules you can share methods between classes: Modules can be included into classes, and this makes their methods available on the class, just as if we’d copied and pasted these methods over to the class definition.
This is useful if we have methods that we want to reuse in certain classes, but also want to keep them in a central place, so we do not have to repeat them everywhere.
Let’s have a look at this pretty contrived code:
module Cream
def cream?
true
end
end
class Cookie
include Cream
end
cookie = Cookie.new
p cookie.cream?
We still haven’t been able to come up with a better minimal example of a module and class that makes more sense than this. And we fully admit that this code is rather weird. However, it’s good enough to quickly explain how modules work :)
If you run this code it will output true
. Why is that?
The method cream?
is defined on the module Cream
, and all it does is always
return the value true
. Now, this module is included into the class Cookie
.
So, if we now instantiate a cookie, we can call the method cream?
on it,
and it will return the value true
.
Cool. Let’s move on and use this for our Person
class, which will hopefully
then make more sense.
Let’s assume that our application has other classes that need to encrypt things. And we want to keep the exact way of how we encrypt things, the implementation in one single place.
Why would we want to do that?
- One reason could be that when we want to switch to a different way of encrypting things (maybe use a stronger encryption algorithm), we would then only need to change it in this one place, in our module, and be done with it.
- Another reason could be that we want the encryption algorithm to be somehow configurable, for example in a configuration file that our application reads. This would then require additional logic that we would not want to duplicate across all the places where we need to encrypt something: we’d want all this to be kept in a central place.
- Another, much simpler, but sometimes also valid reason could be that we
simply want to move some clutter out of sight, and hide it away in another
file: so we can focus on what our
Person
class does, instead of having to look at the nitty-gritty details of how exactly we encrypt things.
Anyhow. Here’s how we can create a meaningful module for our application,
and then use it in the class Person
:
require 'digest'
module Encryption
def encrypt(string)
Digest::SHA2.hexdigest(string)
end
end
class Person
include Encryption
# ...
def encrypted_password
encrypt(@password)
end
end
person = Person.new("Ada")
person.password = "super secret"
puts person.encrypted_password
If you run this code, it will print out the same, encrypted version of the password: cool, that’s what we want.
We have moved the noisy details of the encryption algorithm to a module, and
then included the module to the class Person
. This, at the very least, makes
the method encrypted_password
much easier to read. Doesn’t it?
We refer to the process of moving some logic (code) from one method to another
new method as “extracting a method”. In our case we have extracted the method
encrypt
from the method encrypt_password
. When we do this, methods usually become
shorter and more readable.
Private methods
Remember how we said that instance variables store data that is “private” to the object? Instance variables are only made accessible to the outside world (we say “it is exposed”) if we add attribute accessors to the class.
In the same way classes sometimes want to keep certain methods private: methods that aren’t supposed to be called from outside of the object. Only the object itself is supposed to use them internally, from other methods.
Imagine I am an instance of a class ItalianRestaurant
, and I have a method
pizza
, which is supposed to return an instance of the class Pizza
.
When you approach me, and call the method pizza
(i.e. ask me to bring a
pizza) then I’ll know what to do, and how to do it. I’ll get some prepared
pizza dough from somewhere, some tomato sauce, vegetables and other stuff from
somewhere else, prepare the pizza, bake it, put it on a nice plate, and finally
give (return) it to you.
However, you don’t really care about any of these details. You are hungry, and just want the pizza. All the exact steps involved are something that I keep private to me, and maybe they’ve been our family’s best kept secret for generations.
This is pretty much how objects work, too. The Italian restaurant object exposes some stuff to the outer world (you), and keeps other things private. They’ll let you order a pizza, and other things. But they won’t tell you the exact ingredients of their tomato sauce, or how they manage to make this damn great pizza dough.
In our Person
example it makes sense to make the method encrypt
private.
Currently, if you run the following code it will execute just fine, even though it makes little sense:
person = Person.new("Ada")
p person.encrypt("some other secret")
Why would a person encrypt some arbitrary string for someone else, and return it? This is something that the person object should keep private. The restaurant wouldn’t turn flour, water, olive oil and other ingredients into pizza dough for everyone else either.
We can make the method encrypt
private like so:
module Encryption
private
def encrypt(string)
Digest::SHA2.hexdigest(string)
end
end
The keyword private
tells Ruby that all methods defined from now on, are
supposed to be private. They can be called from within the object (from other
methods that the class defines), but not from outside.
If you now try to call the method it will raise an error:
person = Person.new("Ada")
p person.encrypt("super secret")
This will print the error message:
private method `encrypt' called for #<Person:0x007fa179863770 @name="Ada">
Nice. Does this make sense?
Procs
TBD
Yield
TBD
Regular Expressions
Some people, when confronted with a problem, think “I know, I’ll use regular
expressions.”
Now they have two problems.
That’s a pretty famous joke, and it refers to the fact that regular expressions can be quite a pain to figure out.
However, once you know some basics about them, they’re also extremely powerful, and you can do amazing things with them, not only in Ruby, but also, for example, in your editor, and command line tools.
Regular expressions are sort of a swiss army knife for finding things in strings (text), extracting parts of them, or mass replacing certain bits with something else.
E.g. you could do:
- Extract area codes from phone numbers.
- Validate the format of an email address.
- For a list of files
a-01.mpeg
,b-02.mpeg
,c-03.mpeg
, change their names to01-a.mpeg
,02-b.mpeg
,03-c.mpeg
.
Regular expressions are a language to describe patterns of text. Wikipedia says: a sequence of characters that define a search pattern.
For example the pattern [0-9]+!
means: There needs to be at least one digit,
and it needs to be followed by an exclamation mark. The pattern
([\w]+)-([\d]+)\.mpeg
Does this stuff look scary and cryptic? You bet. That’s why regular expressions have kind of a strange reputation in programming. They’re super powerful, but they’re also kind of a pain: Like black magic, this power comes at a price.
The main reason why the language that is defined as regular expressions is so hard to read is that it dates back as far as 1956, and their first implementations in programming came up in the late 1960s. Back then every single character of your code was kinda worth its weight in gold. Memory was extremely limited, and code had to be as terse as possible.
Now, the most commonly used features of this language are the following:
- String literals: find a particular piece of text
- Anchors: the beginning and the end of the string, or a word
- Character classes: define a set of allowed characters
- Quantifiers: define how often a character is expected to occur
- Captures: once found, capture a particular part of the text, so we can use it
String literals
Let’s walk through some examples, to make this more practical. Let’s say we have the following text:
text = "A regular expression is a sequence of characters that define a search pattern."
… and we are interested to know if it contains the words character
and
sentence
. In Ruby, we could use a regular expression like so:
matches = text.match(/character/)
p matches
This will return an instance of the class MatchData
. Whereas, when we look
for sentence
we’ll get nil
:
matches = text.match(/sentence/)
p matches
Note how in Ruby one can define a regular expression by enclosing it with
slashes /
. There are other ways to define regular expressions, too, but
this is the most common one.
But that’s kinda boring, right? We could just use the method include?
for
strings, which lets us figure out the same thing. Let’s spice this up a little.
Anchors (boundaries)
The most commonly used anchors are: Beginning or end of the string, beginning or end of a line, beginning or end of a word.
For example:
text = "A regular expression is a sequence of characters that define a search pattern."
puts 'Found "A" at the beginning of the string.' if text.match(/^A/)
puts 'Found "O" at the beginning of the string.' if text.match(/^O/)
puts 'Found the string "character".' if text.match(/character/)
puts 'Found the word "character".' if text.match(/character\b/)
This will output:
Found "A" at the beginning of the string.
Found the string "character".
So it finds the string “character”, but not the word “character”. This is
because the regular expression /character\b/
requires a word boundary to be
found after the string literal (i.e. the literal piece of text) “character”.
Since in our example the text “character” is followed by another “s”, the
regular expression won’t match.
Character classes
Let’s say we want to find all the words that start with a vowel. For that
we can use a character class, i.e. a set of allowed characters. Again,
we use the anchor word boundary \b
, this time to express that the vowel
needs to be at the beginning of the word.
While the method match
returns an object (i.e. something “truthy”) when the
pattern matches (and nil
when it doesn’t), the method scan
returns an array
with all the occurances of text that match the pattern.
So let’s use it:
text = "A regular expression is a sequence of characters that define a search pattern."
p text.scan(/\b[aeiou][a-z]*\b/)
This will output:
["expression", "is", "a", "of", "a"]
Our regular expression defines that we’re looking for a piece of text that
- starts with a word boundary,
- followed by a character that is either “a”, “e”, “i”, “o”, or “u”,
- and potentially (
*
) followed by any character between “a” and “z”, and - finally ends with a word boundary.
We’ll explain the star *
quantifier in a bit.
Notice something though?
Regular expressions are case sensitive. I.e. our piece of code did not match the word “A” in the beginning of the string. In order to fix that we need to allow uppercase letters as well:
p text.scan(/\b[AEIOUaeiou][a-z]*\b/)
Our output will include the capital “A” at the beginning of the string as well:
["A", "expression", "is", "a", "of", "a"]
This example also demostrates the difference between a word boundary and
whitespace. A single space would count as whitespace, and we could use it to
match our words, too. However, that would not work at the beginning and end of
a string. And it would not work when our word is followed by punctuation, such
as a comma or fullstop. The word boundary \b
allows all of these, too.
So, what about the star *
in the expression above?
Quantifiers
This is a quantifier. It means “allow the stuff defined before zero, or any number of times”.
In our case it means that we’re looking for a single vowel, followed by either nothing, or one or many characters between “a” and “z”. This means we match both the words “A” and “a” (not followed by anything before the word boundary), as well as the words “is”, “of”, and “expression” (which are followed by one or many characters).
Does that make sense?
Let’s say we want to omit single character words, but we do want to allow all
words longer than one character. For that we could change the “none, one, or
many” quantifier *
to another quantifier +
meaning “at least one, or many”:
p text.scan(/\b[AEIOUaeiou][a-z]+\b/)
This won’t match the words “A” and “a”, and instead output the following:
["expression", "is", "of"]
What if we are looking for words that start with a vowel, and are no more than
2 characters long? We could use the quantifier ?
which means “none, or
exactly one”:
p text.scan(/\b[AEIOUaeiou][a-z]?\b/)
This will output:
["A", "is", "a", "of", "a"]
If we remove the quantifier entirely, then the regular expression will look for a word that starts with a vowel, followed by exactly one character:
p text.scan(/\b[AEIOUaeiou][a-z]\b/)
["is", "of"]
Captures
Using the method scan
with regular expressions like this is quite useful in
many situations. But sometimes, we need something more powerful.
Imagine we need to find all words that are followed by a word that starts with a vowel.
Let’s try using scan
for that:
p text.scan(/\b[A-Za-z]+\b +\b[AEIOUaeiou][a-z]*\b/)
The second part of this regular expression is just the same as above: Any word that starts with a vowel, and that is one or many characters long.
Can you figure out the first part?
It also says: Match something that starts at a word boundary, then has one or many characters between “A” and “Z” or “a” and “z” (that bit is new: you can combine ranges as character classes), and that is followed by at least one space.
If you run this you’ll get the following output:
["regular expression", "is a", "sequence of", "define a"]
Looks alright, doesn’t it?
However, we were only interested in words that are immediately followed by a word starting with a vowel. Our strings contain two words.
So either we’d have to work on these strings more (e.g. use split
to split
off the second word). Or there’s a smarter way of doing the same.
Enter captures.
In regular expressions one can “mark” certain parts of a patterns, saying: “Give me the bits that match this stuff here”. In order to mark a part of the pattern to be captured you’d enclose it with parenthese, like so:
/\b([A-Za-z]+)\b +\b[AEIOUaeiou][a-z]*\b/
Note how we’ve enclosed the first part of the pattern with parentheses. This means “match the full pattern, but only capture the parts that we’ve marked as interesting”.
p text.scan(/\b([A-Za-z]+)\b +\b[AEIOUaeiou][a-z]*\b/)
This returns a nested array like this:
[["regular"], ["is"], ["sequence"], ["define"]]
Awesome! We get all the words that we were interested in.
But why is this a nested array? The method scan
looks for each bit of text
that matches the given pattern (regular expression). It then extracts all the
“marked” (captured) parts from it, and keeps these as an array. As there can be
many occurances that match the pattern, and each of them can have many
captures, we get back a nested array.
Let’s capture the second word (starting with a vowel) as well to demonstrate this:
p text.scan(/\b([A-Za-z]+)\b +\b([AEIOUaeiou][a-z]*)\b/)
This will return:
[["regular", "expression"], ["is", "a"], ["sequence", "of"], ["define", "a"]]
More on character classes
So far we have used character classes like [aeiou]
(listing all allowed
characters literally), and [a-z]
(specifying a range of characters).
There’s more to these.
One can negate classes by prepending a “not” character ^
inside the square
brackets. E.g. [^AEIOUaeiou]
allows every character that is not a vowel.
So we can find all words that do not start with a vowel like so:
p text.scan(/\b[^AEIOUaeiou ][^ ]*\b/)
This means: Start at a word boundary, and allow everything that is not a vowel or a space as a first character, when it is optionally followed by one or many characters that are not a space, followed by a word boundary.
This will output:
["regular", "sequence", "characters", "that", "define", "search", "pattern"]
Also, regular expressions come with “baked-in”, predefined classes. For example
\d
means “any digit”. Here’s a list of common classes:
\d
is the same as[0-9]
(any digit)\D
is the same as[^0-9]
(everything except digits)\w
is the same as[A-Za-z_\-]
, called word character (i.e. this allows all lowercase and uppercase latin letters, as well as underscores and dashes)\W
is the same as[^A-Za-z_\-]
(everything that is not a word character)\s
means “any whitespace”, including spaces, tabs, and linebreaks\S
everything that is not whitespace
That means we could refine our expression from above:
/\b[A-Za-z]+\b +\b[AEIOUaeiou][a-z]*\b/
Like so:
/\b\w+\b +\b[AEIOUaeiou]\w*\b/
This might yield slightly different results if we have words that contained dashes or underscores, but it is same in our case:
["regular expression", "is a", "sequence of", "define a"]
One can also combine these predefined classes with each other, and literal
charachters. For example [\w!?]+
would find a sequence of at one or many
characters that is a word character, an exclamation or a question mark.
Anything
Finally, there’s one special character that matches anything: the dot .
.
I.e. the regular expression .*
matches “any character, zero, or any number of
times”. This may be useful if you are looking, for example, for whatever text
is enclosed in parentheses:
text = "Regular expressions are powerful (and sometimes confusing, even to experienced developers)."
p text.scan(/\(.*\)/)
Notice the backslashes before the opening and closing parentheses? We want to match these literal characters, and not use them with their special meaning of capturing their content here. Therefore we need to escape them to tell Ruby: Yep, we really mean a parenthesis here.
By the way this will output the following:
["(and sometimes confusing, even to experienced developers)"]
Whoops. Maybe we actually also do want to capture a part or this, and omit the actual parentheses from the result. We can do that by placing an extra pair of un-escaped (capturing) parentheses inside the escaped (literal) ones:
p text.scan(/\((.*)\)/)
And now we’ll get this result, which has the parentheses stripped off:
[["and sometimes confusing, even to experienced developers"]]
Rubular
Confused? Don’t worry. We all are.
Try to remember some of the most basic, simple stuff. Then try using it, maybe in your text editor, when you search for a certain phrase. Over time you’ll remember a few more things, bit by bit, and things will become a little less confusing. Writing a long, complicated regular expression, that actually works, without thinking, trying, and re-trying a lot is something that only few developers actually can do - and the author of this book isn’t one of them.
If you cannot figure out a certain regular expression, or if you want to experiment with something then Rubular is a great tool for that. Enter some text to the “test string” text area, and then start writing a regular expression, one bit after another. The app will display the parts that match, and your captures if you define some.
Exercises (old)
These exercises are an outdated set of exercises that need to be ported and adjusted in order to reflect the current order of topics in our book.
Many of them at the moment assume things early on that only are explained later in the book.
Working with Numbers
In order to start irb
open your terminal and type irb
, then hit the
return
key (enter
). In order to quit irb
again (and get back to your
system shell prompt) you can type exit
or press ctrl-d
, which does the
same.
Exercise 1.1
In irb
, calculate:
- How many hours are in a year.
- How many minutes are in a decade?
- How many seconds old are you?
Exercise 1.2
What do you think happens when you combine the following floats and integers?
Try computing these in irb
:
3.0 / 2
3 / 2.0
4 ** 2.0
4.1 % 2
Is the result a float or an integer?
Exercise 1.3
Methods are a way of “doing something with an object”. E.g. in Ruby, numbers have two methods that allow you to check whether the number is odd or even.
Look through the documentation for integer
numbers (called Fixnum
) and find the methods that tell if a number is odd or
even.
Exercise 1.4
In irb
, use these methods to find out if certain numbers are odd or even.
Numbers like 0, 1, 2, 99, -502
etc.
You can use a method by appending a dot .
and then the method name
to the object. E.g. -99.abs
uses (we also say: “calls”) the method
abs
on the number -99
.
Try for yourself what it does, and google for “ruby abs” to find the documentation for this method.
Working with Strings
In order to start irb
open your terminal and type irb
, then hit the
return
key (enter
). In order to quit irb
again (and get back to your
system shell prompt) you can type exit
or press ctrl-d
, which does the
same.
Exercise 2.1
What do you think this will do?
$ irb
> "hello".length + "world".length
Try it yourself.
Exercise 2.3
Skim through the documentation for strings in the Ruby documentation, and look for a method that prepends one string to another string.
Using that method prepend the string "Learning "
to the string "Ruby"
Exercise 2.4
Skim through the documentation for strings in the Ruby documentation, and look for a method that removes characters from a string.
Using that method turn the string "Learning Ruby"
into the string "Lrnng Rb"
.
Exercise 2.5
Find out how to convert the string "1.23"
into the number 1.23
.
You can either, again, skim the documentation page for strings, or google for “ruby convert string to number”.
Then also find out what method can be used to turn the string "1"
into the
number 1
(remember floats and integers are different kinds of numbers).
Confirm that you have found the right methods by trying them in irb
.
Exercise 2.6
There is a method that allows to justify a string, and padding it with another string.
Find that method and use it on the string "Ruby"
together with "<3"
so that
you get the following string back:
"Ruby<3<3<3"
We’ll admit that this is a rather creative usage of this method. Normally you’d use it to align strings to columns (e.g. so that they line up nicely when you format a table). You’ll use this method in other exercises later on.
Working with Arrays (1)
Before you get started, make sure you have your text editor and terminal open,
and you have navigated to your exercises directory in the terminal. E.g. cd
~/ruby-for-beginners/exercises
.
Exercise 3.1
Create a new, empty file. Save it as arrays_1-1.rb
. Fill in the following
line:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# your code goes here
So that, when you run your code (run ruby arrays_1-1.rb
), you get the
following output:
5
Exercise 3.2
Copy your file to a new file: cp arrays_1-1.rb arrays_1-2.rb
, then open this
new file.
Add another line before the line that you just added, so that, when you run your code, you get the following output:
99
Exercise 3.3
Make a new file arrays_1-3.rb
, and fill in the following line:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# your code goes here
p numbers
So that you get the following output:
[2, 4, 6, 8, 10]
Read the documentation for the method select
that you can use on arrays
on the Ruby documentation
Exercise 3.4
Again, copy your last file to a new file: cp arrays_1-3.rb arrays_1-4.rb
,
then open this new file.
Now change the code that you just added so that you get the following output:
[10, 8, 6, 4, 2]
The method select
that you used in the last exercise returns an array.
On this array (the return value) you can use another method, by, again, just
appending a dot .
and the method name to it, i.e., to the end of the line.
There is another method that reverses the order of the array. You can find it by googling for “ruby array reverse”.
Exercise 3.5
Again, copy your last file to a new file: cp arrays_1-4.rb arrays_1-5.rb
,
then open this new file.
Now change your code so that you get the following output:
[10, 8, 4, 2]
Bonus: Find at least three different solutions for this last change.
Working with Hashes (1)
Before you get started, make sure you have your text editor and terminal open,
and you have navigated to your exercises directory in the terminal. E.g. cd
~/ruby-for-beginners/exercises
.
Exercise 4.1
Make a new file hashes_1-1.rb
, and fill in the following line:
dictionary = { :one => 'uno', :two => 'dos', :three => 'tres' }
# your code goes here
So that it prints out dos
.
Exercise 4.2
Make a new file hashes_1-2.rb
, and fill in the following line:
dictionary = { :one => 'uno', :two => 'dos', :three => 'tres' }
# your code goes here
puts dictionary[:four]
So that it prints out cuatro
.
Exercise 4.3
Copy that file to a new file cp hashes_1-2.rb hashes_1-3.rb
, and change your
code so that it prints out the following.
Cuatro
There’s a method that upcases the first letter of a string. Find it by googling for “ruby string upcase first letter”.
Exercise 4.4
There is a method on hashes that allows to check if a certain key is defined on the hash. Find that method by googling for “ruby hash key defined”.
Try this method in irb
by creating a hash like the one above, calling the
method and passing keys like :one
, :two
, :four
, and :ten
.
Exercise 4.5
There is a method on hashes that flips keys and values. Find that method on the Ruby documentation about hashes
Make a new file hashes_1-5.rb
, and fill in the following line using that
method:
dictionary = { :one => 'uno', :two => 'dos', :three => 'tres' }
# your code goes here
This should then output:
{ 'uno' => :one, 'dos' => :two, 'tres' => :three }
Defining methods
Exercise 5.1
Write a method greet
that takes a name, prepends "Hello "
, and appends an
exclamation mark "!"
:
def greet(name)
# your code goes here
end
puts greet("Ada")
This should print out Hello Ada!
.
Exercise 5.2
Once you’ve implemented the method this should print out: Hello Ada!
.
Now change your method so that instead of always using "Hello "
it picks a
random string from the array ["Hello", "Hi", "Ohai", "ZOMG"]
.
Every time you run the program it should print out either "Hello Ada!"
, "Hi
Ada!"
, "Ohai Ada!"
, or "ZOMG Ada!"
.
The method shuffle
on arrays does, well, shuffle the array :) That
means it changes the order of the elements in the array in a random way.
Exercise 5.3
Write a method that converts a distance (a number) from miles to kilometers:
def miles_to_kilometers(miles)
# your code goes here
end
puts miles_to_kilometers(25)
This should print out:
40.2336
Exercise 5.4
Write a method leap_year?
that takes a year (a number), and calculates if it is a leap year.
def leap_year?(year)
# your code goes here
end
p leap_year?(2012)
p leap_year?(2015)
This should print out:
true
false
Hint: The operator %
returns the rest of a division. E.g. 14 % 3
returns 2
.
Bonus: Also make it so that the method returns true
for the year 2000
and
false
for 1900
… because that’s really the definition of leap years.
Working with Arrays (2)
Note: At the moment these exercises require reading up until “Block return values”.
You should know how to use the method collect
on arrays.
Before you get started, make sure you have your text editor and terminal open, and
you have navigated to your exercises directory in the terminal. E.g. cd
~/ruby-for-beginners/exercises
.
Exercise 6.1
Create a new, empty file. Save it as arrays_2-1.rb
. Fill in the following line:
words = ["one", "two", "three", "four", "five"]
# your code goes here
p words
So that you get the following output:
["one", "three", "five"]
Exercise 6.2
Copy your file to a new file: cp arrays_2-1.rb arrays_2-2.rb
, then open this
new file.
Now change your code so that you get the following output:
["One", "Three", "Five"]
Google for ruby string uppercase first letter
.
Exercise 6.3
Copy your file to a new file: cp arrays_2-2.rb arrays_2-3.rb
, then open this
new file.
Now change your code so that you get the following output:
["One <3", "Three <3", "Five <3"]
Use string interpolation for this.
Exercise 6.4
Copy your file to a new file: cp arrays_2-3.rb arrays_2-4.rb
, then open this
new file.
Now change your code so that you get the following output:
["One <3", "Three <3<3<3", "Five <3<3<3<3<3"]
Exercise 6.5
Copy your file to a new file: cp arrays_2-4.rb arrays_2-5.rb
, then open this
new file.
Now change your code so that you get the following output (hint: again, that’s now a string, not an array):
One <3, Three <3<3<3, Five <3<3<3<3<3
Exercise 6.6
Copy your file to a new file: cp arrays_2-5.rb arrays_2-6.rb
, then open this
new file.
Now change your code so that you get the following output, using the newline
character "\n"
:
One <3
Three <3<3<3
Five <3<3<3<3<3
Exercise 6.7
Copy your file to a new file: cp arrays_2-6.rb arrays_2-7.rb
, then open this
new file.
Now change your code so that you get the following output, aligning the second column:
One <3
Three <3<3<3
Five <3<3<3<3<3
As you may guess, strings have a method that is helpful for this. Ask Google: “ruby string align”.
Working with Nested Arrays
Note: At the moment these exercises require reading up until “Block return values”.
You should know how to use the method collect
on arrays.
Before you get started, make sure you have your text editor and terminal open, and
you have navigated to your exercises directory in the terminal. E.g. cd
~/ruby-for-beginners/exercises
.
Exercise 7.1
Make a new file nested_arrays-1.rb
, and fill in the following line:
numbers = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# your code goes here
p sums
So that you get the following output:
[6, 15, 24]
Exercise 7.2
Make a new file nested_arrays-2.rb
, and fill in the following line:
Fill in the following line
numbers = [
[1, 2, 3],
[2, 2, 2],
[3, 2, 1]
]
# your code goes here
lines.each { |line| puts line }
So that you get the following output:
* ** ***
** ** **
*** ** *
Working with Hashes (2)
Exercise 8.1
Make a new file hashes_2-1.rb
, and dd the following lines:
languages = {
:de => 'German',
:en => 'English',
:es => 'Spanish',
}
dictionary = {
:de => { :one => 'eins', :two => 'zwei', :three => 'drei' },
:en => { :one => 'one', :two => 'two', :three => 'three' },
:es => { :one => 'uno', :two => 'dos', :three => 'tres' }
}
Now, at the end of the file, add code that prints out the following:
In German, eins means one, zwei means two, drei means three.
In Spanish, uno means one, duo means two, tres means three.
Exercise 8.2
Now, in a new file hashes_2-2.rb
, with the same hashes from above, add code
that prints out the following table:
de eins zwei drei
en one two three
es uno dos tres
Exercise 8.3
Copy your file to a new file cp hashes_2-2.rb hashes_2-3.rb
and change your
code so that it aligns the table columns:
de eins zwei drei
en one two three
es uno dos tres
Exercise 8.4
Copy your file to a new file cp hashes_2-3.rb hashes_2-4.rb
and change your
code so that it adds delimiters:
| de | eins | zwei | drei |
| en | one | two | three |
| es | uno | dos | tres |
Truthiness
Exercise 9.1
Make a new file truthiness-1.rb
, and add the following lines:
This exercise is about validating what we’ve learned about truthiness.
You have the following array:
objects = [true, false, 0, 1, "", [], Object.new, Class.new, Module.new]
Add some code that outputs the following table. The last column should be
filled in with by either true
or false
depending what the operation
!!object
, which is the same as
not not object` for each of the objects
returns:
object | !!object
true | [true|false]
false | [true|false]
nil | [true|false]
0 | [true|false]
1 | [true|false]
"" | [true|false]
[] | [true|false]
#<Object:0x007fb3dc0ea1b8> | [true|false]
#<Class:0x007fb3dc0e2cd8> | [true|false]
#<Module:0x007fb3dc0d9ea8> | [true|false]
You can use the method inspect
in order to, for each of the values, get a
representation that looks like the code.
Of course you will get different object ids for the object, class, and module instances everytime you run your code.
So let’s prettify this table by removing the object ids. You can do this
calling sub(/:.*>/, ">")
on whatever inspect
returns.
Your table should now look like this:
object | !!object
true | true
false | false
nil | false
0 | true
1 | true
"" | true
[] | true
#<Object> | true
#<Class> | true
#<Module> | true
Bonus: Exercise 9.2
Copy your file to a new file truthiness-2.rb
, and change your code so that
it outputs the following table. Fill each cell with the result that ==
returns for each combination.
| true | false | 0 | 1 | "" | [] | #<Object> | #<Class> | #<Module>
true | | | | | | | | |
false | | | | | | | | |
0 | | | | | | | | |
1 | | | | | | | | |
"" | | | | | | | | |
[] | | | | | | | | |
#<Object> | | | | | | | | |
#<Class> | | | | | | | | |
#<Module> | | | | | | | | |
Tip: Break this up in sub-tasks:
- Write some code that collects an array of arrays containing all rows: An
array
rows
that has many arrays, each of which holds 9 results (cells). - Add the value’s representation to the beginning to the row. If you don’t know how, google for “ruby array add to beginning”
- Make sure each of the cells is 9 characters wide.
- Join each of the row with the string
" | "
. - Add the table headers row.
- Join all rows with the string
"\n"
(newline), and output the result.
The Email Class
Exercise 10.1
In a new file email_1.rb
write a class Email
that has a subject
,
date
, and from
attribute. Make it so that these attributes can be populated
through new
and initialize
.
The following code
class Email
# fill in this class body
end
email = Email.new("Homework this week", "2014-12-01", "Ferdous")
puts "Date: #{email.date}"
puts "From: #{email.from}"
puts "Subject: #{email.subject}"
should then output the following:
Date: 2014-12-01
From: Ferdous
Subject: Homework this week
Exercise 10.2
Once you have this class, copy your file to email_2.rb
.
Change your class so that the initialize
method now takes a subject
string,
and a headers
hash. This is then more in line with how actual emails are
stored in the real world: the values date
and from
are stored on a hash,
which is called the “email headers”.
Doing so, in the code above the only line you should change is the one that instantiates the email object, which should now read:
email = Email.new("Keep on coding! :)", { :date => "2014-12-01", :from => "Ferdous" })
Your program should now still produce the same output.
The Mailbox Class
Exercise 11.1
In a new file mailbox-1.rb
Write a class that has a name
and emails
attribute. Make it so that the these attributes can be populated through the
initialize
method (name
being a string, and emails
being an array of
Email
instances).
The following code
class Email
# your class from the last exercise
end
class Mailbox
# fill in this class body
end
emails = [
Email.new("Homework this week", { :date => "2014-12-01", :from => "Ferdous" }),
Email.new("Keep on coding! :)", { :date => "2014-12-01", :from => "Dajana" }),
Email.new("Re: Homework this week", { :date => "2014-12-02", :from => "Ariane" })
]
mailbox = Mailbox.new("Ruby Study Group", emails)
mailbox.emails.each do |email|
puts "Date: #{email.date}"
puts "From: #{email.from}"
puts "Subject: #{email.subject}"
puts
end
should then output the following:
Date: 2014-12-01
From: Ferdous
Subject: Homework this week
Date: 2014-12-01
From: Dajana
Subject: Keep on coding! :)
Date: 2014-12-02
From: Arianne
Subject: Re: Homework this week
The Mailbox Text Formatter
The mailbox text formatter exercise is a milestone in the beginners group curriculum, and it may take a little bit longer to complete it. That is fine, and, to some extent, the point :)
Exercise 12.1
Make a new file mailbox_text-1.rb
. Fill in and complete the following
class definitions:
class Email
# your class from the last exercise
end
class Mailbox
# your class from the last exercise
end
class MailboxTextFormatter
# fill in this class body
end
emails = [
Email.new("Homework this week", { :date => "2014-12-01", :from => "Ferdous" }),
Email.new("Keep on coding! :)", { :date => "2014-12-01", :from => "Dajana" }),
Email.new("Re: Homework this week", { :date => "2014-12-02", :from => "Ariane" })
]
mailbox = Mailbox.new("Ruby Study Group", emails)
formatter = MailboxTextFormatter.new(mailbox)
puts formatter.format
Your goal is to complete the code in a way so it outputs the following:
Mailbox: Ruby Study Group
+------------+---------+------------------------+
| Date | From | Subject |
+------------+---------+------------------------+
| 2014-12-01 | Ferdous | Homework this week |
| 2014-12-01 | Dajana | Keep on coding! :) |
| 2014-12-02 | Ariane | Re: Homework this week |
+------------+---------+------------------------+
You are allowed to add as many methods to the classes Email
, Mailbox
and
MailboxTextFormatter
as you deem useful. In your final solution you are not
allowed to change any of the code outside (after) the class definitions. For
debugging purposes, you can, of course, change all the code you want :)
If you do this exercise in one of our study groups then best do it together with someone else. We found pairs to work best, and a three person team would be better than doing it on your own.
Try to come up with a working solution first. It doesn’t matter how elegant, generic, or pretty it is. Whatever produces the required output is fine for a first solution.
Then, later, look at your code, and try to improve it by cleaning up everything that you don’t like, or deam ugly.
Eventually, one goal to aim for would be: Adding another column to the table only requires mimimal changes, e.g. changes to one or two places in your formatter class.
The Mailbox Html Formatter
Separation of concerns
One question that may have come up while working on the mailbox text formatter exercise is:
Why would we have a separate class for formatting the ASCII table (that is, a
plain text table that uses characters like +
, -
, and |
)?
The reason is: We want each one of our classes to encapsulate one concept that is useful in our application. We also say: each one of our classes should be concerned with one responsibility.
An email vaguely resembles the concept of an analog letter, written on paper: some message is being sent from one person to another. Nowadays everyone knows what an email is: it stores all information about this particular message. The same is true for mailboxes, which are used to store a bunch of emails. Formatting a number of emails in order to be displayed on a text based terminal is a very different concept, and concern.
Therefore it makes a lot of sense to have three different classes implement each one of these concepts, or concerns. And it even makes so much sense that it is called a design principle in programming: The principle of separation of concerns.
Aside from being comprehensible and mapping to concepts that we already know, one other advantage is: We can now easily implement other formatter classes that format our emails in a different way, suitable to be displayed in other media.
And that’s what this exercise is about: We want to display our mailbox contents in HTML, the format that browsers like to use. If you are unfamiliar with what HTML is, and how it looks like, you can read up on it here. This will be our first step towards learning how to build a web application.
Model, View, Controller
Before we get to that, we’d like to point out one other aspect, that you’ll remember when we get to talk about the architecture that Rails use to structure and separate concerns, called “model, view, controller”.
- The two classes
Email
andMailbox
in your application are models: they are concerned with modelling those “real-world” things that your users are interested in: they want to work with emails. - The class
MailboxTextFormatter
on the other hand is a view: It is used in order to generate a certain representation, in a format that is suitable to a certain medium (the terminal, in our case). - You don’t have a controller class so far, but the little bit of code at the end of the file does what a controller usually would do: it generates some models (the emails, and the mailbox objects) and passes them to the view (the formatter) in order to be rendered into something that can then be returned and displayed.
If this doesn’t make a whole lot of sense to you at the moment, don’t worry. You’ll understand it more once we build our first Rails application.
Exercise 13.1
Ok, now to our exercise. We will start over with the same code again, except
that our formatter class now will be called MailboxHtmlFormatter
.
Copy your file mailbox_text-1.rb
to mailbox_html-1.rb
and change it
like so. Then fill in the MailboxHtmlFormatter
class.
class Email
# your class from the last exercise
end
class Mailbox
# your class from the last exercise
end
class MailboxHtmlFormatter
# fill in this class body
end
emails = [
Email.new("Homework this week", { :date => "2014-12-01", :from => "Ferdous" }),
Email.new("Keep on coding! :)", { :date => "2014-12-01", :from => "Dajana" }),
Email.new("Re: Homework this week", { :date => "2014-12-02", :from => "Ariane" })
]
mailbox = Mailbox.new("Ruby Study Group", emails)
formatter = MailboxHtmlFormatter.new(mailbox)
puts formatter.format
Your goal is to complete the code in a way so it outputs the following:
<html>
<head>
<style>
table {
border-collapse: collapse;
}
td, th {
border: 1px solid black;
padding: 1em;
}
</style>
</head>
<body>
<h1>Ruby Study Group</h1>
<table>
<thead>
<tr>
<th>Date</th>
<th>From</th>
<th>Subject</th>
</tr>
</thead>
<tbody>
<tr>
<td>2014-12-01</td>
<td>Ferdous</td>
<td>Homework this week</td>
</tr>
<tr>
<td>2014-12-01</td>
<td>Dajana</td>
<td>Keep on coding! :)</td>
</tr>
<tr>
<td>2014-12-02</td>
<td>Ariane</td>
<td>Re: Homework this week</td>
</tr>
</tbody>
</table>
</body>
</html>
Does that look scary? A little bit, maybe. It’s probably fair to say that manually writing HTML isn’t very popular amongst most programmers. Therefore there are quite a few tools that make our lives easier. And your task is to write such a tool.
Also, this exercise should actually be easier for you to complete than the previous one. You already have a bunch of practice in iterating over emails, and working with arrays and strings. And this time, you don’t need to deal with the maximum length of strings per column. You can just interpolate things together.
Storing our HTML to a File
Exercise 14.1
In this exercise our goal is to store the generated HTML for our mailbox to a file, so that we can finally view it in an actual browser.
Your objective is to write a file (using Ruby) that contains all the HTML from the last exercise.
Building on the last exercise, copy your file to a new file cp mailbox_html-1.rb
mailbox_file-1.rb
and change the last line:
Instead of passing the HTML to puts
you should be passing it to a different
method, so that running your program writes the HTML file that we are after.
In order to find that method try googling for “ruby write file”. Call this
file emails.html
.
When you open this file in a text editor you should see the same HTML from the last exercise. When you open it in your browser it should look like this:
Reading from a CSV File
So far our program has all the email data hardcoded: All the data is baked right into our code. Everytime you run it, it will display all the same emails. Obviously that’s not a very useful for a real world program.
Let’s change this to read our data from an external data source instead.
One very simple and pretty popular way to store data to files is using the format CSV. This stands for “comma separated values” (although, often times semicolons are used as a separator instead of commas), and it is something that spreadsheets can read and export. Being able to work with CSV can be pretty handy: just write a little Ruby script, and filter that data, or work with it otherwise.
The first line in the code below require "csv"
makes Ruby’s CSV library
available to your program so that you can then use the class CSV
.
Exercise 15.1
Building on the same code from the last exercises, your objective is to
read the email data from a file emails.csv
. This file should be stored
in the same directory as your Ruby program, and contain the following data:
Date,From,Subject
2014-12-01,Ferdous,Homework this week
2014-12-01,Dajana,Keep on coding! :)
2014-12-02,Ariane,Re: Homework this week
2014-12-11,Maria,I'm back in Berlin
You can create that file by copying the four lines from above to your editor.
In order to complete this exercise you’ll need to find out how to:
- Read the contents of a file to Ruby, as a string
- Parse this string as CSV (google for “ruby csv” and/or look at the documentation)
- For each of the rows, ignoring the header line, create an
Email
instance
In other words, you should replace the following lines of code:
emails = [
Email.new("Homework this week", { :from => "Ferdous", :date => "2014-12-01" }),
Email.new("Keep on coding! :)", { :from => "Dajana", :date => "2014-12-01" }),
Email.new("Re: Homework this week", { :from => "Ariane", :date => "2014-12-02" })
]
Once you have figured out how to read and parse the CSV file, and create an
array of Email
instances out of that data, your next task is to encapsulate
all of that into a class EmailsCsvStore
. This class should take a filename,
and have a method read
, which returns the emails array.
In the end that part of the code should read:
require "csv"
class Email
# your class from the last exercise
end
class Mailbox
# your class from the last exercise
end
class MailboxHtmlFormatter
# your class from the last exercise
end
class EmailsCsvStore
def initialize(filename)
# fill in this method
end
def read
# fill in this method
end
end
store = EmailsCsvStore.new('emails.csv')
emails = store.read
mailbox = Mailbox.new("Ruby Study Group", emails)
formatter = MailboxHtmlFormatter.new(mailbox)
# your code from the last exercise, writing the file
Exercises
If you haven’t done the try ruby course already, we recommend you do at least a couple of lessons over there too.
Whenever you sit down to do some of the exercises in this book make sure you have your text editor and terminal open. In your terminal:
Create a new directory where you want to keep Ruby files for our exercises. For example:
~/ruby-for-beginners/exercises
. In order to create that directory in the terminal you can run:mkdir ~/ruby-for-beginners/exercises
.Then navigate to this directory:
cd ~/ruby-for-beginners/exercises
In order to check if you are in the right directory you can always run:
pwd
. (That’s short for print working directory, meaning, it prints the name of the current directory.)In order to see all the files and subdirectories contained in the current directory you can run
ls
.
Credits: The exercises 1.1, 1.2, and 2.1 are taken from Introduction to Ruby.
Working with Numbers
In order to do these exercises you should have read at least the chapter about [built_in_classes/numbers.html].
Exercise 1.1
In irb
, calcluate:
- How many hours are in a year.
- How many minutes are in a decade?
- How many seconds old are you?
To start irb
open your terminal and type irb
, then
hit the return
key (enter
). To quit it (and get back
to your system shell prompt) type exit
or press ctrl-d
.
Exercise 1.2
What do you think happens when you combine floats and integers the following in the following calculations?
Try computing these in irb
:
3 / 2
3.0 / 2
3 / 2.0
4 * 2.0
0 + 1
Is the result a float or an integer?
Exercise 1.3
Try finding out what “modulo” means by asking Google.
In Ruby (and many other languages) the operator for modulo is %
.
Try the following in irb
:
5 % 2
15 % 2
505 % 2
And:
8 % 5
9 % 5
10 % 5
11 % 5
Exercise 1.4
Methods are a way of “doing something with an object”, and you’ll learn a lot more about them in a few chapters.
In Ruby, numbers have methods that allow you to check whether the number is odd or even.
Look through the documentation for integer numbers and find the methods that tell if a number is odd or even.
Look at the examples for some of the other methods on that page.
You can use a method by appending a dot .
and then the method name
to the object. E.g. -99.abs
uses (we also say: “calls”) the method
abs
on the number -99
.
Exercise 1.5
In irb
, use these methods to find out if certain numbers are odd or even.
Try a bunch of numbers like 0, 1, 2, 99, -502
etc.
Try for yourself what it does, and google for “ruby number odd even” to find the documentation for these methods.
Working with Strings
In order to do these exercises you should have read at least the chapter about [built_in_classes/strings.html].
Exercise 2.1
In irb
try figuring out how to produce the following String: "Ruby<3<3<3"
.
Try using no more than two other String objects.
To start irb
open your terminal and type irb
, then
hit the return
key (enter
). To quit it (and get back
to your system shell prompt) type exit
or press ctrl-d
.
Exercise 2.2
Look through the documentation for Strings.
Do you spot any interesting methods?
Exercise 2.3
What do you think this will do?
$ irb
> "hello".length + "world".length
Try it yourself in irb
.
Exercise 2.4
Look through the documentation for Strings,
and find out how to convert the string "1.23"
into the number 1.23
.
If you can’t find it, google for “ruby convert string to float”.
Then also find out what method can be used to turn the string "1"
into the
number 1
(remember floats and integers are different kinds of numbers).
Confirm that you have found the right methods by trying them in irb
.
Working with Arrays (1)
In order to do these exercises you should have read at least the chapter about [built_in_classes/arrays.html].
Make sure you have your text editor and terminal open, and you have navigated
to your exercises directory in the terminal. E.g. cd ~/ruby-for-beginners/exercises
.
Exercise 3.1
In the Array [1, 2, 3, 4, 5]
, what’s the index of the number 4
?
Exercise 3.2
In irb
, how can you ask the Array, and check if your previous answer was correct?
Exercise 3.3
In irb
, how can you ask the Array for the number of elements it contains?
Exercise 3.1
Create a new, empty file. Save it as arrays_1-1.rb
. Fill in the following
line:
numbers = [1, 2, 3, 4, 5, 6]
# your code goes here
… so that, when you run your code (run ruby arrays_1-1.rb
), you get the
following output:
5
Exercise 3.2
Copy your file to a new file: cp arrays_1-1.rb arrays_1-2.rb
, then open this
new file.
Add another line before the line that you just added, so that, when you run your code, you get the following output:
99
Exercise 3.3
Make a new file arrays_1-3.rb
, and fill in the following line:
numbers = [1, 2, 3, 4, 5, 6]
# your code goes here
p numbers
… so that you get the following output:
[2, 4, 6]
You know how to get the individual numbers. And you know that you can list things separated by comma, and enclosed with square brackets in order to get a new Array. Try combining this, and “listing” the code that gets a number from the array.
Exercise 3.4
Again, copy your last file to a new file: cp arrays_1-3.rb arrays_1-4.rb
,
then open this new file.
Now add another line after the one that you just added (i.e. before you
finally output the array using p
).
Try to figure out how to transform your Array so you get the following output:
[6, 4, 2]
Before you do anything else, try describing the problem in English. What verb do you use to describe this transformation?
Look through the methods listed on the left side of the documentation for Arrays. to see if any of these describes what you’re looking for.
Working with Hashes (1)
In order to do these exercises you should have read at least the chapter about [built_in_classes/hashes.html].
Make sure you have your text editor and terminal open, and you have navigated
to your exercises directory in the terminal. E.g. cd ~/ruby-for-beginners/exercises
.
Exercise 4.1
Make a new file hashes_1-1.rb
, and fill in the following line:
dictionary = { :one => 'uno', :two => 'dos', :three => 'tres' }
# your code goes here
… so that it prints out dos
.
Exercise 4.2
Make a new file hashes_1-2.rb
, and fill in the following line:
dictionary = { :one => 'uno', :two => 'dos', :three => 'tres' }
# your code goes here
puts dictionary[:four]
… so that it prints out cuatro
.
Exercise 4.3
Copy that file to a new file cp hashes_1-2.rb hashes_1-3.rb
, and change your
code so that it prints out the following.
Cuatro
There’s a method that upcases the first letter of a string. Find it by googling for “ruby string upcase first letter”.
Exercise 4.4
There is a method on hashes that allows to check if a certain key is defined on the hash. Find that method by googling for “ruby hash key defined”.
Try this method in irb
by creating a hash like the one above, calling the
method and passing keys like :one
, :two
, :four
, and :ten
.
Exercise 4.5
There is a method on hashes that flips keys and values. Find that method on the Ruby documentation about Hashes
Make a new file hashes_1-5.rb
, and fill in the following line using that
method:
dictionary = { :one => 'uno', :two => 'dos', :three => 'tres' }
# your code goes here
This should then output:
{ 'uno' => :one, 'dos' => :two, 'tres' => :three }