seaorm-soft-delete 0.1.0

Soft-delete support for SeaORM entities
Documentation

seaorm-soft-delete

Soft-delete support for SeaORM entities.

Instead of DELETE, soft-deleted records have deleted_at set to the current timestamp. Queries exclude soft-deleted records by default.

Installation

[dependencies]
seaorm-soft-delete = "0.1"

If you don't need the migration helper, disable the default feature:

seaorm-soft-delete = { version = "0.1", default-features = false }

Setup

1. Add deleted_at column via migration

use seaorm_soft_delete::SoftDeleteMigration;

// inside your MigrationTrait::up()
SoftDeleteMigration::add_column(&manager, Users::Table).await?;

Note: Uses ADD COLUMN IF NOT EXISTS — idempotent on PostgreSQL only. On MySQL/SQLite, guard the migration yourself.

2. Implement SoftDeleteEntity on your entity

use seaorm_soft_delete::SoftDeleteEntity;

impl SoftDeleteEntity for users::Entity {
    fn deleted_at_column() -> users::Column {
        users::Column::DeletedAt
    }
}

3. Implement SoftDeleteActiveModel on your active model

use seaorm_soft_delete::SoftDeleteActiveModel;
use sea_orm::Set;

impl SoftDeleteActiveModel for users::ActiveModel {
    fn set_deleted_at(&mut self, value: Option<chrono::DateTime<chrono::Utc>>) {
        self.deleted_at = Set(value.map(|v| v.into()));
    }
}

4. Implement SoftDeleteModel on your model (optional)

Adds is_deleted() directly on model instances.

use seaorm_soft_delete::SoftDeleteModel;

impl SoftDeleteModel for users::Model {
    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
        self.deleted_at.map(|v| v.into())
    }
}

Querying

// Active records only (WHERE deleted_at IS NULL) — use by default
users::Entity::find_active().all(&db).await?;
users::Entity::find_active_by_id(id).one(&db).await?;

// Soft-deleted records only (WHERE deleted_at IS NOT NULL)
users::Entity::find_deleted().all(&db).await?;
users::Entity::find_deleted_by_id(id).one(&db).await?;

// All records including soft-deleted
users::Entity::find_with_deleted().all(&db).await?;

All return Select<Entity> — chain .filter(), .order_by(), .paginate() etc. normally.

Write operations

let model: ActiveModel = user.into();

// Soft-delete: sets deleted_at = NOW()
model.soft_delete(&db).await?;

// Restore: sets deleted_at = NULL
model.restore(&db).await?;

// Hard delete: permanently removes the row
model.hard_delete(&db).await?;

Checking deleted state

// Requires SoftDeleteModel impl (see Setup step 4)
if user.is_deleted() {
    println!("deleted at {:?}", user.deleted_at());
}

Performance tip

For large tables, add a partial index so active-record queries stay fast:

CREATE INDEX idx_users_active ON users (id) WHERE deleted_at IS NULL;

MSRV

Rust 1.85+

Out of scope (v1)

  • Cascading soft deletes
  • Custom column names (always deleted_at)
  • MySQL/SQLite idempotent migration

License

MIT