Chapter 8

Patterns in the Wild

This final chapter showcases patterns drawn from real Rugo libraries and projects. These are the idioms that emerge when you put all the pieces together.

This final chapter showcases patterns drawn from real Rugo libraries and projects. These are the idioms that emerge when you put all the pieces together.

The Collection Wrapper

One of the most reusable patterns in Rugo: wrap an array in a hash with iterator methods. This is exactly how the Rugh GitHub client exposes API results.

# A reusable collection wrapper — idiomatic Rugo pattern

def wrap(items)
  col = {items: items, length: len(items)}

  col["each"] = fn(callback)
    for item in items
      callback(item)
    end
  end

  col["map"] = fn(callback)
    result = []
    for item in items
      result = append(result, callback(item))
    end
    return wrap(result)
  end

  col["select"] = fn(callback)
    result = []
    for item in items
      if callback(item)
        result = append(result, item)
      end
    end
    return wrap(result)
  end

  return col
end

names = wrap(["Alice", "Bob", "Charlie", "Dave"])
puts "Count: #{names.length}"

names.each(fn(name)
  puts("Hello, #{name}!")
end)

short = names.select(fn(n) len(n) <= 4 end)
puts "Short names: #{short.length}"
short.each(fn(n) puts(n) end)
Count: 4
Hello, Alice!
Hello, Bob!
Hello, Charlie!
Hello, Dave!
Short names: 2
Bob
Dave

The key insight: select returns a new wrapped collection, so you can chain operations. map does the same. This is the Rugo equivalent of Ruby's Enumerable — built from nothing but hashes and lambdas.

The Builder Pattern

Method chaining works naturally when each method returns self (or rather, the hash). This pattern is great for constructing complex objects step by step.

use "str"

def query_builder(table)
  q = {
    __table__: table,
    __conditions__: [],
    __order__: nil,
    __limit__: nil
  }

  q["where"] = fn(condition)
    q.__conditions__ = append(q.__conditions__, condition)
    return q
  end

  q["order_by"] = fn(field)
    q.__order__ = field
    return q
  end

  q["limit"] = fn(n)
    q.__limit__ = n
    return q
  end

  q["to_sql"] = fn()
    sql = "SELECT * FROM " + q.__table__
    if len(q.__conditions__) > 0
      sql += " WHERE " + str.join(q.__conditions__, " AND ")
    end
    if q.__order__ != nil
      sql += " ORDER BY " + q.__order__
    end
    if q.__limit__ != nil
      sql += " LIMIT " + q.__limit__
    end
    return sql
  end

  return q
end

sql = query_builder("users").where("age >= 18").where("active = 1").order_by("name").limit("10").to_sql()
puts sql
SELECT * FROM users WHERE age >= 18 AND active = 1 ORDER BY name LIMIT 10

Each method mutates the hash and returns it, enabling the fluid interface. The Gummy ORM uses this same pattern for its query building.

Inline Tests

Rugo borrows from Rust: you can embed tests right next to your code. rugo run ignores them; rugo rats executes them. Keep your tests close to what they test.

use "test"

def add(a, b)
  return a + b
end

def factorial(n)
  if n <= 1
    return 1
  end
  return n * factorial(n - 1)
end

puts add(2, 3)
puts factorial(5)

rats "add works correctly"
  test.assert_eq(add(1, 2), 3)
  test.assert_eq(add(-1, 1), 0)
  test.assert_eq(add(0, 0), 0)
end

rats "factorial computes correctly"
  test.assert_eq(factorial(0), 1)
  test.assert_eq(factorial(1), 1)
  test.assert_eq(factorial(5), 120)
end

Running normally:

5
120

Running tests with rugo rats:

ok 1 - add works correctly
ok 2 - factorial computes correctly
2 tests, 2 passed, 0 failed, 0 skipped

This is incredibly useful for library code. The tests document the expected behavior and catch regressions — right there in the same file.

Lessons from Real Libraries

After studying Gummy (an ORM) and Rugh (a GitHub API client), some patterns emerge as the Rugo way:

The Attach Pattern

Libraries build domain objects by attaching methods to a central hash. Each domain module calls attach(client) to inject its functions:

# From the Rugh GitHub client:
# user.attach(gh)
# repo.attach(gh)
# issue.attach(gh)

This gives you a clean API (gh.repos(), gh.issues()) while keeping the implementation modular.

Smart Records

Database rows and API responses aren't dumb data — they come with actions. Insert a record, get back an object that can .save() and .delete() itself:

# From Gummy ORM:
# alice = Users.insert({name: "Alice", age: 30})
# alice.name = "Alicia"
# alice.save()              # persists the change
# alice.delete()            # removes from database

The trick is attaching closures at creation time that close over the connection and table name.

Convention for Internal State

Use double underscores for internal fields that shouldn't be part of the public API:

model = {}
model["__conn__"] = conn        # internal: database connection
model["__table__"] = name       # internal: table name
model["insert"] = fn(attrs)     # public: insert a record
  # ...
end

This isn't enforced by the language — it's a convention. But it clearly separates interface from implementation.


The Rugo Philosophy

After eight chapters, the philosophy boils down to a few principles:

  1. Start simple. Hashes before structs. Functions before classes. Graduate to more structure only when you need it.

  2. Be explicit about failure. Use try/or at the right level. Don't let errors propagate silently, and don't panic unnecessarily.

  3. Compose small pieces. Lambdas, closures, and hashes combine into surprisingly powerful patterns without complex abstractions.

  4. Use the shell. Don't rewrite curl or grep in Rugo. Shell out for what the shell does best, then process results in Rugo.

  5. Test where you code. Inline rats blocks keep tests and implementation together. Use them.

Rugo doesn't try to be everything. It's a sharp tool for a specific job: scripts and tools that need more structure than Bash but less ceremony than Go. Write it clean, keep it simple, ship a binary.