fix: Use incremental ID

This commit is contained in:
Mahmoud Emad 2025-05-17 13:00:05 +03:00
parent bde5db0e52
commit a676854251
13 changed files with 149 additions and 116 deletions

View File

@ -130,7 +130,7 @@ pub fn model(_attr: TokenStream, item: TokenStream) -> TokenStream {
}
fn get_id(&self) -> u32 {
self.base_data.id.unwrap_or(0)
self.base_data.id
}
fn base_data_mut(&mut self) -> &mut heromodels_core::BaseModelData {

View File

@ -33,68 +33,73 @@ fn main() {
println!("Hero Models - Basic Usage Example");
println!("================================");
// Create users with different ID configurations
// Create users with auto-generated IDs
// User 1: With explicit ID
let user1 = User::new(Some(1))
// User 1
let user1 = User::new()
.username("johndoe")
.email("john.doe@example.com")
.full_name("John Doe")
.is_active(false)
.build();
// User 2: With auto-generated ID
let user2 = User::new(None)
// User 2
let user2 = User::new()
.username("janesmith")
.email("jane.smith@example.com")
.full_name("Jane Smith")
.is_active(true)
.build();
// User 3: With explicit ID
let user3 = User::new(Some(3))
// User 3
let user3 = User::new()
.username("willism")
.email("willis.masters@example.com")
.full_name("Willis Masters")
.is_active(true)
.build();
// User 4: With explicit ID
let user4 = User::new(Some(4))
// User 4
let user4 = User::new()
.username("carrols")
.email("carrol.smith@example.com")
.full_name("Carrol Smith")
.is_active(false)
.build();
// Save all users to database
db.collection().expect("can open user collection").set(&user1).expect("can set user");
db.collection().expect("can open user collection").set(&user2).expect("can set user");
db.collection().expect("can open user collection").set(&user3).expect("can set user");
db.collection().expect("can open user collection").set(&user4).expect("can set user");
// Save all users to database and get their assigned IDs
let user1_id = db.collection().expect("can open user collection").set(&user1).expect("can set user");
let user2_id = db.collection().expect("can open user collection").set(&user2).expect("can set user");
let user3_id = db.collection().expect("can open user collection").set(&user3).expect("can set user");
let user4_id = db.collection().expect("can open user collection").set(&user4).expect("can set user");
// Retrieve all users from database
println!("User 1 assigned ID: {}", user1_id);
println!("User 2 assigned ID: {}", user2_id);
println!("User 3 assigned ID: {}", user3_id);
println!("User 4 assigned ID: {}", user4_id);
// Retrieve all users from database using the assigned IDs
let db_user1 = db.collection::<User>().expect("can open user collection")
.get_by_id(user1.get_id()).expect("can load user").expect("user should exist");
.get_by_id(user1_id).expect("can load user").expect("user should exist");
let db_user2 = db.collection::<User>().expect("can open user collection")
.get_by_id(user2.get_id()).expect("can load user").expect("user should exist");
.get_by_id(user2_id).expect("can load user").expect("user should exist");
let db_user3 = db.collection::<User>().expect("can open user collection")
.get_by_id(user3.get_id()).expect("can load user").expect("user should exist");
.get_by_id(user3_id).expect("can load user").expect("user should exist");
let db_user4 = db.collection::<User>().expect("can open user collection")
.get_by_id(user4.get_id()).expect("can load user").expect("user should exist");
.get_by_id(user4_id).expect("can load user").expect("user should exist");
// Print all users retrieved from database
println!("\n--- Users Retrieved from Database ---");
println!("\n1. User with explicit ID (1):");
println!("\n1. First user:");
print_user_details(&db_user1);
println!("\n2. User with auto-generated ID:");
println!("\n2. Second user:");
print_user_details(&db_user2);
println!("\n3. User with explicit ID (3):");
println!("\n3. Third user:");
print_user_details(&db_user3);
println!("\n4. User with explicit ID (4):");
println!("\n4. Fourth user:");
print_user_details(&db_user4);
// Demonstrate different ways to retrieve users from the database
@ -126,9 +131,11 @@ fn main() {
// 3. Delete a user and show the updated results
println!("\n3. After Deleting a User:");
let user_to_delete_id = active_users[0].get_id();
println!("Deleting user with ID: {}", user_to_delete_id);
db.collection::<User>()
.expect("can open user collection")
.delete_by_id(active_users[0].get_id())
.delete_by_id(user_to_delete_id)
.expect("can delete existing user");
// Show remaining active users
@ -165,21 +172,24 @@ fn main() {
// 1. Create and save a comment
println!("\n1. Creating a Comment:");
let comment = Comment::new(None)
let comment = Comment::new()
.user_id(db_user1.get_id()) // commenter's user ID
.content("This is a comment on the user")
.build();
db.collection()
// Save the comment and get its assigned ID
let comment_id = db.collection()
.expect("can open comment collection")
.set(&comment)
.expect("can set comment");
// 2. Retrieve the comment from database
println!("Comment assigned ID: {}", comment_id);
// 2. Retrieve the comment from database using the assigned ID
let db_comment = db
.collection::<Comment>()
.expect("can open comment collection")
.get_by_id(comment.get_id())
.get_by_id(comment_id)
.expect("can load comment")
.expect("comment should exist");

View File

@ -19,7 +19,7 @@ fn main() {
println!("SimpleUser DB Prefix: {}", SimpleUser::db_prefix());
let user = SimpleUser {
base_data: BaseModelData::new(1),
base_data: BaseModelData::new(),
login: "johndoe".to_string(),
full_name: "John Doe".to_string(),
};

View File

@ -32,8 +32,9 @@ where
/// Get an object from its ID. This does not use an index lookup
fn get_by_id(&self, id: u32) -> Result<Option<V>, Error<Self::Error>>;
/// Store an item in the DB.
fn set(&self, value: &V) -> Result<(), Error<Self::Error>>;
/// Store an item in the DB and return the assigned ID.
/// This method does not modify the original model.
fn set(&self, value: &V) -> Result<u32, Error<Self::Error>>;
/// Delete all items from the db with a given index.
fn delete<I, Q>(&self, key: &Q) -> Result<(), Error<Self::Error>>

View File

@ -26,7 +26,7 @@ impl OurDB {
data_path.push("data");
let data_db = ourdb::OurDB::new(ourdb::OurDBConfig {
incremental_mode: false,
incremental_mode: true,
path: data_path,
file_size: None,
keysize: None,
@ -87,7 +87,7 @@ where
Self::get_ourdb_value(&mut data_db, id)
}
fn set(&self, value: &M) -> Result<(), super::Error<Self::Error>> {
fn set(&self, value: &M) -> Result<u32, super::Error<Self::Error>> {
// Before inserting the new object, check if an object with this ID already exists. If it does, we potentially need to update indices.
let mut data_db = self.data.lock().expect("can lock data DB");
let old_obj: Option<M> = Self::get_ourdb_value(&mut data_db, value.get_id())?;
@ -134,13 +134,52 @@ where
}
}
// set or update the object
let v = bincode::serde::encode_to_vec(value, BINCODE_CONFIG)?;
// Get the current ID
let id = value.get_id();
data_db.set(OurDBSetArgs {
id: Some(id),
data: &v,
})?;
// If id is 0, it's a new object, so let OurDB auto-generate an ID
// Otherwise, it's an update to an existing object
let id_param = if id == 0 { None } else { Some(id) };
// For new objects (id == 0), we need to get the assigned ID from OurDB
// and update the model before serializing it
let assigned_id = if id == 0 {
// First, get the next ID that OurDB will assign
let next_id = data_db.get_next_id()?;
// Create a mutable clone of the value and update its ID
// This is a bit of a hack, but we need to update the ID before serializing
let mut value_clone = value.clone();
let base_data = value_clone.base_data_mut();
base_data.update_id(next_id);
// Now serialize the updated model
let v = bincode::serde::encode_to_vec(&value_clone, BINCODE_CONFIG)?;
// Save to OurDB with the ID parameter set to None to let OurDB auto-generate the ID
let assigned_id = data_db.set(OurDBSetArgs {
id: id_param,
data: &v,
})?;
// The assigned ID should match the next_id we got earlier
assert_eq!(assigned_id, next_id, "OurDB assigned a different ID than expected");
// Return the assigned ID
assigned_id
} else {
// For existing objects, just serialize and save
let v = bincode::serde::encode_to_vec(value, BINCODE_CONFIG)?;
// Save to OurDB with the existing ID
let assigned_id = data_db.set(OurDBSetArgs {
id: id_param,
data: &v,
})?;
// Return the existing ID
assigned_id
};
// Now add the new indices
for index_key in indices_to_add {
@ -148,12 +187,14 @@ where
// Load the existing id set for the index or create a new set
let mut existing_ids =
Self::get_tst_value::<HashSet<u32>>(&mut index_db, &key)?.unwrap_or_default();
existing_ids.insert(id);
// Use the assigned ID for new objects
existing_ids.insert(assigned_id);
let encoded_ids = bincode::serde::encode_to_vec(existing_ids, BINCODE_CONFIG)?;
index_db.set(&key, encoded_ids)?;
}
Ok(())
// Return the assigned ID
Ok(assigned_id)
}
fn delete<I, Q>(&self, key: &Q) -> Result<(), super::Error<Self::Error>>

View File

@ -139,14 +139,13 @@ pub struct Calendar {
}
impl Calendar {
/// Creates a new calendar
/// Creates a new calendar with auto-generated ID
///
/// # Arguments
/// * `id` - Optional ID for the calendar. If None, the ID will be auto-generated.
/// * `name` - Name of the calendar
pub fn new(id: Option<u32>, name: impl ToString) -> Self {
pub fn new(name: impl ToString) -> Self {
Self {
base_data: BaseModelData::new(id),
base_data: BaseModelData::new(),
name: name.to_string(),
description: None,
events: Vec::new(),

View File

@ -13,13 +13,10 @@ pub struct Comment {
}
impl Comment {
/// Create a new comment
///
/// # Arguments
/// * `id` - Optional ID for the comment. If None, the ID will be auto-generated.
pub fn new(id: Option<u32>) -> Self {
/// Create a new comment with auto-generated ID
pub fn new() -> Self {
Self {
base_data: BaseModelData::new(id),
base_data: BaseModelData::new(),
user_id: 0,
content: String::new(),
}

View File

@ -21,10 +21,9 @@ pub struct Account {
}
impl Account {
/// Create a new account
/// Create a new account with auto-generated ID
///
/// # Arguments
/// * `id` - Optional ID for the account. If None, the ID will be auto-generated.
/// * `name` - Name of the account
/// * `user_id` - ID of the user who owns the account
/// * `description` - Description of the account
@ -32,7 +31,6 @@ impl Account {
/// * `address` - Address of the account on the blockchain
/// * `pubkey` - Public key
pub fn new(
id: Option<u32>,
name: impl ToString,
user_id: u32,
description: impl ToString,
@ -41,7 +39,7 @@ impl Account {
pubkey: impl ToString
) -> Self {
Self {
base_data: BaseModelData::new(id),
base_data: BaseModelData::new(),
name: name.to_string(),
user_id,
description: description.to_string(),

View File

@ -33,9 +33,8 @@ pub struct Asset {
}
impl Asset {
/// Create a new asset
/// Create a new asset with auto-generated ID
pub fn new(
id: Option<u32>,
name: impl ToString,
description: impl ToString,
amount: f64,
@ -44,7 +43,7 @@ impl Asset {
decimals: u8,
) -> Self {
Self {
base_data: BaseModelData::new(id),
base_data: BaseModelData::new(),
name: name.to_string(),
description: description.to_string(),
amount,
@ -72,14 +71,14 @@ impl Asset {
if amount <= 0.0 {
return Err("Transfer amount must be positive");
}
if self.amount < amount {
return Err("Insufficient balance for transfer");
}
self.amount -= amount;
target.amount += amount;
Ok(())
}
}

View File

@ -111,9 +111,8 @@ pub struct Listing {
}
impl Listing {
/// Create a new listing
/// Create a new listing with auto-generated ID
pub fn new(
id: Option<u32>,
title: impl ToString,
description: impl ToString,
asset_id: impl ToString,
@ -127,7 +126,7 @@ impl Listing {
image_url: Option<impl ToString>,
) -> Self {
Self {
base_data: BaseModelData::new(id),
base_data: BaseModelData::new(),
title: title.to_string(),
description: description.to_string(),
asset_id: asset_id.to_string(),
@ -153,32 +152,32 @@ impl Listing {
if self.listing_type != ListingType::Auction {
return Err("Bids can only be placed on auction listings");
}
// Check if listing is active
if self.status != ListingStatus::Active {
return Err("Cannot place bid on inactive listing");
}
// Check if bid amount is higher than current price
if bid.amount <= self.price {
return Err("Bid amount must be higher than current price");
}
// Check if there are existing bids and if the new bid is higher
if let Some(highest_bid) = self.highest_bid() {
if bid.amount <= highest_bid.amount {
return Err("Bid amount must be higher than current highest bid");
}
}
// Add the bid
self.bids.push(bid);
// Update the current price to the new highest bid
if let Some(highest_bid) = self.highest_bid() {
self.price = highest_bid.amount;
}
Ok(self)
}
@ -195,12 +194,12 @@ impl Listing {
if self.status != ListingStatus::Active {
return Err("Cannot complete sale for inactive listing");
}
self.status = ListingStatus::Sold;
self.buyer_id = Some(buyer_id.to_string());
self.sale_price = Some(sale_price);
self.sold_at = Some(Utc::now());
// If this was an auction, accept the winning bid and reject others
if self.listing_type == ListingType::Auction {
for bid in &mut self.bids {
@ -211,7 +210,7 @@ impl Listing {
}
}
}
Ok(self)
}
@ -220,16 +219,16 @@ impl Listing {
if self.status != ListingStatus::Active {
return Err("Cannot cancel inactive listing");
}
self.status = ListingStatus::Cancelled;
// Cancel all active bids
for bid in &mut self.bids {
if bid.status == BidStatus::Active {
bid.status = BidStatus::Cancelled;
}
}
Ok(self)
}
@ -239,7 +238,7 @@ impl Listing {
if let Some(expires_at) = self.expires_at {
if Utc::now() > expires_at {
self.status = ListingStatus::Expired;
// Cancel all active bids
for bid in &mut self.bids {
if bid.status == BidStatus::Active {
@ -249,7 +248,7 @@ impl Listing {
}
}
}
self
}

View File

@ -75,16 +75,15 @@ pub struct Ballot {
}
impl Ballot {
/// Create a new ballot
/// Create a new ballot with auto-generated ID
///
/// # Arguments
/// * `id` - Optional ID for the ballot. If None, the ID will be auto-generated.
/// * `user_id` - ID of the user who cast this ballot
/// * `vote_option_id` - ID of the vote option chosen
/// * `shares_count` - Number of shares/tokens/voting power
pub fn new(id: Option<u32>, user_id: u32, vote_option_id: u8, shares_count: i64) -> Self {
pub fn new(user_id: u32, vote_option_id: u8, shares_count: i64) -> Self {
Self {
base_data: BaseModelData::new(id),
base_data: BaseModelData::new(),
user_id,
vote_option_id,
shares_count,
@ -114,18 +113,17 @@ pub struct Proposal {
}
impl Proposal {
/// Create a new proposal
/// Create a new proposal with auto-generated ID
///
/// # Arguments
/// * `id` - Optional ID for the proposal. If None, the ID will be auto-generated.
/// * `creator_id` - ID of the user who created the proposal
/// * `title` - Title of the proposal
/// * `description` - Description of the proposal
/// * `vote_start_date` - Date when voting starts
/// * `vote_end_date` - Date when voting ends
pub fn new(id: Option<u32>, creator_id: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime<Utc>, vote_end_date: DateTime<Utc>) -> Self {
pub fn new(creator_id: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime<Utc>, vote_end_date: DateTime<Utc>) -> Self {
Self {
base_data: BaseModelData::new(id),
base_data: BaseModelData::new(),
creator_id: creator_id.to_string(),
title: title.to_string(),
description: description.to_string(),
@ -145,7 +143,7 @@ impl Proposal {
self
}
pub fn cast_vote(mut self, ballot_id: Option<u32>, user_id: u32, chosen_option_id: u8, shares: i64) -> Self {
pub fn cast_vote(mut self, user_id: u32, chosen_option_id: u8, shares: i64) -> Self {
if self.vote_status != VoteEventStatus::Open {
eprintln!("Voting is not open for proposal '{}'", self.title);
return self;
@ -161,7 +159,7 @@ impl Proposal {
}
}
let new_ballot = Ballot::new(ballot_id, user_id, chosen_option_id, shares);
let new_ballot = Ballot::new(user_id, chosen_option_id, shares);
self.ballots.push(new_ballot);
if let Some(option) = self.options.iter_mut().find(|opt| opt.id == chosen_option_id) {

View File

@ -26,13 +26,10 @@ pub struct User {
}
impl User {
/// Create a new user
///
/// # Arguments
/// * `id` - Optional ID for the user. If None, the ID will be auto-generated.
pub fn new(id: Option<u32>) -> Self {
/// Create a new user with auto-generated ID
pub fn new() -> Self {
Self {
base_data: BaseModelData::new(id),
base_data: BaseModelData::new(),
username: String::new(),
email: String::new(),
full_name: String::new(),

View File

@ -59,21 +59,11 @@ pub trait Model:
}
/// Get the unique ID for this model
/// Returns 0 if the ID is None
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: Option<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
@ -98,8 +88,8 @@ pub trait Index {
/// Base struct that all models should include
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaseModelData {
/// Unique incremental ID per circle
pub id: Option<u32>,
/// Unique incremental ID - will be auto-generated by OurDB
pub id: u32,
/// Unix epoch timestamp for creation time
pub created_at: i64,
@ -112,11 +102,12 @@ pub struct BaseModelData {
}
impl BaseModelData {
/// Create a new BaseModelData instance
pub fn new(id: Option<u32>) -> Self {
/// Create a new BaseModelData instance with ID set to 0
/// The ID will be auto-generated by OurDB when the model is saved
pub fn new() -> Self {
let now = chrono::Utc::now().timestamp();
Self {
id,
id: 0, // This will be replaced by OurDB with an auto-generated ID
created_at: now,
modified_at: now,
comments: Vec::new(),
@ -124,8 +115,8 @@ impl BaseModelData {
}
/// Create a new BaseModelDataBuilder
pub fn builder(id: Option<u32>) -> BaseModelDataBuilder {
BaseModelDataBuilder::new(id)
pub fn builder() -> BaseModelDataBuilder {
BaseModelDataBuilder::new()
}
/// Add a comment to this model
@ -144,11 +135,15 @@ impl BaseModelData {
pub fn update_modified(&mut self) {
self.modified_at = chrono::Utc::now().timestamp();
}
/// Update the ID of this model
pub fn update_id(&mut self, id: u32) {
self.id = id;
}
}
/// Builder for BaseModelData
pub struct BaseModelDataBuilder {
id: Option<u32>,
created_at: Option<i64>,
modified_at: Option<i64>,
comments: Vec<u32>,
@ -156,9 +151,8 @@ pub struct BaseModelDataBuilder {
impl BaseModelDataBuilder {
/// Create a new BaseModelDataBuilder
pub fn new(id: Option<u32>) -> Self {
pub fn new() -> Self {
Self {
id,
created_at: None,
modified_at: None,
comments: Vec::new(),
@ -193,7 +187,7 @@ impl BaseModelDataBuilder {
pub fn build(self) -> BaseModelData {
let now = chrono::Utc::now().timestamp();
BaseModelData {
id: self.id,
id: 0, // This will be replaced by OurDB with an auto-generated ID
created_at: self.created_at.unwrap_or(now),
modified_at: self.modified_at.unwrap_or(now),
comments: self.comments,