7.1 KiB
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:
-
BaseModel - Provides database-related functionality:
db_prefix()
- Returns a string prefix for database operationsdb_keys()
- Returns index keys for the modelget_id()
- Returns the model's unique ID
-
ModelBuilder - Provides builder pattern functionality:
base_data_mut()
- Gets a mutable reference to the base dataid()
- Sets the ID for the modelbuild()
- 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:
// 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:
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:
/// 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:
/// 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):
#[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:
// 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:
// 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:
// 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:
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:
cargo run --example basic_user_example
Verify that all functionality is preserved and that the output matches the expected output.
Benefits of This Approach
- Simplified Code Structure: One trait instead of two means less cognitive overhead
- Reduced Boilerplate: A single macro implementation reduces repetitive code
- Improved Usability: No need to import multiple traits or worry about which trait provides which method
- Maintained Functionality: All existing functionality is preserved
- Better Encapsulation: The model concept is more cohesive as a single trait