Skip to main content

Model Events

Ormed emits lifecycle events for every model operation: saving, saved, creating, created, updating, updated, deleting, deleted, trashed, forceDeleted, restoring, restored, retrieved, and replicating.

:::note Snippet context These snippets focus on event wiring and handlers. They assume you already have a DataSource or QueryContext unless the snippet explicitly shows setup. :::

Handler requirements​

Handlers are static methods that take the event type you annotate with @OrmEvent.

Rules:

  • Signature: static void onXyz(ModelXyzEvent event)
  • Public/static only; no return value
  • Use event.cancel() on cancellable events to abort the operation
(table: 'audited_users')
class AuditedUser extends Model<AuditedUser> {
const AuditedUser({required this.id, required this.email});

(isPrimaryKey: true, autoIncrement: true)
final int id;

final String email;

(ModelSavingEvent)
static void onSaving(ModelSavingEvent event) =>
modelEventLog.add('saving ${event.attributes['email']}');

(ModelCreatedEvent)
static void onCreated(ModelCreatedEvent event) =>
modelEventLog.add('created ${event.model.id}');

(ModelForceDeletedEvent)
static void onForceDeleted(ModelForceDeletedEvent event) =>
modelEventLog.add('forceDeleted ${event.model.id}');
}

Keep handlers small and side-effect-aware; push heavy work to background queues when needed.

Listen globally​

Attach listeners once when your app boots:

  • Register listeners once during app startup.
  • Global listeners receive events from every DataSource.

Guard operations (cancel)​

Cancel dangerous deletes or updates in a listener by calling event.cancel().

Cancellation requires a model instance (so your handler can inspect fields). Use a query-based delete (delete() / deleteReturning()) or pass a tracked model to a repository method.

void enforceActiveUserDeletes(EventBus bus) {
bus.on<ModelDeletingEvent>((event) {
if (!_forUser(event.modelType)) return;
final user = event.model as EventUser;
if (!user.active) {
event.cancel(); // block delete/forceDelete
modelEventLog.add('blocked delete for inactive user ${user.id}');
}
});
}

Full lifecycle walkthrough​

This wires listeners and creates a demo DataSource.

final bus = EventBus.instance;
registerModelEventListeners(bus);
enforceActiveUserDeletes(bus);

final dataSource = DataSource(
DataSourceOptions(
name: 'events-demo',
driver: SqliteDriverAdapter.inMemory(),
entities: generatedOrmModelDefinitions,
),
);
await dataSource.init();

Event ordering​

For a create + update + soft delete + restore flow, events fire in this order:

  1. saving → creating → created → saved
  2. saving → updating → updated → saved
  3. deleting → deleted → trashed (soft delete)
  4. restoring → restored

Hard deletes emit deleting → deleted → forceDeleted.

Replication​

ModelReplicatingEvent fires before Repository.replicate returns the copy. Cancel it to block replication or attach metadata to the new instance.

Suppressing Events​

Sometimes you need to perform operations without triggering events—for example, during bulk imports, test seeding, or internal system updates.

Query-Level Suppression​

Use withoutEvents() to suppress all model events for a query:

Future<void> withoutEventsQuery(DataSource dataSource) async {
// Suppress events for any query operation
final query = dataSource.query<$UserPost>().withoutEvents();

// Insert without events
final post = await query.insertReturning({
'author_id': 1,
'title': 'No Events',
});

// Update without events
await dataSource
.query<$UserPost>()
.withoutEvents()
.whereEquals('id', post.id)
.update({'title': 'Updated Silently'});

// Delete without events
await dataSource
.query<$UserPost>()
.withoutEvents()
.whereEquals('id', post.id)
.delete();
}

Relation-Level Suppression​

Use createQuietlyRelation and createManyQuietlyRelation to create related models without events:

Future<void> createQuietlyRelation(DataSource dataSource) async {
final user = await dataSource.query<$UserWithPosts>().first();

if (user != null) {
// Create without triggering model events (creating/created/saving/saved)
final post = await user.createQuietlyRelation<$UserPost>('posts', {
'title': 'Silent Post',
});

// Works with DTOs too
final dtoPost = await user.createQuietlyRelation<$UserPost>(
'posts',
UserPostInsertDto(title: 'Silent DTO Post'),
);

print('Quietly created: ${post.title}, ${dtoPost.title}');
}
}

:::tip When to suppress events

  • Bulk imports: Skip audit logging for performance
  • Test seeding: Avoid triggering side effects in tests
  • System migrations: Update data without user-facing events
  • Internal sync: Replicate data without duplicate notifications :::

Verify Event Wiring​

  1. Test one create/update/delete lifecycle sequence.
  2. Test one cancellable guard (event.cancel() path).
  3. Test one suppressed-events path (withoutEvents/quiet relations).

Read This Next​