2

Flexible APIs with Keyword Arguments and Blocks

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

en/topic_02_methods_keywords_blocks

Overview

Why this topic matters ๐Ÿ’ก

Ruby methods can be compact while still supporting flexible calling styles. Keyword arguments communicate meaning at the call site, and blocks let callers inject behavior without creating ceremony-heavy callback interfaces.

Learning outcomes ๐ŸŽฏ

By the end of this topic, students should be able to:

  • explain the difference between positional and keyword arguments;
  • use default values to keep APIs convenient without multiplying overloads;
  • pass behavior with blocks for lightweight customization;
  • explain when a block is clearer than introducing a new class;
  • compare Ruby block-based style with common Java callback patterns.

Assessment focus โœ…

Students should be able to build a small API that remains readable for both the author and the caller.

Short Note

A good Ruby API often reads almost like a sentence. Keyword arguments help a caller understand meaning without opening the method body. Blocks let the caller supply small pieces of behavior without introducing heavy structure.

For Java developers, this is an important contrast. In Java, flexibility often invites more types, more overloads, or more scaffolding. In Ruby, the language encourages small, expressive call sites.

Ruby beauty in this topic:

  • keyword arguments make intent visible;
  • default values remove repetitive call-site noise;
  • blocks support lightweight customization.

Ruby caution in this topic:

  • too many keyword arguments can make a method ambiguous;
  • if the block does too much work, it becomes hard to reason about.

Reflection prompt:

  • When does a block make the code more expressive, and when should behavior move into an object?

Worked Examples

Example 1: Formatting API for readable call sites ๐Ÿ’ก

A formatter is a realistic small API because callers often care about readability more than implementation detail.

formatter.wrap("warning", left: "(", right: ")")
worked_examples.md
ruby

This is better than positional punctuation arguments because the call explains itself.

Example 2: Notification hook with a block ๐Ÿ’ก

Blocks are useful when the framework owns the iteration and the caller owns the side effect.

notifier.notify_all(users) do |message|
  audit_log << message
end
worked_examples.md
ruby

Why this is useful:

  • the notifier controls traversal;
  • the caller controls what happens with each message;
  • the code avoids interface scaffolding for a very small behavior slot.

Cheatsheet

Keyword arguments

def greet(name, punctuation: "!")
  "Hello, #{name}#{punctuation}"
end
cheatsheet.md
ruby

Defaults

greet("Mina")
greet("Mina", punctuation: ".")
cheatsheet.md
ruby

Yielding to a block

def notify_all(users)
  users.each { |user| yield "Notify #{user}" }
end
cheatsheet.md
ruby

Useful questions ๐Ÿค”

  • Does the caller need to understand argument meaning at a glance?
  • Is the injected behavior small enough for a block?

Study Guide

Topic purpose ๐ŸŽฏ

Study how Ruby lets methods stay flexible without becoming verbose. The emphasis is not syntax memorization, but API readability and behavior injection.

Study sequence ๐Ÿชœ

  1. Read overview.md.
  2. Read shortnote.md and compare Ruby's style to Java overloads or callback types.
  3. Review worked_examples.md before touching the exercises.
  4. Use cheatsheet.md while reading example.rb.
  5. Complete the basic exercise around keyword arguments.
  6. Complete the advanced exercise around blocks.

What to notice ๐Ÿ”Ž

  • Keywords document the method call.
  • Defaults preserve convenience.
  • Blocks are most elegant when the injected behavior is small and local.

Reflection questions ๐Ÿค”

  • Why is left: and right: a better API than multiple punctuation positions?
  • What does a block communicate better than a callback class in this exercise?
  • At what point would you replace the block with an object collaborator?

Source Files and Tests

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

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

class Greeter
  def greet(name, punctuation: "!")
    "Hello, #{name}#{punctuation}"
  end

  def greet_many(names)
    names.map { |name| greet(name) }
  end
