Checkpoint: OOP & Stdlib

Reviewed & published by Brayan K

This checkpoint ties together Ruby's object-oriented features and standard-library tools — inheritance, custom exceptions, dates, and serialization — into one realistic build challenge.

Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Review the recap, attempt the challenge before peeking, then test yourself with the quiz.

What This Checkpoint Covers

1️⃣ Recap: Inheritance, super & Pattern Matching

A subclass inherits its parent's behaviour and can override methods, calling super to reuse the parent's version. Constants live on the class, and case/in deconstructs data by shape.

# Recap: inheritance, super, class methods, constants, pattern matching.
class Animal
  SPECIES_COUNT = 0   # a constant lives on the class
  attr_reader :name
  def initialize(name)
    @name = name
  end
  def speak = "..."
  def describe = "#{name} says #{speak}"
end

class Dog < Animal
  def speak = "Woof"               # override
  def describe = "Dog: #{super}"   # call parent's version
end

puts Dog.new("Rex").describe

# Pattern matching on a small data object:
result = { status: :ok, code: 200 }
case result
in { status: :ok, code: }
  puts "OK with code #{code}"
in { status: :error }
  puts "failed"
end

2️⃣ Recap: Exceptions, Dates & JSON

Custom errors give failures a meaningful name, require 'date' brings calendar handling, and to_json serializes hashes for transport — the standard-library trio you'll reach for constantly.

# Recap: custom exceptions, date/time, JSON.
require 'json'
require 'date'

class OutOfStockError < StandardError; end

def order(qty, stock)
  raise OutOfStockError, "only #{stock} left" if qty > stock
  qty
end

begin
  order(5, 2)
rescue OutOfStockError => e
  puts "Order failed: #{e.message}"
end

event = { name: "Sale", on: Date.new(2026, 6, 18).to_s }
puts event.to_json

3️⃣ Build Challenge: Shapes to JSON

Now combine it all: a class hierarchy with overridden area, a custom validation error, and JSON serialization of the whole collection. Try it yourself first — the solution is hidden below.

# 🎯 BUILD CHALLENGE: Shapes -> JSON
# Save as shapes.rb and run with: ruby shapes.rb
#
# 1. require 'json'.
# 2. Define InvalidShapeError < StandardError.
# 3. Build a Shape base class with: name, an area method that
#    raises NotImplementedError, and a to_h that returns
#    { name:, area: } with area rounded to 2 dp.
# 4. Subclass Circle(radius) — raise InvalidShapeError if radius <= 0,
#    call super("circle"), area = PI * r**2.
# 5. Subclass Rectangle(w, h) — super("rectangle"), area = w * h.
# 6. Map an array of shapes through to_h and JSON.pretty_generate it.
# 7. Show the custom error firing when you create Circle.new(-1).
#
# ✅ Expected output:
#   [
#     {
#       "name": "circle",
#       "area": 12.57
#     },
#     {
#       "name": "rectangle",
#       "area": 12
#     }
#   ]
#   Rejected: radius must be positive

# your code here

Full Solution

require 'json'

class InvalidShapeError < StandardError; end

class Shape
  attr_reader :name
  def initialize(name)
    @name = name
  end
  def area
    raise NotImplementedError, "subclass must define area"
  end
  def to_h
    { name: name, area: area.round(2) }
  end
end

class Circle < Shape
  def initialize(radius)
    raise InvalidShapeError, "radius must be positive" if radius <= 0
    super("circle")
    @radius = radius
  end
  def area = Math::PI * @radius**2
end

class Rectangle < Shape
  def initialize(w, h)
    super("rectangle")
    @w = w
    @h = h
  end
  def area = @w * @h
end

shapes = [Circle.new(2), Rectangle.new(3, 4)]

# Serialize the whole hierarchy to JSON:
puts JSON.pretty_generate(shapes.map(&:to_h))

# Custom error in action:
begin
  Circle.new(-1)
rescue InvalidShapeError => e
  puts "Rejected: #{e.message}"
end

# ---- OUTPUT ----
# [
#   { "name": "circle", "area": 12.57 },
#   { "name": "rectangle", "area": 12 }
# ]
# Rejected: radius must be positive

📝 Checkpoint Quiz

It calls the parent's method of the same name, forwarding the same arguments the current method received.

So a plain rescue catches it, while system-level Exception subclasses (like Ctrl-C) still propagate.

Give it a to_h (or to_json) that returns only primitives — strings, numbers, arrays, and hashes.

It matches by structure (deconstruction). With no matching branch and no else, it raises NoMatchingPatternError.

require 'date' for Date/DateTime; Time is built in and needs no require.

Three test methods ran, making four assertions total (one test made two assertions) — and all passed.

🎉 Checkpoint Complete!

Practice quiz

