Examples & Recipes
End-to-end walkthroughs demonstrating how Ormed pieces work together.
End-to-End SQLite Workflow
1. Dependencies (pubspec.yaml)
dependencies:
ormed:
ormed_sqlite:
dev_dependencies:
build_runner:
ormed_cli:
2. Model (lib/user.dart)
// #region intro-model
import 'package:ormed/ormed.dart';
part 'user.orm.dart';
(table: 'users')
class User extends Model<User> {
const User({
required this.id,
required this.email,
this.name,
this.createdAt,
});
(isPrimaryKey: true, autoIncrement: true)
final int id;
final String email;
final String? name;
final DateTime? createdAt;
}
// #endregion intro-model
3. Generate
dart run build_runner build --delete-conflicting-outputs
This produces lib/orm_registry.g.dart with bootstrapOrm() (and other helpers).
4. Bootstrap CLI
dart run ormed_cli:ormed init
Creates ormed.yaml, registry, and migrations directory.
5. Create/Apply Migrations
dart run ormed_cli:ormed make --name create_users --create --table users
# Edit the generated file
dart run ormed_cli:ormed migrate
6. Query
- Notes
- Code
This is the “put it together” step: initialize the data source, run a query, then map results.
Future<void> sqliteQueryExample() async {
final dataSource = DataSource(
DataSourceOptions(
name: 'primary',
driver: SqliteDriverAdapter.file('database.sqlite'),
entities: generatedOrmModelDefinitions,
),
);
await dataSource.init();
// Insert a user
final user = await dataSource.repo<$User>().insert(
$User(id: 0, email: 'john@example.com', name: 'John'),
);
// Query users
final users = await dataSource
.query<$User>()
.whereEquals('name', 'John')
.get();
// Update
user.setAttribute('name', 'John Smith');
await dataSource.repo<$User>().update(user);
// Delete
await dataSource.repo<$User>().delete(user);
}
Static Helpers Pattern
Bind a global resolver for cleaner code:
Future<void> staticHelpersPattern() async {
final dataSource = DataSource(
DataSourceOptions(
name: 'primary',
driver: SqliteDriverAdapter.file('database.sqlite'),
entities: generatedOrmModelDefinitions,
),
);
await dataSource.init();
// Now static helpers work automatically!
final users = await Users.query().get();
final post = await Posts.find(1);
}
QueryContext (Advanced)
Future<void> queryContextExample() async {
final registry = bootstrapOrm();
final context = QueryContext(
registry: registry,
driver: SqliteDriverAdapter.inMemory(),
);
final users = await context.query<$User>().get();
print('Users: ${users.length}');
}
Observability Example
Future<void> observabilityExample() async {
final dataSource = DataSource(
DataSourceOptions(
name: 'primary',
driver: SqliteDriverAdapter.file('database.sqlite'),
entities: generatedOrmModelDefinitions,
logging: true,
),
);
await dataSource.init();
// Log all queries
dataSource.onQuery((entry) {
print('Query: ${entry.sql}');
print('Duration: ${entry.duration}ms');
});
await dataSource.query<$User>().get();
}
Output:
{
"type": "query",
"model": "User",
"sql": "SELECT \"id\", \"email\" FROM \"users\" WHERE \"id\" = ?",
"parameters": [1],
"duration_ms": 0.42
}
Working with Relations
Eager Loading
Future<void> eagerLoadingExample(DataSource dataSource) async {
// Load posts with their authors and comments
final posts = await dataSource
.query<$Post>()
.with_(['author', 'comments'])
.whereEquals('published', true)
.orderBy('createdAt', descending: true)
.limit(10)
.get();
for (final post in posts) {
print('${post.title} by ${post.author?.name}');
print('Comments: ${post.comments?.length}');
}
}
Eager Loading Aggregates
Future<void> eagerAggregatesExample(DataSource dataSource) async {
// Load users with post counts (without loading all posts)
final users = await dataSource.query<$User>().get();
for (final user in users) {
await user.loadCount(['posts']);
print('${user.name}: ${user.getAttribute<int>("posts_count")} posts');
}
// Or with withCount (if available on query builder)
// final users = await dataSource.query<$User>()
// .withCount(['posts'])
// .get();
}
Lazy Loading
Future<void> lazyLoadingExample(DataSource dataSource) async {
final post = await dataSource.query<$Post>().firstOrFail();
// Lazy load when needed
await post.load(['author']);
print('Author: ${post.author?.name}');
// Load multiple if not already loaded
await post.loadMissing(['tags', 'comments']);
// Nested relation loading
await post.load(['comments.author']);
}
Lazy Loading Aggregates
Future<void> lazyLoadingAggregatesExample(DataSource dataSource) async {
final post = await dataSource.query<$Post>().firstOrFail();
await post.loadCount(['comments']);
print('Comments: ${post.getAttribute<int>("comments_count")}');
await post.loadSum(['orders'], 'total', alias: 'order_total');
print('Total: \$${post.getAttribute<num>("order_total")}');
}
Relation Mutations
Future<void> relationMutationsExample(DataSource dataSource) async {
final post = await dataSource.query<$Post>().firstOrFail();
// BelongsTo: Associate & Dissociate
final author = await dataSource
.query<$User>()
.whereEquals('email', 'john@example.com')
.firstOrFail();
post.associate('author', author);
await post.save();
post.dissociate('author');
await post.save();
// ManyToMany: Attach, Detach & Sync
await post.attach('tags', [1, 2, 3]);
await post.detach('tags', [1]);
await post.sync('tags', [4, 5, 6]);
// With pivot data
await post.attach(
'tags',
[7],
pivotData: {'created_at': DateTime.now(), 'priority': 'high'},
);
}
Batch Loading
Future<void> batchLoadingExample(DataSource dataSource) async {
final posts = await dataSource.query<$Post>().limit(10).get();
// Single query loads all authors
await Model.loadRelations(posts, 'author');
// Load multiple relations
await Model.loadRelationsMany(posts, ['author', 'tags', 'comments']);
// Load only missing relations
await Model.loadRelationsMissing(posts, ['author', 'tags']);
}
Preventing Lazy Loading
Future<void> preventLazyLoadingExample(DataSource dataSource) async {
const environment = 'development';
final post = await dataSource.query<$Post>().firstOrFail();
// Enable in development
if (environment != 'production') {
ModelRelations.preventsLazyLoading = true;
}
// Now any lazy load throws
try {
await post.load(['author']); // Throws!
} on LazyLoadingViolationException catch (e) {
print('Blocked: ${e.relationName} on ${e.modelName}');
}
}
Manual Join Recipe
Future<void> manualJoinExample(DataSource dataSource) async {
final recentPosts = await dataSource
.query<$Post>()
.join('authors', (join) {
join.on('authors.id', '=', 'posts.author_id');
join.where('authors.active', true);
})
.joinRelation('tags')
.select(['authors.name', 'rel_tags_0.label'])
.orderBy('posts.published_at', descending: true)
.limit(5)
.rows();
final summaries = recentPosts.map((row) {
final model = row.model;
final author = row.row['author_name'];
final tag = row.row['tag_label'];
return '${model.title} by $author (${tag ?? "untagged"})';
});
}
Seeding Data
Future<void> seedingDataExample(DataSource dataSource) async {
// Truncate and seed
await dataSource.repo<$User>().query().delete();
final admin = await dataSource.repo<$User>().insert(
$User(id: 0, email: 'admin@example.com', name: 'Admin'),
);
await dataSource.repo<$Post>().insertMany([
$Post(id: 0, userId: admin.id, title: 'Hello', content: '...'),
$Post(id: 0, userId: admin.id, title: 'Another', content: '...'),
]);
}
Testing Tips
- Use
SqliteDriverAdapter.inMemory()for fast tests without a real database - Attach listeners to
context.onQuery/.onMutationto assert behavior - For migrations, use
MigrationRunnerwith a fake ledger to verify ordering - For Postgres integration tests, use
PostgresTestHarnesswhich spins up a schema per test