end
en/topic_02_methods_keywords_blocks/example.rb
Ruby course source
# STUDENT TASK (BASIC)
# Topic: topic_02_methods_keywords_blocks
#
# What to do:
# - Implement or improve the class/methods in this file.
# - Read tests in tests/topic_02_methods_keywords_blocks_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 Formatter
  def wrap(text, left: "[", right: "]")
    "#{left}#{text}#{right}"
  end
end
en/topic_02_methods_keywords_blocks/basic_exercise.rb
Ruby course source
# STUDENT TASK (ADVANCED)
# Topic: topic_02_methods_keywords_blocks
#
# Academic purpose:
# - Study blocks as a lightweight form of behavior injection.
# - Contrast Ruby's block style with heavier callback abstractions common in Java.
#
# Real-world use case:
# - Notification systems often own the list of users but let the caller decide what to do
#   with each message: log it, enqueue it, send it, or collect it for later.
# - A block is useful when the framework controls iteration and the caller controls the side effect.
#
# Why Ruby is beautiful here:
# - The method can stay tiny while remaining flexible.
# - Callers can customize behavior without defining a new class.
# - The code reads close to the problem statement: "for each user, do this".
#
# What to do:
# - Complete the challenge behavior requested by the guide.
# - Focus on what the block buys you in API design, not just how `yield` works.
# - Use tests in tests/topic_02_methods_keywords_blocks_spec.rb under the "advanced exercise" examples.
#
# Expected outcome:
# - Advanced tests pass and you can explain when a block is the right abstraction.

class Notifier
  def notify_all(users)
    return [] unless block_given?

    users.map { |u| yield "Notify #{u}" }
  end
end
en/topic_02_methods_keywords_blocks/adv_exercise.rb
Ruby course source
# ANSWER KEY (BASIC)
# Topic: topic_02_methods_keywords_blocks
#
# Solution idea:
# - Use keyword arguments so the call site explains the meaning of each wrapper.
# - Keep defaults in the method signature instead of branching inside the method body.

class Formatter
  def wrap(text, left: "[", right: "]")
    "#{left}#{text}#{right}"
  end
end
en/topic_02_methods_keywords_blocks/answer_basic_exercise.rb
Ruby course source
# ANSWER KEY (ADVANCED)
# Topic: topic_02_methods_keywords_blocks
#
# Solution idea:
# - The notifier owns the iteration over users.
# - The caller owns what to do with each generated message.
# - Returning an empty array when no block is given keeps the method safe for the exercise.

class Notifier
  def notify_all(users)
    return [] unless block_given?

    users.map { |user| yield "Notify #{user}" }
  end
end
en/topic_02_methods_keywords_blocks/answer_adv_exercise.rb
Ruby course source
# This spec is your learning companion for topic_02_methods_keywords_blocks.
#
# 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_02_methods_keywords_blocks" do
  describe "EXAMPLE purpose: understand the reference implementation" do
      it "greets with default and custom punctuation" do
        g = Greeter.new
        expect(g.greet("Mina")).to eq("Hello, Mina!")
        expect(g.greet("Mina", punctuation: ".")).to eq("Hello, Mina.")
      end
  end

  describe "BASIC EXERCISE purpose: implement the comparable task" do
      it "wraps text with keyword defaults" do
        f = Formatter.new
        expect(f.wrap("ruby")).to eq("[ruby]")
        expect(f.wrap("ruby", left: "<", right: ">")) .to eq("<ruby>")
      end
  end

  describe "ADVANCED EXERCISE purpose: solve challenge and edge cases" do
      it "yields notifications and handles empty list" do
        n = Notifier.new
        out = n.notify_all(%w[a b]) { |msg| msg.upcase }
        expect(out).to eq(["NOTIFY A", "NOTIFY B"])
        expect(n.notify_all([]) { |msg| msg }).to eq([])
      end
  end
end
en/topic_02_methods_keywords_blocks/tests/topic_02_methods_keywords_blocks_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_02_methods_keywords_blocks/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.