Skip to main content

Command Palette

Search for a command to run...

Exploring The Well-Grounded Rubyist, Third Edition: Part 1

Summary of Chapter 1: Ruby Programming Fundamentals

Published
20 min read
Exploring The Well-Grounded Rubyist, Third Edition: Part 1

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:


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:

  1. Arithmetic operations:

    1. Addition: 1 + 2

    2. Subtraction: 4 - 3

    3. Multiplication: 3 * 7

    4. Division: 1 / 3

      All 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 number
      

      You’ll often encounter floats in Ruby when:

      • Performing division that results in a non-integer value (e.g., 5 / 2.0 gives 2.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 / 2 or 5 / 2.0).

  2. Assignment operations in Ruby:

    1. x = 1

    2. string = "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 = 1 assigns the integer object 1 to the variable x.

      • string = "Hello World!" assigns the string object "Hello World!" to the variable string.

      💡 Note: Variables in Ruby don’t have a fixed type—they simply refer to an object. That means you can reassign x to 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 World
      

      Or use interpolation:

      puts "#{first} #{second}"  # => Hello World
      

      Accessing 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.

  3. Comparing values in Ruby:

    • To compare two values for equality, Ruby uses the double equals sign (==):

        x == y
      

      This checks whether the value of x is equal to the value of y.

      ⚠️ Important: Don't confuse this with a single equals sign (=), which is used for assignment (e.g., x = y assigns y to x).

      Example:

      x = 5
      y = 5
      x == y   # => true
      
      x = 5
      y = 6
      x == y   # => false
      

      You 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.

  4. Convert a numeric string to a number:

    1. x = "100".to_i

    2. s = "100"

    3. x = s.to_i

      To 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:

  1. Print something to the screen:

    • puts adds a newline to the string it outputs if there isn’t one at the end already; print doesn’t.

      print prints 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.)

      p outputs an inspect string, which may contain extra information about what it’s printing.

  2. Get a line of keyboard input:

    • gets

    • string = gets

      You can assign the input line directly to a variable (the variable string in the second example). Try gets in an irb session, and a cursor will wait for you to enter input.


Conditional execution:

  •   x = 1
      y = 1
      if x == y
       puts "Yes!"
      else
       puts "No!"
      end
    

    In Ruby, you can run different blocks of code depending on whether a condition is true or false.

    • The if keyword starts a condition.

    • x == y checks whether x is equal to y.

    • puts prints a line to the console.

    • The else block runs if the condition is false.

    • The statement ends with end, which is required.

    Output:

    Since x and y are both 1, this code will print:

    Yes!
    

    You can also use elsif to add more conditions. You'll learn more about that in Chapter 6.


Ruby’s special objects and comments:

  1. Special value objects:

    1. true

    2. false

    3. nil

      The objects true and false often serve as return values for conditional expressions. The object nil is a kind of “nonobject” indicating the absence of a value or result. false and nil cause a conditional expression to evaluate as false; all other objects (including true, of course, but also including 0 and empty strings) cause it to evaluate to true. More on these in chapter 7.

  2. Default object:

    • self

      The keyword self refers 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.

  3. Put comments in code files:

    •   # A comment
        x = 1 # A comment
      

      Comments 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.

TypeRuby conventionNonconventional
Localfirst_namefirstName, 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!
  • def starts the method definition.

  • name is 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_i

In this line:

  • "100" is a string object and the receiver of the message.

  • to_i is the method (or message) sent to the string.

  • The result is the integer 100, because to_i converts 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 100

This pattern is common in Ruby:
object.method → result

So, "hello".upcase sends the upcase message to the string "hello", which returns "HELLO".

Why the double terminology?

You may notice that Rubyists sometimes say things like:

  • “Calling the to_i method”

  • or “Sending the to_i message 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_i

You’re sending the message to_i to the string "100". If the string knows how to handle that message (i.e., it has a method named to_i), it responds accordingly.

But what if it doesn’t?

Dynamic behavior with method_missing

Ruby 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 using method_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_i method on the string "100".

  • We're passing 9 as an argument, telling Ruby to interpret "100" as a base-9 number.

  • The result is the decimal equivalent: 81. So x is now 81.

Parentheses: optional, but helpful

In Ruby, parentheses around method arguments are usually optional:

"100".to_i 9  # works, but may be hard to read

However, 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:

  1. Defining methods (what you want objects to be able to do)

  2. 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 message method to object with 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 self during execution, and any method call without an explicit receiver is automatically sent to self.

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         # => Integer

When you write "hello", you’re creating a String object—an instance of the String class.

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, Dog is a custom class, and fido is 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, and gets

    • Conditional execution with if, else, and elsif

  • self refers 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 puts and print.

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.

The Well-Grounded Rubyist

Part 1 of 1

In this series, I will share my journey through the book The Well-Grounded Rubyist (3rd Edition) by David A. Black and Joseph Leo III. Each article summarizes the key takeaways, concepts, and practical examples from every chapter.