277 lines
7.1 KiB
Markdown
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 |