Nesso ORM
A lightweight Object-Relational Mapper for Strada, built on DBI.
Overview
Nesso provides a thin, practical model layer over DBI. It offers:
Schema Definition
Define models with tables, columns, and primary keys.
CRUD Operations
Create, read, update, and delete records with simple method calls.
Relationships
Define has_many, belongs_to, and has_one relationships between models.
Two Styles
Use Data Mapper style or ActiveRecord style based on preference.
Quick Start
use lib "lib";
use DBI;
use Nesso;
use Nesso::Record; # For ActiveRecord-style methods
# Connect to database and create Nesso instance
my scalar $db = DBI::connect("dbi:SQLite:app.db", "", "");
my scalar $n = Nesso::new($db);
# Define a model
$n->define("users", {
"table" => "users",
"columns" => ["id", "username", "email"],
"primary" => "id"
});
# Create and save a record
my scalar $user = $n->create("users", { "username" => "alice" });
$user->save(); # ActiveRecord style
# Query records
my scalar $found = $n->find("users", 1);
say($found->{"username"});
Defining Models
Use define() to register a model with its schema:
$n->define("users", {
"table" => "users", # Database table name
"columns" => ["id", "username", "email", "created_at"],
"primary" => "id" # Primary key column
});
$n->define("posts", {
"table" => "posts",
"columns" => ["id", "user_id", "title", "body"],
"primary" => "id"
});
CRUD Operations
Create
Create a new record object (not saved until you call save()):
my scalar $user = $n->create("users", {
"username" => "alice",
"email" => "alice@example.com"
});
# Data Mapper style
$n->save($user);
# Or ActiveRecord style (requires Nesso::Record)
$user->save();
Read
# Find by primary key
my scalar $user = $n->find("users", 42);
# Find first matching record
my scalar $alice = $n->find_by("users", { "username" => "alice" });
# Find all matching records
my scalar $active = $n->where("users", { "status" => "active" });
# Get all records
my scalar $all = $n->all("users");
# Count records
my int $count = $n->count("users", { "status" => "active" });
Update
# Modify and save
$user->{"email"} = "newemail@example.com";
$user->save();
# Or use update() for multiple fields at once
$user->update({
"email" => "new@example.com",
"username" => "alice2"
});
Delete
# Data Mapper style
$n->delete($user);
# Or ActiveRecord style
$user->delete();
Query Options
Use special directives in conditions for ordering, limiting, and pagination:
# Order by column
my scalar $recent = $n->where("posts", {
"status" => "published",
"_order" => "created_at DESC"
});
# Limit results
my scalar $top10 = $n->where("posts", {
"_order" => "views DESC",
"_limit" => 10
});
# Pagination
my scalar $page2 = $n->where("posts", {
"_order" => "created_at DESC",
"_limit" => 20,
"_offset" => 20
});
Relationships
Nesso supports three types of relationships:
has_many
One-to-many relationship (e.g., a user has many posts):
# A user has many posts (posts.user_id references users.id)
$n->has_many("users", "posts", "user_id");
# Fetch user's posts
my scalar $user = $n->find("users", 1);
my scalar $posts = $n->related($user, "posts"); # Returns array
# Or: $posts = $user->related("posts");
foreach my scalar $post (@{$posts}) {
say($post->{"title"});
}
belongs_to
Many-to-one relationship (e.g., a post belongs to an author):
# A post belongs to an author (posts.user_id references users.id)
$n->belongs_to("posts", "author", "users", "user_id");
# Fetch post's author
my scalar $post = $n->find("posts", 1);
my scalar $author = $post->related("author"); # Returns single record
say("Written by: " . $author->{"username"});
has_one
One-to-one relationship (e.g., a user has one profile):
# A user has one profile (profiles.user_id references users.id)
$n->has_one("users", "profile", "profiles", "user_id");
# Fetch user's profile
my scalar $profile = $user->related("profile"); # Returns single record or undef
if (defined($profile)) {
say($profile->{"bio"});
}
Transactions
Wrap multiple operations in a transaction for atomicity. The closure passed to transaction() runs within a database transaction — if it succeeds, changes are committed; if it throws, changes are rolled back:
$n->transaction(func () {
# All operations here are atomic
my scalar $user = $n->create("users", { "username" => "bob" });
$user->save();
my scalar $post = $n->create("posts", {
"user_id" => $user->id(),
"title" => "First post"
});
$post->save();
# If anything throws, both operations are rolled back
});
ActiveRecord Methods
When you use Nesso::Record, records gain these instance methods:
| Method | Description | Example |
|---|---|---|
save() | INSERT or UPDATE the record | $user->save(); |
update($attrs) | Update attributes and save | $user->update({"email" => "..."}) |
delete() | DELETE the record | $user->delete(); |
reload() | Refresh from database | $user->reload(); |
related($name) | Fetch related records | $user->related("posts") |
id() | Get primary key value | $user->id() |
model() | Get model name | $user->model() |
is_new() | Check if unsaved | $user->is_new() |
Custom Model Classes
Create custom classes that inherit from Nesso::Record to add model-specific methods:
package User;
inherit Nesso::Record;
func set_password(scalar $self, str $password) void {
$self->{"password_hash"} = crypt::hash_password($password);
$self->save();
}
func authenticate(scalar $self, str $password) int {
return crypt::check_password($password, $self->{"password_hash"});
}
func full_name(scalar $self) str {
return $self->{"first_name"} . " " . $self->{"last_name"};
}
Specify the custom class in the model definition:
$n->define("users", {
"table" => "users",
"columns" => ["id", "username", "password_hash", "first_name", "last_name"],
"primary" => "id",
"class" => "User" # Use custom User class
});
# Now records are blessed as User objects
my scalar $user = $n->find("users", 1);
$user->set_password("secret123"); # Custom method
say($user->full_name()); # Custom method
$user->save(); # Inherited method
Debug Mode
Enable SQL logging to see all queries:
$n->set_debug(1);
# Now all queries are logged:
# [Nesso] SQL: SELECT * FROM users WHERE id = ?
# [Nesso] PARAMS: ["1"]
my scalar $user = $n->find("users", 1);
$n->set_debug(0); # Disable logging
Raw SQL Queries
For complex queries, use query():
my scalar $stats = $n->query(
"SELECT status, COUNT(*) as count FROM users GROUP BY status",
[]
);
foreach my scalar $row (@{$stats}) {
say($row->{"status"} . ": " . $row->{"count"});
}
Database Swapping
Change the database handle at runtime:
# Switch to a different database
my scalar $test_db = DBI::connect("dbi:SQLite:test.db", "", "");
$n->set_db($test_db);
# Or clone the Nesso instance with a different database
my scalar $test_n = $n->clone($test_db);
# $test_n has same models and relationships, different database
Complete Example
use lib "lib";
use DBI;
use Nesso;
use Nesso::Record;
func main() int {
# Connect
my scalar $db = DBI::connect("dbi:SQLite:blog.db", "", "");
my scalar $n = Nesso::new($db);
# Create tables
DBI::do_sql($db, "CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY, username TEXT, email TEXT
)");
DBI::do_sql($db, "CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY, user_id INTEGER, title TEXT, body TEXT
)");
# Define models
$n->define("users", {
"table" => "users", "columns" => ["id", "username", "email"], "primary" => "id"
});
$n->define("posts", {
"table" => "posts", "columns" => ["id", "user_id", "title", "body"], "primary" => "id"
});
# Define relationships
$n->has_many("users", "posts", "user_id");
$n->belongs_to("posts", "author", "users", "user_id");
# Create user and posts
my scalar $user = $n->create("users", {
"username" => "alice", "email" => "alice@example.com"
});
$user->save();
my scalar $post = $n->create("posts", {
"user_id" => $user->id(),
"title" => "Hello World",
"body" => "My first blog post!"
});
$post->save();
# Query with relationships
my scalar $all_users = $n->all("users");
foreach my scalar $u (@{$all_users}) {
say("User: " . $u->{"username"});
my scalar $posts = $u->related("posts");
foreach my scalar $p (@{$posts}) {
say(" - " . $p->{"title"});
}
}
DBI::disconnect($db);
return 0;
}
Function Reference
| Category | Method | Description |
|---|---|---|
| Setup | new($dbh) | Create Nesso instance |
define($name, $schema) | Register a model | |
set_db($dbh) | Change database handle | |
clone($dbh) | Clone with different DB | |
| CRUD | create($model, $attrs) | Create record (unsaved) |
save($record) | INSERT or UPDATE | |
find($model, $id) | Find by primary key | |
find_by($model, $conds) | Find first match | |
where($model, $conds) | Find all matches | |
delete($record) | Delete record | |
| Query | all($model) | Get all records |
count($model, $conds) | Count matches | |
| Relations | has_many($owner, $name, $fk) | Define 1:N relation |
belongs_to($owner, $name, $target, $fk) | Define N:1 relation | |
has_one($owner, $name, $target, $fk) | Define 1:1 relation | |
related($record, $name) | Fetch related records | |
| Other | transaction($callback) | Atomic operations |
query($sql, $params) | Raw SQL query |
$n->get_db().