1

Ruby Basics and the First TDD Cycle

Ruby source files are converted directly from the English source tree.

en/topic_01_ruby_basics

Overview

I begin the course with a very small shift that turns out to matter a great deal: Ruby code is often built from expressions rather than ceremony. A method can say what it means directly, and a test can describe behavior before the implementation exists.

That may sound minor, but it changes how the code feels. If you are coming from Java, you are used to more visible scaffolding around even simple behavior. In Ruby, I want you to see how some of that scaffolding can disappear without the thinking becoming weaker.

What I Want You To Learn Here

By the end of this topic, I want you to be able to:

  • explain what it means for Ruby to be expression-oriented
  • write a small class with simple behavior and minimal ceremony
  • use RSpec to describe behavior before implementation
  • separate normal behavior from validation behavior
  • explain why clear names matter even in tiny examples

What You Need Before Starting

You only need a few basics:

  • some familiarity with Java classes and methods
  • comfort reading unit tests
  • no prior Ruby experience

If you do not come from Java, that is fine. The comparisons will still help, and the exercises are small enough to learn from directly.

What I Want You To Notice

The arithmetic in this topic is not the real point. I am using simple arithmetic because I do not want the domain to distract you from the design.

What I really want you to notice is this:

  • a small method can still express a clear idea
  • a test can make the intended behavior visible before the code exists
  • a short Ruby method is only good when the naming stays strong

If the Ruby version ends up shorter than a Java version, that is useful. If it ends up shorter and clearer, that is the real win.

Short Note

One of the first things I want you to feel in Ruby is that methods return the last evaluated expression. That sounds like a small rule, but it changes the texture of the code.

In Java, even simple methods often carry visible scaffolding: type declarations, explicit returns, and a little extra structure around the real idea. Ruby lets a small method say the idea more directly.

def double(n)
  n * 2
end
shortnote.md
ruby

I am not showing you that method because it is clever. I am showing it because it is direct. Ruby often reads best when the parts that are only ceremony fall away and the parts carrying meaning remain visible.

This topic is not really about arithmetic. I am using arithmetic to teach design discipline at a very small scale:

  • choose a clear object name
  • choose a method name that describes behavior
  • write the expectation first
  • add validation only when the domain asks for it

Ruby's strength in a topic like this is that a class can express one idea with very little boilerplate. The caution matters just as much: short code is not automatically good code. If the names are weak, Ruby's concision can hide confusion instead of removing it.

As you work through the exercises, keep one question in mind: what became clearer in the Ruby version, and what only became shorter?

Worked Examples

Example 1: Even A Small Method Needs A Decision

I like using division as an early teaching example because the interesting part is not the math. The interesting part is what the method should do when the divisor is zero.

class SafeDivider
  def divide(a, b)
    raise ArgumentError, "divisor cannot be zero" if b.zero?

    a.to_f / b
  end
end
worked_examples.md
ruby

I use this example because it reads from top to bottom. The guard clause names the invalid condition early, and the normal path remains easy to see.

Just as importantly, it introduces a habit I want to keep throughout the course: even a small API needs a design decision about failure.

Example 2: TDD Keeps The Design Honest

In a first topic, it is easy to think tests are only there to catch arithmetic mistakes. I want you to see something broader. A small test helps separate:

  • the object's responsibility
  • the expected behavior
  • the edge case that changes the behavior

That habit becomes more valuable as the course gets more complex. The arithmetic is small. The design habit is not.

Cheatsheet

I do not expect you to memorize everything in this topic on the first pass. I want this sheet to sit nearby while you read and code.

Core Form

class Calculator
  def add(a, b)
    a + b
  end
end
cheatsheet.md
ruby

Basic Testing Pattern

RSpec.describe Calculator do
  it "adds two numbers" do
    expect(Calculator.new.add(2, 3)).to eq(5)
  end
end
cheatsheet.md
ruby

Error Expectation

expect { divider.divide(10, 0) }.to raise_error(ArgumentError, /zero/)
cheatsheet.md
ruby

Ideas I Want You To Remember

  • Ruby returns the last expression.
  • Constructor calls are written as ClassName.new.
  • Even a small method should communicate intent through naming.

Study Guide

In this first topic, I want you to learn the smallest useful Ruby workflow: read the behavior, describe it in a test, implement the method, and then handle an invalid case explicitly.

Suggested Order

I recommend this sequence:

  1. Read overview.md to see what this topic is trying to teach.
  2. Read shortnote.md and notice the Java-to-Ruby mindset shift.
  3. Use worked_examples.md to see why validation matters even in tiny code.
  4. Skim cheatsheet.md while reading example.rb.
  5. Run the tests and keep the example behavior green.
  6. Implement basic_exercise.rb.
  7. Implement adv_exercise.rb.

What I Want You To Notice

As you work, pay attention to these ideas:

  • Ruby methods do not need explicit return in common cases.
  • A short method can still express a strong design choice.
  • Guard clauses keep exceptional behavior from obscuring the happy path.

Questions To Keep In Mind

As you finish the topic, I want you to think about these questions:

  • Why is SafeDivider a better teaching example than a second arithmetic method with no edge case?
  • What does the RSpec example communicate that an informal comment would not?
  • When does concise Ruby become too implicit for a beginner?

