ormkit 0.3.0

A compile-time safe async ORM for PostgreSQL powered by SQLx
Documentation
# ActiveModel Guide

Ormkit provides two ActiveModel patterns for tracking field changes. This guide explains when to use each.

## Overview

ActiveModel enables efficient partial updates by tracking which fields have changed:

```rust
let mut model = user.into_active_model();
model.name = ActiveValue::Set("New Name");
// Only 'name' field will be updated in database
repo.update(model).await?;
```

## Two Patterns

### 1. Typed ActiveModel (Recommended)

**Compile-time safe field tracking with full type checking.**

```rust
#[derive(ormkit::Entity)]
#[ormkit(table = "users")]
struct User {
    #[ormkit(id)]
    id: Uuid,
    email: String,
    name: String,
}

// Generated by derive macro
struct UserActiveModel {
    pub id: ActiveValue<Uuid>,
    pub email: ActiveValue<String>,
    pub name: ActiveValue<String>,
}
```

**Benefits:**
- ✅ Compile-time type checking
- ✅ IDE autocomplete for all fields
- ✅ No runtime string errors
- ✅ Diesel-grade guarantees

**When to use:**
- Most applications (default choice)
- When you want maximum type safety
- When IDE support is important

### 2. HashMap ActiveModel (Escape Hatch)

**Runtime field tracking with dynamic string keys.**

```rust
use ormkit::active_model::ActiveModel;

let mut model = ActiveModel::<User>::default();
model.set("email", "user@example.com");
model.set("name", "John Doe");
```

**Benefits:**
- ✅ Dynamic field access by string
- ✅ Useful for admin panels
- ✅ Generic code that works with any entity

**When to use:**
- Admin panels with dynamic forms
- Generic CRUD builders
- When field names are only known at runtime

---

## Typed ActiveModel Usage

### Basic Updates

```rust
use ormkit::{Entity, Repository};
use ormkit::active_value::ActiveValue;

let user = repo.find_by_id(user_id).await?;

// Convert to ActiveModel
let mut model = user.into_active_model();

// Update specific fields
model.name = ActiveValue::Set("Jane Doe".to_string());
model.email = ActiveValue::Set("jane@example.com".to_string());

// Save (only changed fields are updated)
let updated = repo.update(model).await?;
```

### Field States

```rust
use ormkit::active_value::ActiveValue;

// Set a new value
model.name = ActiveValue::Set("New Name");

// Keep existing value (no update)
model.email = ActiveValue::NotSet;

// Reset to default/None
model.bio = ActiveValue::NotSet; // Won't be included in UPDATE
```

### Partial Updates

```rust
// Only update fields that are Set
let mut model = user.into_active_model();
model.name = ActiveValue::Set("Updated");
model.email = ActiveValue::NotSet; // Won't be updated

repo.update(model).await?;
// Generated SQL: UPDATE users SET name = $1 WHERE id = $2
```

---

## HashMap ActiveModel Usage

### Dynamic Updates

```rust
use ormkit::active_model::ActiveModel;

let mut model = ActiveModel::<User>::default();

// Set fields dynamically
model.set("name", "John Doe");
model.set("email", "john@example.com");

// Check if field is modified
if model.is_modified("name") {
    println!("Name will be updated");
}

// Get changes
for (field, value) in model.get_changes() {
    println!("{}: {:?}", field, value);
}
```

### Generic CRUD

```rust
async fn update_entity<T: Entity>(
    repo: &Repository<T>,
    id: T::Id,
    updates: HashMap<String, String>,
) -> Result<T, OrmkitError> {
    let entity = repo.find_by_id(id).await?;
    let mut model = ActiveModel::from_entity(entity);

    for (field, value) in updates {
        model.set(&field, value);
    }

    repo.update(model).await
}
```

---

## Migration Path

### From HashMap to Typed

**Current (HashMap):**
```rust
let mut model = ActiveModel::<User>::default();
model.set("email", "user@example.com");
```

**Migrate to (Typed):**
```rust
let mut model = UserActiveModel::default();
model.email = ActiveValue::Set("user@example.com".to_string());
```

### Benefits of Migration

1. **Type Safety**: Catch errors at compile time
2. **IDE Support**: Autocomplete for field names
3. **Performance**: No string hashing overhead
4. **Refactoring**: Field renames caught by compiler

---

## Recommendation

**Use Typed ActiveModel by default.**

Only use HashMap ActiveModel when you truly need dynamic field access (admin panels, generic builders).

The typed approach provides:
- Better error messages
- IDE autocomplete
- Compile-time guarantees
- Easier refactoring

HashMap is an escape hatch for specific use cases, not the default path.

---

## See Also

- [Core ORM Guide]CORE_GUIDE.md - Basic ORM usage
- [Type-Safe Queries]CORE_GUIDE.md#type-safe-queries - Query DSL
- [Production Guide]PRODUCTION_GUIDE.md - Production considerations