Exploring The Well-Grounded Rubyist, Third Edition: Part 1
Summary of Chapter 1: Ruby Programming Fundamentals

About the book

The book The Well-Grounded Rubyist, Third Edition was written by David A. Black and Joseph Leo III. Ruby is a general-purpose, object-oriented, interpreted programming language designed by Yukihiro “Matz” Matsumoto. Ruby was first announced in 1993. The first public release appeared in 1995, and the language became very popular in Japan during the 1990s.
The purpose of The Well-Grounded Rubyist is to give you a broad and deep understanding of how Ruby works and a considerable toolkit of Ruby techniques and idioms that you can use for real programming, and many other topics, as we will discuss in the following sections.
This edition targets Ruby 2.5.
You can find the book on:
Amazon: Buy on Amazon
Introduction
I'm currently reading The Well-Grounded Rubyist, and this article is my attempt to share what I've been learning and understanding along the way.
This isn’t meant to replace the book—far from it. I highly recommend that you buy the book and read it for a deeper and more complete experience. But if you don’t have enough time right now, I hope this article serves as a helpful starting point—or even motivates you to dive into the book yourself.
And just to be clear:
We're talking about Ruby the programming language, not Ruby the singer—so no need to wonder, "Leih beydary keda?" 😄
Chapter 1 - Bootstrapping your Ruby literacy
The goal of the chapter is to bootstrap you into the study of Ruby with enough knowledge and skill to proceed comfortably into what lies beyond.
Basic Ruby language literacy
Installing Ruby and using a text editor
Though you’re free to install and compile Ruby from source from www.ruby-lang.org, it’s far more common for Rubyists using macOS or Linux to install versions of Ruby using a version manager.
The most popular version managers are RVM (https://rvm.io), rbenv (personal preference) (https://github.com/rbenv/rbenv), and chruby (https://github.com/postmodern/chruby).
Windows users are encouraged to use the RubyInstaller (https://rubyinstaller.org/).
All version managers are free, and all provide a safe and easy way to download and run Ruby. This book references Ruby version 2.5.1.
You’ll also need a text editor (any editor you like, as long as it’s a plain-text editor and not a word processor like VSCode (personal preference) and RubyMine [IDE]) and a directory (a.k.a. a folder) in which to store your Ruby program files.
The interactive Ruby console program (irb), your new best friend: The irb utility ships with Ruby and is the most widely used Ruby command-line tool, aside from the interpreter itself.
After starting irb, type Ruby code into it, and the code executes, printing out the resulting value.
Type irb at the command line and enter sample code as you encounter it in the text.

To exit from irb normally, you can type exit. On many systems, Ctrl+D works too.
If you find irb(main):001> is too noisy, you can use irb --simple-prompt, which makes irb output easier to read.
A Ruby syntax survival kit
In this section, we’ll start exploring some of the basic elements of Ruby syntax. You’ll learn:
How to perform arithmetic operations
How to store objects in variables so you can reuse them later in your code
How to compare object values using equality and comparison operators
And how to write conditional logic by comparing variables with each other
These are the building blocks of writing meaningful Ruby code, and they’ll give you a solid foundation to build on as we go deeper into the language.
Basic operations in Ruby:
Arithmetic operations:
Addition:
1 + 2Subtraction:
4 - 3Multiplication:
3 * 7Division:
1 / 3All these operations work on integers or floating-point numbers (floats). Mixing integers and floats together, as some of the examples do, produces a floating-point result.
What is a Float?
In Ruby, a float (short for floating-point number) is a number that has a decimal point. It’s used to represent real numbers, including both whole and fractional parts.
For example:
3.14 # a float 0.5 # a float 10.0 # also a float, even though it's a whole numberYou’ll often encounter floats in Ruby when:
Performing division that results in a non-integer value (e.g.,
5 / 2.0gives2.5)Working with measurements, prices, or anything that requires precision beyond whole numbers
💡 Note: If you divide two integers in Ruby (like
5 / 2), the result is also an integer (2). To get a float result, at least one number should be a float (5.0 / 2or5 / 2.0).
Assignment operations in Ruby:
x = 1string = "Hello World!"This operation binds a local variable (on the left) to an object (on the right). In Ruby, everything is an object, including numbers, strings, and even
nil(a.k.a. nothing).x = 1assigns the integer object1to the variablex.string = "Hello World!"assigns the string object"Hello World!"to the variablestring.
💡 Note: Variables in Ruby don’t have a fixed type—they simply refer to an object. That means you can reassign
xto a different kind of value later:x = 1 x = "Now I'm a string"This dynamic behavior makes Ruby very flexible, but it also means you should keep track of what your variables represent to avoid confusion.
Working with Strings in Ruby
In Ruby, a string is a sequence of characters enclosed in quotes. You can use either single quotes
'or double quotes"to create strings.Basic Examples:
greeting = "Hello, world!" name = 'Ruby'Difference Between Single and Double Quotes:
Single quotes
'...'treat the content literally.- Double quotes
"..."allow string interpolation and escape sequences.
Interpolation (Only with
"):You can insert variable values into a string using
#{...}:name = "Kareem" puts "Hello, #{name}!" # => Hello, Kareem!With single quotes, interpolation won't work:
puts 'Hello, #{name}!' # => Hello, #{name}!Common String Methods:

Concatenation:
You can combine strings using
+:first = "Hello" second = "World" puts first + " " + second # => Hello WorldOr use interpolation:
puts "#{first} #{second}" # => Hello WorldAccessing Characters:
Strings are like arrays of characters. You can use indices:
greeting = "Hello" puts greeting[0] # => "H" puts greeting[-1] # => "o" (last character)Summary
Strings are one of the most commonly used data types in Ruby. They are flexible, powerful, and come with many built-in methods that make text manipulation easy and expressive.
Comparing values in Ruby:
To compare two values for equality, Ruby uses the double equals sign (
==):x == yThis checks whether the value of
xis equal to the value ofy.⚠️ Important: Don't confuse this with a single equals sign (
=), which is used for assignment (e.g.,x = yassignsytox).Example:
x = 5 y = 5 x == y # => true x = 5 y = 6 x == y # => falseYou can also use other comparison operators:
!=— not equal<— less than>— greater than<=— less than or equal to>=— greater than or equal to
These are essential for making decisions in your code.
Convert a numeric string to a number:
x = "100".to_is = "100"x = s.to_iTo perform arithmetic, you have to make sure you have numbers rather than strings of characters. to_i performs string-to-integer conversion.
Basic input/output methods and flow control in Ruby:
Print something to the screen:

putsadds a newline to the string it outputs if there isn’t one at the end already;printdoesn’t.printprints exactly what it’s told to and leaves the cursor at the end. (Note: On some platforms, an extra line is automatically output at the end of a program.)poutputs an inspect string, which may contain extra information about what it’s printing.
Get a line of keyboard input:
getsstring = getsYou can assign the input line directly to a variable (the variable string in the second example). Try gets in an
irbsession, and a cursor will wait for you to enter input.
Conditional execution:
x = 1 y = 1 if x == y puts "Yes!" else puts "No!" endIn Ruby, you can run different blocks of code depending on whether a condition is true or false.
The
ifkeyword starts a condition.x == ychecks whetherxis equal toy.putsprints a line to the console.The
elseblock runs if the condition is false.The statement ends with
end, which is required.
Output:
Since
xandyare both1, this code will print:Yes!You can also use
elsifto add more conditions. You'll learn more about that in Chapter 6.
Ruby’s special objects and comments:
Special value objects:
truefalsenilThe objects
trueandfalseoften serve as return values for conditional expressions. The objectnilis a kind of “nonobject” indicating the absence of a value or result.falseandnilcause a conditional expression to evaluate as false; all other objects (includingtrue, of course, but also including 0 and empty strings) cause it to evaluate to true. More on these in chapter 7.
Default object:
selfThe keyword
selfrefers to the default object. Self is a role that different objects play, depending on the execution context. Method calls that don’t specify a calling object are called on self. More on this in chapter 5.
Put comments in code files:
# A comment x = 1 # A commentComments are ignored by the interpreter.
The variety of Ruby identifiers
Ruby has a small number of identifier types that you’ll want to be able to spot and differentiate from each other at a glance. The identifier family tree looks like this:
Variables:
Local
Instance
Class
Global
Constants
Keywords
Method names
VARIABLES:
Local variables: start with a lowercase letter or an underscore and consist of letters, underscores, and/or digits. x, string, abc, var1, start_value, and firstName are all valid local variable names.
💡Note, however, that the Ruby convention is to use underscores rather than camel case when composing local variable names from multiple words—for example, first_name rather than firstName.
Instance variables: which serve the purpose of storing information within individual objects, always start with a single at-sign (@) and consist thereafter of the same character set as local variables—for example, @age and @last_name.
Class variables: which store information per class hierarchy (again, don’t worry about the semantics at this stage), follow the same rules as instance variables, except that they start with two at-signs—for example, @@running_total.
Global variables: are recognizable by their leading dollar sign ($)—for example, $population. The segment after the dollar sign doesn’t follow local-variable naming conventions; there are global variables called $:, $1, and $/, as well as $stdin and $LOAD_PATH. As long as it begins with a dollar sign, it’s a global variable.
| Type | Ruby convention | Nonconventional |
| Local | first_name | firstName, firstName, _firstName, name1 |
| Instance | @first_name | @First_name, @firstName |
| Class | @@first_name | @@First_name, @@firstName |
| Global | $FIRST_NAME | $first_name, $firstName, $name1 |
CONSTANTS:
Constants begin with an uppercase letter. A, String, FirstName, and STDIN are all valid constant names. The Ruby convention is to use either camel case (FirstName) or underscore-separated all-uppercase words (FIRST_NAME) in composing constant names from multiple words.
KEYWORDS:
Ruby has numerous keywords—predefined, reserved terms associated with specific programming tasks and contexts. Keywords include def (for method definitions), class (for class definitions), if (conditional execution), and ــFILE__ (the name of the file currently being executed). There are about 40 of them, and they’re generally short, single-word (as opposed to underscore-composed) identifiers.
You can see them here: https://docs.ruby-lang.org/en/2.5.0/keywords_rdoc.html
METHOD NAMES:
Names of methods in Ruby follow the same rules and conventions as local variables (except that they can end with ?, !, or =, with significance that you’ll see later).
Method calls, messages, and Ruby objects
Introduction to Functions in Ruby
As your Ruby programs grow, you'll need ways to organize your code and avoid repeating yourself. That’s where functions and classes come in.
What is a Function (Method) in Ruby?
A function (called a method in Ruby) is a reusable block of code that performs a specific task. You define it once and call it whenever you need it.
Example:
def greet(name) puts "Hello, #{name}!" end greet("Ruby") # => Hello, Ruby!
defstarts the method definition.
nameis a parameter.You call it using the method name and pass an argument.
Ruby has many built-in methods, and you can also write your own to make your code cleaner and more modular.
Ruby sees all data structures and values—from simple scalar (atomic) values like integers and strings, to complex data structures like arrays—as objects.
In Ruby, every object can respond to a set of messages. These messages correspond to methods—named actions that the object knows how to perform.
Method calls as messages
A method is like a command or request you send to an object. When you call a method, you're sending a message to an object asking it to do something or give you information.
Using the dot (.) operator
The most common way to send a message (i.e., call a method) is using the dot operator (
.):"100".to_iIn this line:
"100"is a string object and the receiver of the message.
to_iis the method (or message) sent to the string.The result is the integer
100, becauseto_iconverts a string to an integer.Storing the result
You can assign the result of a method call to a variable:
x = "100".to_i # x now holds the integer 100This pattern is common in Ruby:
object.method → resultSo,
"hello".upcasesends theupcasemessage to the string"hello", which returns"HELLO".
Why the double terminology?
You may notice that Rubyists sometimes say things like:
“Calling the
to_imethod”or “Sending the
to_imessage to a string”Aren’t those the same thing?
Not exactly.
While calling a method is the common terminology in most programming languages, Ruby encourages thinking in terms of sending messages. Here's why the distinction matters:
Message vs. Method
Message: A request sent to an object to perform an action (e.g.,
"100".to_i)Method: The actual implementation the object uses to respond to that message
So, when you write:
"100".to_iYou’re sending the message
to_ito the string"100". If the string knows how to handle that message (i.e., it has a method namedto_i), it responds accordingly.But what if it doesn’t?
Dynamic behavior with
method_missingRuby is flexible—if an object doesn’t have a method that matches the message, it doesn't crash right away. Instead, Ruby gives the object a chance to handle the situation using a special method called
method_missing.This is a powerful feature that allows developers to write dynamic code. For example:
class Ghost def method_missing(method_name, *args) puts "You tried to call '#{method_name}', but I'm a ghost!" end end phantom = Ghost.new phantom.disappear # => You tried to call 'disappear', but I'm a ghost!This flexibility is used extensively in frameworks like Ruby on Rails, where method names can be constructed dynamically (e.g.,
find_by_email_and_name) and caught usingmethod_missing.Summary
"Calling a method" is familiar and straightforward.
"Sending a message" is conceptually richer—it captures how Ruby lets objects decide how (or if) they respond.
If no method matches, Ruby gives objects one last chance to respond via
method_missing.
Method Calls, Arguments, and the Power of Objects

In Ruby, methods can take arguments, and—like nearly everything else in Ruby—arguments are also objects.
Example with an argument:
x = "100".to_i(9)
Here, we're calling the
to_imethod on the string"100".We're passing
9as an argument, telling Ruby to interpret"100"as a base-9 number.The result is the decimal equivalent:
81. Soxis now81.Parentheses: optional, but helpful
In Ruby, parentheses around method arguments are usually optional:
"100".to_i 9 # works, but may be hard to readHowever, they are recommended for clarity, especially when:
There are multiple arguments
You're chaining methods
The syntax might otherwise be ambiguous
Many Rubyists use parentheses consistently in method calls just to be safe.
Everything is an Object. Messages are Method Calls.
Ruby programs are made up of objects and messages. As a Ruby developer, your two core activities are:
Defining methods (what you want objects to be able to do)
Calling methods (sending messages to objects to ask them to do something)
The dot operator (
.) connects the message to the object:object.method(arguments)Think of it as:
Send the messagemethodtoobjectwith some arguments.Implicit Receivers: When You Don’t See the Dot
Some method calls don’t show a dot or an object at all. For example:
puts "Hello"There’s no visible object receiving
puts, but it’s still a method call. In this case, the method is being sent to the default object:self.Ruby always has a current
selfduring execution, and any method call without an explicit receiver is automatically sent toself.Objects and Classes
The most important concept in Ruby is the object.
Closely connected to it is the idea of the class—which defines how objects behave:
What methods they respond to
How they are constructed
How they interact with other objects
You’ll dive deeper into classes later, but for now, remember: everything in Ruby revolves around objects, messages, and the methods those objects use to respond.
The Origin of Objects: Classes
In Ruby, every object belongs to a class—a blueprint that defines the object’s behavior and available methods.
What is a class?
A class defines what an object can do:
What methods it responds to
How it's initialized
What kind of data it holds
Every object in Ruby is an instance of exactly one class. For example:
"hello".class # => String [1, 2, 3].class # => Array 5.class # => IntegerWhen you write
"hello", you’re creating a String object—an instance of theStringclass.Built-in vs. Custom Classes
Ruby comes with many built-in classes:
String,Array,Hash,Integer,Float, and so on.But you can also define your own classes:
class Dog def bark "Woof!" end end fido = Dog.new puts fido.bark # => "Woof!"Here,
Dogis a custom class, andfidois an object (instance) of that class.Modifying Built-in Classes (Monkey Patching)
Ruby is highly flexible—you can even change the behavior of built-in classes:
class String def shout self.upcase + "!" end end puts "hello".shout # => "HELLO!"While this is possible, it’s usually discouraged in production code because it can lead to unexpected bugs or conflicts—especially in large applications or libraries.
⚠️ You’ll explore the risks and use cases of this technique (called monkey patching) in Chapter 13.
Writing and saving a simple program
At this point, you can start creating program files in the Ruby sample code directory you created a little while back. Your first program will be a Celsius-to-Fahrenheit temperature converter.
The first version will be simple; the focus will be on the file-creation and programrunning processes, rather than any elaborate program logic.

Using a plain-text editor, type the code from the following listing into a text file and save it under the filename c2f.rb in your sample code directory.
You now have a complete (albeit tiny) Ruby program on your disk, and you can run it.
Feeding the program to Ruby
Running a Ruby program involves passing the program’s source file (or files) to the Ruby interpreter, which is called ruby. You’ll do that now ... sort of. You’ll feed the program to ruby, but instead of asking Ruby to run the program, you’ll ask it to check the program code for syntax errors.
$ ruby -cw c2f.rb
The -cw command-line flag is shorthand for two flags: -c and -w. The -c flag means check for syntax errors. The -w flag activates a higher level of warning: Ruby will fuss at you if you’ve done things that are legal Ruby but are questionable on grounds other than syntax.
RUNNING THE PROGRAM
$ ruby c2f.rb

The result of the calculation is correct, but having the output spread over three lines looks bad.
SECOND CONVERTER ITERATION
The problem can be traced to the difference between the puts command and the print command. puts adds a newline to the end of the string it prints out, if the string doesn’t end with one already. print, on the other hand, prints out the string you ask it to and then stops; it doesn’t automatically jump to the next line. To fix the problem, change the first two puts commands to print:
print "The result is "
print fahrenheit
puts "."
Summary
In this chapter, you’ve taken your first steps into the Ruby programming language by learning its foundational concepts:
Ruby is an object-oriented language: Everything is an object—even numbers, strings, and
nil.Method calls are message sends: Ruby encourages you to think of methods as messages sent to objects, using the dot (
.) operator.Variables are flexible: They hold references to objects, and you can reassign them to different types anytime.
You explored key Ruby syntax:
Arithmetic and comparison operations
Input/output with
puts,print,p, andgetsConditional execution with
if,else, andelsif
selfrefers to the current default object: This plays a central role in how methods are called and how code is evaluated.Ruby classes define object behavior: Every object is an instance of a class, but Ruby’s flexibility allows objects to be extended beyond their original class.
You wrote and ran your first Ruby program, learning how to use the interpreter and correct formatting issues using
putsandprint.
As you continue reading, you’ll go deeper into Ruby’s object model, methods, control structures, and more advanced patterns like dynamic behavior with method_missing. With this foundation, you're now ready to write more powerful and idiomatic Ruby code.
Final Note
If you enjoyed this article or found it helpful, I’d love to hear your thoughts!
Feel free to share your feedback—especially if you notice anything that needs correction. I’m always happy to improve the content.
Thanks for reading, and stay tuned for the next articles in this series as we continue exploring The Well-Grounded Rubyist together.
