feat: add EntityState::Updated, change name from insert to upsert in catalog

This commit is contained in:
2025-10-14 13:01:16 +02:00
parent c01cfaf027
commit 827ac20e87
4 changed files with 32 additions and 26 deletions

View File

@@ -70,7 +70,7 @@ fn main() {
price: 125000, price: 125000,
}; };
// Insert the new laptop into the catalog. Note that at this time the product is in memory. // Insert the new laptop into the catalog. Note that at this time the product is in memory.
catalog.insert(new_laptop); catalog.upsert(new_laptop);
// Persist the changes in the catalog to the database. // Persist the changes in the catalog to the database.
catalog.persist().unwrap(); catalog.persist().unwrap();
// Remove the SQLite database file. // Remove the SQLite database file.

View File

@@ -33,14 +33,18 @@ impl Catalog {
Ok(()) Ok(())
} }
/// Inserts a single object that implements the `EAV` trait into the catalog. /// Inserts or updates a single object that implements the `EAV` trait into the catalog.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `object` - The object to insert. /// * `object` - The object to insert.
pub fn insert(&mut self, object: impl EAV) { pub fn upsert(&mut self, object: impl EAV) {
let mut entity = object.into(); let mut entity = object.into();
entity.state = EntityState::New; if self.items.contains_key(&entity.id) {
entity.state = EntityState::Updated;
} else {
entity.state = EntityState::New;
}
self.items.insert(entity.id.clone(), entity); self.items.insert(entity.id.clone(), entity);
} }
@@ -51,7 +55,7 @@ impl Catalog {
/// * `objects` - A vector of objects to insert. /// * `objects` - A vector of objects to insert.
pub fn insert_many(&mut self, objects: Vec<impl EAV>) { pub fn insert_many(&mut self, objects: Vec<impl EAV>) {
for object in objects { for object in objects {
self.insert(object); self.upsert(object);
} }
} }
@@ -300,7 +304,7 @@ mod tests {
in_stock: true, in_stock: true,
}; };
let item_id = item.id.clone(); let item_id = item.id.clone();
catalog.insert(item); catalog.upsert(item);
let entity = catalog.items.get(&item_id).unwrap(); let entity = catalog.items.get(&item_id).unwrap();
assert_eq!(entity.id, item_id); assert_eq!(entity.id, item_id);
assert_eq!(entity.state, EntityState::New); assert_eq!(entity.state, EntityState::New);
@@ -321,14 +325,14 @@ mod tests {
in_stock: true, in_stock: true,
}; };
let item_id = item1.id.clone(); let item_id = item1.id.clone();
catalog.insert(item1); catalog.upsert(item1);
let item2 = Item { let item2 = Item {
id: "item-123".to_string(), id: "item-123".to_string(),
name: "Second Item".to_string(), name: "Second Item".to_string(),
price: 200, price: 200,
in_stock: false, in_stock: false,
}; };
catalog.insert(item2); catalog.upsert(item2);
assert_eq!(catalog.items.len(), 1); assert_eq!(catalog.items.len(), 1);
let entity = catalog.items.get(&item_id).unwrap(); let entity = catalog.items.get(&item_id).unwrap();
assert_eq!(entity.value_of("name"), Some(&Value::from("Second Item"))); assert_eq!(entity.value_of("name"), Some(&Value::from("Second Item")));
@@ -376,7 +380,7 @@ mod tests {
price: 100, price: 100,
in_stock: true, in_stock: true,
}; };
catalog.insert(item.clone()); catalog.upsert(item.clone());
let retrieved_item: Option<Item> = catalog.get("item-123"); let retrieved_item: Option<Item> = catalog.get("item-123");
assert_eq!(retrieved_item, Some(item)); assert_eq!(retrieved_item, Some(item));
} }
@@ -391,7 +395,7 @@ mod tests {
price: 100, price: 100,
in_stock: true, in_stock: true,
}; };
catalog.insert(item.clone()); catalog.upsert(item.clone());
let retrieved_item: Option<Item> = catalog.get("nonexistent-id"); let retrieved_item: Option<Item> = catalog.get("nonexistent-id");
assert!(retrieved_item.is_none()); assert!(retrieved_item.is_none());
} }
@@ -413,8 +417,8 @@ mod tests {
price: 200, price: 200,
in_stock: false, in_stock: false,
}; };
catalog.insert(item1.clone()); catalog.upsert(item1.clone());
catalog.insert(item2.clone()); catalog.upsert(item2.clone());
let retrieved_item: Option<Item> = let retrieved_item: Option<Item> =
catalog.get_by_class_and_attribute("name", "Unique Item"); catalog.get_by_class_and_attribute("name", "Unique Item");
assert_eq!(retrieved_item, Some(item2)); assert_eq!(retrieved_item, Some(item2));
@@ -436,8 +440,8 @@ mod tests {
price: 250, price: 250,
in_stock: false, in_stock: false,
}; };
catalog.insert(item1.clone()); catalog.upsert(item1.clone());
catalog.insert(item2.clone()); catalog.upsert(item2.clone());
// Test with &str for String attribute // Test with &str for String attribute
let retrieved_by_name: Option<Item> = let retrieved_by_name: Option<Item> =
catalog.get_by_class_and_attribute("name", "Item One"); catalog.get_by_class_and_attribute("name", "Item One");
@@ -460,7 +464,7 @@ mod tests {
price: 100, price: 100,
in_stock: true, in_stock: true,
}; };
catalog.insert(item.clone()); catalog.upsert(item.clone());
// Test with a value that doesn't exist // Test with a value that doesn't exist
let retrieved_item: Option<Item> = let retrieved_item: Option<Item> =
catalog.get_by_class_and_attribute("name", "Non-existent Name"); catalog.get_by_class_and_attribute("name", "Non-existent Name");
@@ -488,8 +492,8 @@ mod tests {
price: 200, price: 200,
in_stock: false, in_stock: false,
}; };
catalog.insert(item1.clone()); catalog.upsert(item1.clone());
catalog.insert(item2.clone()); catalog.upsert(item2.clone());
let results: Vec<Item> = catalog.list_by_class::<Item>().collect(); let results: Vec<Item> = catalog.list_by_class::<Item>().collect();
assert_eq!(results.len(), 2); assert_eq!(results.len(), 2);
assert!(results.contains(&item1)); assert!(results.contains(&item1));
@@ -527,9 +531,9 @@ mod tests {
price: 300, price: 300,
in_stock: true, in_stock: true,
}; };
catalog.insert(item1.clone()); catalog.upsert(item1.clone());
catalog.insert(item2.clone()); catalog.upsert(item2.clone());
catalog.insert(item3.clone()); catalog.upsert(item3.clone());
let results: Vec<Item> = catalog let results: Vec<Item> = catalog
.list_by_class_and_attribute("in_stock", true) .list_by_class_and_attribute("in_stock", true)
.collect(); .collect();
@@ -549,7 +553,7 @@ mod tests {
price: 100, price: 100,
in_stock: true, in_stock: true,
}; };
catalog.insert(item); catalog.upsert(item);
// Search for a value that doesn't exist // Search for a value that doesn't exist
let results: Vec<Item> = catalog let results: Vec<Item> = catalog
.list_by_class_and_attribute("in_stock", false) .list_by_class_and_attribute("in_stock", false)
@@ -580,7 +584,7 @@ mod tests {
in_stock: true, in_stock: true,
}; };
let item_id = item.id.clone(); let item_id = item.id.clone();
catalog.insert(item); catalog.upsert(item);
catalog.delete(&item_id); catalog.delete(&item_id);
let entity = catalog.items.get(&item_id).unwrap(); let entity = catalog.items.get(&item_id).unwrap();
assert_eq!(entity.state, EntityState::ToDelete); assert_eq!(entity.state, EntityState::ToDelete);
@@ -596,7 +600,7 @@ mod tests {
price: 100, price: 100,
in_stock: true, in_stock: true,
}; };
catalog.insert(item); catalog.upsert(item);
let original_items = catalog.items.clone(); let original_items = catalog.items.clone();
// Attempt to delete a non-existent entity, which should not panic or change anything. // Attempt to delete a non-existent entity, which should not panic or change anything.
catalog.delete("nonexistent-id"); catalog.delete("nonexistent-id");
@@ -622,7 +626,7 @@ mod tests {
price: 123, price: 123,
in_stock: true, in_stock: true,
}; };
catalog1.insert(item1.clone()); catalog1.upsert(item1.clone());
assert!(catalog1.persist().is_ok()); assert!(catalog1.persist().is_ok());
// 2. Create a new catalog and load the item to verify it was persisted // 2. Create a new catalog and load the item to verify it was persisted
let mut catalog2 = Catalog::new(db_path); let mut catalog2 = Catalog::new(db_path);
@@ -652,7 +656,7 @@ mod tests {
price: 123, price: 123,
in_stock: true, in_stock: true,
}; };
catalog1.insert(item1.clone()); catalog1.upsert(item1.clone());
assert!(catalog1.persist().is_ok()); assert!(catalog1.persist().is_ok());
// 2. Mark the item for deletion and persist again. // 2. Mark the item for deletion and persist again.
catalog1.delete(&item1.id); catalog1.delete(&item1.id);

View File

@@ -4,6 +4,8 @@ pub enum EntityState {
/// The entity is newly created and has not been persisted. /// The entity is newly created and has not been persisted.
#[default] #[default]
New, New,
/// The entity has been updated and changes have not been persisted,
Updated,
/// The state of the entity is unknown. /// The state of the entity is unknown.
Unknown, Unknown,
/// The entity has been loaded from the database. /// The entity has been loaded from the database.

View File

@@ -59,7 +59,7 @@ mod tests {
// save some info for late comparison // save some info for late comparison
let original_product = product.clone(); let original_product = product.clone();
// insert the new object into catalog consuming it // insert the new object into catalog consuming it
catalog.insert(product); catalog.upsert(product);
// read value from catalog using the original key // read value from catalog using the original key
let read_product = catalog.get::<Product>(&original_product.id).unwrap(); let read_product = catalog.get::<Product>(&original_product.id).unwrap();
// assert equality between original and read // assert equality between original and read