What does a bare super (no parentheses) do?

  • Passes no arguments to the parent
  • Calls a method named 'super'
  • Forwards the same arguments the current method received to the parent's method
  • Creates a new instance

Answer: Forwards the same arguments the current method received to the parent's method. Bare super forwards the exact arguments the current method received. super() passes none, and super(a, b) passes exactly those.

In the Circle subclass, what does super("circle") do?

  • Passes just the string 'circle' to Shape#initialize
  • Forwards radius to the parent
  • Skips the parent constructor
  • Raises an error

Answer: Passes just the string 'circle' to Shape#initialize. super("circle") deliberately passes one argument up to Shape#initialize, setting the name, regardless of Circle's own arguments.

Why raise NotImplementedError in the base Shape#area method?

  • To make Shape run faster
  • To return a default area of 0
  • To skip the subclass
  • To mark Shape as abstract so calling area on a raw Shape fails loudly

Answer: To mark Shape as abstract so calling area on a raw Shape fails loudly. It documents the contract 'every shape must define area' and fails clearly if a subclass forgets to override it.

Why should a custom exception inherit from StandardError rather than Exception?

  • StandardError is faster
  • A plain rescue catches StandardError, while Exception subclasses like Ctrl-C still propagate
  • Exception cannot be subclassed
  • StandardError prints a stack trace automatically

Answer: A plain rescue catches StandardError, while Exception subclasses like Ctrl-C still propagate. A bare rescue catches StandardError and its descendants but not system-level Exceptions like SystemExit or Interrupt.

How do you make a custom object JSON-serializable?

  • Give it a to_h (or to_json) that returns only primitives
  • Add a to_s method
  • Inherit from JSON::Object
  • Nothing; JSON handles any object

Answer: Give it a to_h (or to_json) that returns only primitives. JSON only understands strings, numbers, booleans, null, arrays, and hashes, so convert the object via to_h returning those primitives.

What does shapes.map(&:to_h) produce before JSON.pretty_generate?

  • A single hash
  • A JSON string
  • A list of hashes, one per shape
  • An array of strings

Answer: A list of hashes, one per shape. map(&:to_h) calls to_h on each shape, turning the array of objects into an array of hashes ready for JSON.

What does case/in (pattern matching) compare with?

  • === case-equality
  • Structure, by deconstructing the value
  • Object identity
  • String equality only

Answer: Structure, by deconstructing the value. case/in matches by structure (deconstruction), binding variables like { status: :ok, code: } from the shape of the data.

Which error fires when case/in has no matching branch and no else?

  • NoMethodError
  • ArgumentError
  • StandardError
  • NoMatchingPatternError

Answer: NoMatchingPatternError. With no matching in-clause and no else, case/in raises NoMatchingPatternError.

Which require do you need for Date, and what about Time?

  • require 'date' for both
  • require 'date' for Date/DateTime; Time is built in and needs no require
  • require 'time' for both
  • Neither needs a require

Answer: require 'date' for Date/DateTime; Time is built in and needs no require. Date and DateTime live in the date library (require 'date'), but Time is part of core Ruby.

A Minitest summary reads '3 runs, 4 assertions'. What does it tell you?

  • 3 files were loaded
  • Four tests failed
  • Three test methods ran, making four assertions total
  • Three assertions passed and one failed

Answer: Three test methods ran, making four assertions total. Runs counts test methods (3) and assertions counts total checks (4) — one test made two assertions.

Continue this course

Frequently asked questions

When does super pass arguments automatically?

A bare super (no parentheses) forwards the exact arguments the current method received to the parent's method of the same name. super() with empty parentheses passes no arguments, and super(a, b) passes exactly those. This distinction matters in initialize: super forwards everything, while super("circle") deliberately passes just one argument up to Shape#initialize.

Why raise NotImplementedError in the base class?

It turns the base Shape into an 'abstract' class: instances exist, but calling area on a raw Shape (or a subclass that forgot to define it) raises a clear error instead of silently misbehaving. This documents the contract — 'every shape must define area' — and fails loudly during development. Subclasses like Circle and Rectangle satisfy the contract by overriding area.

How does an object become JSON-serializable?

JSON only understands strings, numbers, booleans, null, arrays, and hashes. To serialize a custom object, give it a to_h (or to_json) that converts it into those primitives — here to_h returns { name:, area: }. Then JSON.generate or JSON.pretty_generate can handle it. Mapping a collection with map(&:to_h) turns a list of objects into a list of hashes ready for JSON.

Should a custom exception inherit from StandardError or Exception?

StandardError, almost always. A bare rescue catches StandardError and its descendants, but not the more critical Exception subclasses like SystemExit or Interrupt (Ctrl-C). Inheriting your InvalidShapeError from StandardError means ordinary rescue clauses catch it as expected, while leaving those system-level exceptions free to propagate and terminate the program when they should.