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:
-
Start simple. Hashes before structs. Functions before classes. Graduate to more structure only when you need it.
-
Be explicit about failure. Use
try/orat the right level. Don't let errors propagate silently, and don't panic unnecessarily. -
Compose small pieces. Lambdas, closures, and hashes combine into surprisingly powerful patterns without complex abstractions.
-
Use the shell. Don't rewrite
curlorgrepin Rugo. Shell out for what the shell does best, then process results in Rugo. -
Test where you code. Inline
ratsblocks 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.