Source Files and Tests

Ruby source files are converted directly from the English source tree.

# EXAMPLE CODE
# Topic: topic_01_ruby_basics
#
# Purpose:
# - This file demonstrates reference implementation for the concept.
# - It should pass tests from the beginning.
# - Read and understand it before solving exercises.

class Calculator
  def add(a, b)
    a + b
  end
end
en/topic_01_ruby_basics/example.rb
Ruby course source
# STUDENT TASK (BASIC)
# Topic: topic_01_ruby_basics
#
# What to do:
# - Implement or improve the class/methods in this file.
# - Read tests in tests/topic_01_ruby_basics_spec.rb under the "basic exercise" examples.
# - Make tests pass without breaking the example/advanced sections.
#
# Expected outcome:
# - You can run this topic tests and see all examples green after implementation.

class Multiplier
  def multiply(a, b)
    a * b
  end
end
en/topic_01_ruby_basics/basic_exercise.rb
Ruby course source
# STUDENT TASK (ADVANCED)
# Topic: topic_01_ruby_basics
#
# Academic purpose:
# - Learn that even a tiny Ruby method still expresses a domain decision.
# - Practice guard clauses as a readable way to separate invalid input from the happy path.
#
# Real-world use case:
# - Division-like operations appear in pricing, reporting, analytics, and rate calculations.
# - In those settings, silently accepting a zero divisor usually hides a bug upstream.
# - Raising a focused exception is often better than producing misleading data.
#
# Why Ruby is beautiful here:
# - A guard clause states the invalid condition in one line.
# - The normal behavior remains visually dominant.
# - The method stays short without losing intent.
#
# What to do:
# - Complete the challenge behavior requested by the guide.
# - Explain to yourself why raising is preferable to returning a magic value here.
# - Use tests in tests/topic_01_ruby_basics_spec.rb under the "advanced exercise" examples.
#
# Expected outcome:
# - Advanced tests pass and you can justify the validation strategy.

class SafeDivider
  def divide(a, b)
    raise ArgumentError, "divisor cannot be zero" if b.zero?

    a.to_f / b
  end
end
en/topic_01_ruby_basics/adv_exercise.rb
Ruby course source
# ANSWER KEY (BASIC)
# Topic: topic_01_ruby_basics
#
# Solution idea:
# - The behavior is intentionally small.
# - The point is to see that Ruby can express a clear method without ceremony.
# - Multiplication delegates to Ruby's numeric operator directly.

class Multiplier
  def multiply(a, b)
    a * b
  end
end
en/topic_01_ruby_basics/answer_basic_exercise.rb
Ruby course source
# ANSWER KEY (ADVANCED)
# Topic: topic_01_ruby_basics
#
# Solution idea:
# - Separate invalid input from the normal calculation path.
# - A guard clause makes the failure policy explicit at the top of the method.
# - Returning a float keeps the result predictable for division.

class SafeDivider
  def divide(a, b)
    raise ArgumentError, "divisor cannot be zero" if b.zero?

    a.to_f / b
  end
end
en/topic_01_ruby_basics/answer_adv_exercise.rb
Ruby course source
# This spec is your learning companion for topic_01_ruby_basics.
#
# How to use this file:
# 1) Run tests and observe failures/successes.
# 2) Keep EXAMPLE specs green from the beginning.
# 3) Implement BASIC exercise until BASIC specs pass.
# 4) Implement ADVANCED exercise and pass edge cases.
#
# Expected final result:
# - All examples in this file pass.
# - You understand both the concept and the implementation tradeoffs.

require_relative "../example"
require_relative "../basic_exercise"
require_relative "../adv_exercise"

RSpec.describe "topic_01_ruby_basics" do
  describe "EXAMPLE purpose: understand the reference implementation" do
      it "adds numbers in Calculator" do
        expect(Calculator.new.add(2, 3)).to eq(5)
      end
  end

  describe "BASIC EXERCISE purpose: implement the comparable task" do
      it "multiplies positive/zero/negative numbers" do
        m = Multiplier.new
        expect(m.multiply(3, 4)).to eq(12)
        expect(m.multiply(7, 0)).to eq(0)
        expect(m.multiply(-2, 5)).to eq(-10)
      end
  end

  describe "ADVANCED EXERCISE purpose: solve challenge and edge cases" do
      it "divides safely and raises for zero divisor" do
        d = SafeDivider.new
        expect(d.divide(10, 2)).to eq(5.0)
        expect { d.divide(10, 0) }.to raise_error(ArgumentError, /zero/)
      end
  end
end
en/topic_01_ruby_basics/tests/topic_01_ruby_basics_spec.rb
Ruby course source
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"

exec "${ROOT_DIR}/run_tests.sh" "$(basename "${SCRIPT_DIR}")"
en/topic_01_ruby_basics/run_topic_tests.sh
Ruby course source

Study Prompts

  1. Read the spec first and identify which behaviors belong to the example, basic exercise, and advanced exercise.

  2. Attempt the exercises before opening the answer files, then compare the design choices.