db/model_trait_unification_plan.md
2025-04-22 12:53:17 +04:00

277 lines
7.1 KiB
Markdown

# Model Trait Unification Plan
## Introduction
This document outlines the plan to unify the `BaseModel` and `ModelBuilder` traits in the heromodels crate into a single `Model` trait. The goal is to simplify the codebase, reduce boilerplate, and make the API more intuitive while maintaining all existing functionality.
## Current Structure
Currently, the codebase has two separate traits:
1. **BaseModel** - Provides database-related functionality:
- `db_prefix()` - Returns a string prefix for database operations
- `db_keys()` - Returns index keys for the model
- `get_id()` - Returns the model's unique ID
2. **ModelBuilder** - Provides builder pattern functionality:
- `base_data_mut()` - Gets a mutable reference to the base data
- `id()` - Sets the ID for the model
- `build()` - Finalizes the model by updating timestamps
Each model (like User and Comment) implements both traits separately, and there are two separate macros for implementing these traits:
```rust
// For BaseModel
impl_base_model!(User, "user");
// For ModelBuilder
impl_model_builder!(User);
```
This leads to duplication and cognitive overhead when working with models.
## Proposed Structure
We will create a unified `Model` trait that combines all the functionality from both existing traits:
```mermaid
classDiagram
class Model {
<<trait>>
+db_prefix() static str
+db_keys() Vec~IndexKey~
+get_id() u32
+base_data_mut() &mut BaseModelData
+id(u32) Self
+build() Self
}
class User {
+base_data BaseModelData
+username String
+email String
+full_name String
+is_active bool
}
class Comment {
+base_data BaseModelData
+user_id u32
+content String
}
Model <|-- User
Model <|-- Comment
```
## Implementation Steps
### 1. Update model.rs
Create the new `Model` trait that combines all methods from both existing traits:
```rust
/// Unified trait for all models
pub trait Model: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static {
/// Get the database prefix for this model type
fn db_prefix() -> &'static str where Self: Sized;
/// Returns a list of index keys for this model instance
/// These keys will be used to create additional indexes in the TST
fn db_keys(&self) -> Vec<IndexKey> {
Vec::new()
}
/// Get the unique ID for this model
fn get_id(&self) -> u32;
/// Get a mutable reference to the base_data field
fn base_data_mut(&mut self) -> &mut BaseModelData;
/// Set the ID for this model
fn id(mut self, id: u32) -> Self where Self: Sized {
self.base_data_mut().id = id;
self
}
/// Build the model, updating the modified timestamp
fn build(mut self) -> Self where Self: Sized {
self.base_data_mut().update_modified();
self
}
}
```
Create a new implementation macro that implements all required methods:
```rust
/// Macro to implement Model for a struct that contains a base_data field of type BaseModelData
#[macro_export]
macro_rules! impl_model {
($type:ty, $prefix:expr) => {
impl $crate::core::model::Model for $type {
fn db_prefix() -> &'static str {
$prefix
}
fn get_id(&self) -> u32 {
self.base_data.id
}
fn base_data_mut(&mut self) -> &mut $crate::core::model::BaseModelData {
&mut self.base_data
}
// Other methods have default implementations
}
};
}
```
Mark the old traits and macros as deprecated (or remove them entirely):
```rust
#[deprecated(
since = "0.2.0",
note = "Use the unified Model trait instead"
)]
pub trait BaseModel { /* ... */ }
#[deprecated(
since = "0.2.0",
note = "Use the unified Model trait instead"
)]
pub trait ModelBuilder { /* ... */ }
#[deprecated(
since = "0.2.0",
note = "Use impl_model instead"
)]
#[macro_export]
macro_rules! impl_base_model { /* ... */ }
#[deprecated(
since = "0.2.0",
note = "Use impl_model instead"
)]
#[macro_export]
macro_rules! impl_model_builder { /* ... */ }
```
### 2. Update User and Comment Implementations
For User:
```rust
// Implement Model for User
impl_model!(User, "user");
// Custom implementation of db_keys
impl Model for User {
fn db_keys(&self) -> Vec<IndexKey> {
vec![
IndexKey {
name: "username",
value: self.username.clone(),
},
IndexKey {
name: "email",
value: self.email.clone(),
},
IndexKey {
name: "is_active",
value: self.is_active.to_string(),
},
]
}
}
```
For Comment:
```rust
// Implement Model for Comment
impl_model!(Comment, "comment");
// Custom implementation of db_keys
impl Model for Comment {
fn db_keys(&self) -> Vec<IndexKey> {
vec![
IndexKey {
name: "user_id",
value: self.user_id.to_string(),
},
]
}
}
```
### 3. Update Imports and References
Update the lib.rs file to export the new trait and macro:
```rust
// Export core module
pub mod core;
// Export userexample module
pub mod userexample;
// Re-export key types for convenience
pub use core::model::{Model, BaseModelData, IndexKey};
pub use core::Comment;
pub use userexample::User;
// Re-export macros
pub use crate::impl_model;
```
Update the example code to use the new trait:
```rust
use heromodels::{Model, Comment, User};
fn main() {
println!("Hero Models - Basic Usage Example");
println!("================================");
// Create a new user using the fluent interface
let user = User::new(1)
.username("johndoe")
.email("john.doe@example.com")
.full_name("John Doe")
.build();
println!("Created user: {:?}", user);
println!("User ID: {}", user.get_id());
println!("User DB Prefix: {}", User::db_prefix());
// Create a comment for the user
let comment = Comment::new(1)
.user_id(2) // commenter's user ID
.content("This is a comment on the user")
.build();
println!("\nCreated comment: {:?}", comment);
println!("Comment ID: {}", comment.get_id());
println!("Comment DB Prefix: {}", Comment::db_prefix());
}
```
### 4. Testing
Run the example to ensure it works as expected:
```bash
cargo run --example basic_user_example
```
Verify that all functionality is preserved and that the output matches the expected output.
## Benefits of This Approach
1. **Simplified Code Structure**: One trait instead of two means less cognitive overhead
2. **Reduced Boilerplate**: A single macro implementation reduces repetitive code
3. **Improved Usability**: No need to import multiple traits or worry about which trait provides which method
4. **Maintained Functionality**: All existing functionality is preserved
5. **Better Encapsulation**: The model concept is more cohesive as a single trait