feat: add subclass to entity struct

This commit is contained in:
2025-10-20 16:20:56 +02:00
parent c41e806817
commit baa3fc9147
7 changed files with 61 additions and 8 deletions

View File

@@ -6,6 +6,7 @@ pub fn run(path: &path::Path) -> result::Result<(), FailedTo> {
CREATE TABLE IF NOT EXISTS entity ( CREATE TABLE IF NOT EXISTS entity (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
class TEXT NOT NULL, class TEXT NOT NULL,
subclass TEXT,
ref_date INTEGER ref_date INTEGER
); );
CREATE TABLE IF NOT EXISTS attribute ( CREATE TABLE IF NOT EXISTS attribute (

View File

@@ -38,6 +38,7 @@ mod tests {
let mut entity = Entity { let mut entity = Entity {
id: "entity1".to_string(), id: "entity1".to_string(),
class: "class1".to_string(), class: "class1".to_string(),
subclass: None,
attributes: HashMap::new(), attributes: HashMap::new(),
state: Default::default(), state: Default::default(),
ref_date: None, ref_date: None,
@@ -77,6 +78,7 @@ mod tests {
let mut entity = Entity { let mut entity = Entity {
id: "entity2".to_string(), id: "entity2".to_string(),
class: "class1".to_string(), class: "class1".to_string(),
subclass: None,
attributes: HashMap::new(), attributes: HashMap::new(),
state: Default::default(), state: Default::default(),
ref_date: None, ref_date: None,
@@ -101,6 +103,7 @@ mod tests {
let mut entity = Entity { let mut entity = Entity {
id: "entity3".to_string(), id: "entity3".to_string(),
class: "class1".to_string(), class: "class1".to_string(),
subclass: None,
attributes: HashMap::new(), attributes: HashMap::new(),
state: Default::default(), state: Default::default(),
ref_date: None, ref_date: None,

View File

@@ -3,11 +3,13 @@ use crate::*;
pub fn run(row: &rusqlite::Row) -> rusqlite::Result<Entity> { pub fn run(row: &rusqlite::Row) -> rusqlite::Result<Entity> {
let id: String = row.get(0)?; let id: String = row.get(0)?;
let class: String = row.get(1)?; let class: String = row.get(1)?;
let ref_date: Option<u64> = row.get(2)?; let subclass: Option<String> = row.get(2)?;
let ref_date: Option<u64> = row.get(3)?;
let entity = Entity { let entity = Entity {
id, id,
state: EntityState::Loaded, state: EntityState::Loaded,
class, class,
subclass,
ref_date, ref_date,
attributes: std::collections::HashMap::new(), attributes: std::collections::HashMap::new(),
}; };
@@ -26,9 +28,10 @@ mod tests {
fn map_row_to_entity_should_correctly_map_valid_row() { fn map_row_to_entity_should_correctly_map_valid_row() {
// Verifies that a valid database row is correctly mapped to an Entity struct with all fields populated. // Verifies that a valid database row is correctly mapped to an Entity struct with all fields populated.
let conn = Connection::open_in_memory().unwrap(); let conn = Connection::open_in_memory().unwrap();
let entity = get_entity_from_query(&conn, "SELECT 'e1', 'c1', 12345").unwrap(); let entity = get_entity_from_query(&conn, "SELECT 'e1', 'c1', 's1', 12345").unwrap();
assert_eq!(entity.id, "e1"); assert_eq!(entity.id, "e1");
assert_eq!(entity.class, "c1"); assert_eq!(entity.class, "c1");
assert_eq!(entity.subclass, Some("s1".to_string()));
assert_eq!(entity.ref_date, Some(12345)); assert_eq!(entity.ref_date, Some(12345));
assert_eq!(entity.state, EntityState::Loaded); assert_eq!(entity.state, EntityState::Loaded);
assert!(entity.attributes.is_empty()); assert!(entity.attributes.is_empty());
@@ -37,9 +40,20 @@ mod tests {
fn map_row_to_entity_should_handle_null_ref_date() { fn map_row_to_entity_should_handle_null_ref_date() {
// Ensures that a row with a NULL 'ref_date' is successfully mapped to an Entity with 'ref_date' as None. // Ensures that a row with a NULL 'ref_date' is successfully mapped to an Entity with 'ref_date' as None.
let conn = Connection::open_in_memory().unwrap(); let conn = Connection::open_in_memory().unwrap();
let entity = get_entity_from_query(&conn, "SELECT 'e2', 'c2', NULL").unwrap(); let entity = get_entity_from_query(&conn, "SELECT 'e2', 'c2', 's2', NULL").unwrap();
assert_eq!(entity.id, "e2"); assert_eq!(entity.id, "e2");
assert_eq!(entity.class, "c2"); assert_eq!(entity.class, "c2");
assert_eq!(entity.subclass, Some("s2".to_string()));
assert_eq!(entity.ref_date, None);
}
#[test]
fn map_row_to_entity_should_handle_null_subclass() {
// Ensures that a row with a NULL 'subclass' is successfully mapped to an Entity with 'subclass' as None.
let conn = Connection::open_in_memory().unwrap();
let entity = get_entity_from_query(&conn, "SELECT 'e2', 'c2', NULL, NULL").unwrap();
assert_eq!(entity.id, "e2");
assert_eq!(entity.class, "c2");
assert_eq!(entity.subclass, None);
assert_eq!(entity.ref_date, None); assert_eq!(entity.ref_date, None);
} }
#[test] #[test]

View File

@@ -113,6 +113,7 @@ mod tests {
let mut entity = Entity { let mut entity = Entity {
id: "e1".to_string(), id: "e1".to_string(),
class: "c1".to_string(), class: "c1".to_string(),
subclass: None,
attributes: HashMap::new(), attributes: HashMap::new(),
state: EntityState::New, state: EntityState::New,
ref_date: None, ref_date: None,
@@ -141,6 +142,7 @@ mod tests {
let entity = Entity { let entity = Entity {
id: "e1".to_string(), id: "e1".to_string(),
class: "c1".to_string(), class: "c1".to_string(),
subclass: None,
attributes: HashMap::new(), attributes: HashMap::new(),
state: EntityState::ToDelete, state: EntityState::ToDelete,
ref_date: None, ref_date: None,
@@ -161,6 +163,7 @@ mod tests {
let to_delete = Entity { let to_delete = Entity {
id: "e1".to_string(), id: "e1".to_string(),
class: "c1".to_string(), class: "c1".to_string(),
subclass: None,
attributes: HashMap::new(), attributes: HashMap::new(),
state: EntityState::ToDelete, state: EntityState::ToDelete,
ref_date: None, ref_date: None,
@@ -168,6 +171,7 @@ mod tests {
let to_add = Entity { let to_add = Entity {
id: "e2".to_string(), id: "e2".to_string(),
class: "c2".to_string(), class: "c2".to_string(),
subclass: None,
attributes: HashMap::new(), attributes: HashMap::new(),
state: EntityState::New, state: EntityState::New,
ref_date: None, ref_date: None,
@@ -190,6 +194,7 @@ mod tests {
let unmodified = Entity { let unmodified = Entity {
id: "e1".to_string(), id: "e1".to_string(),
class: "c1".to_string(), class: "c1".to_string(),
subclass: None,
attributes: HashMap::new(), attributes: HashMap::new(),
state: EntityState::Loaded, state: EntityState::Loaded,
ref_date: None, ref_date: None,
@@ -213,6 +218,7 @@ mod tests {
let mut new_entity = Entity { let mut new_entity = Entity {
id: "e_new".to_string(), id: "e_new".to_string(),
class: "c1".to_string(), class: "c1".to_string(),
subclass: None,
attributes: HashMap::new(), attributes: HashMap::new(),
state: EntityState::New, state: EntityState::New,
ref_date: None, ref_date: None,

View File

@@ -32,8 +32,9 @@
//! and the necessary `From` and `TryFrom` conversions. //! and the necessary `From` and `TryFrom` conversions.
//! //!
//! ```rust,no_run //! ```rust,no_run
//! use heave::{EAV, Catalog, Entity, Value, FailedTo, Comparison, Filter}; //! use heave::*;
//! use std::convert::{From, TryFrom}; //! use std::convert::{From, TryFrom};
//! use std::result::Result;
//! //!
//! // Define a simple struct representing a product. //! // Define a simple struct representing a product.
//! #[derive(Debug, Default, PartialEq, Clone)] //! #[derive(Debug, Default, PartialEq, Clone)]
@@ -68,10 +69,10 @@
//! //!
//! fn try_from(entity: Entity) -> Result<Self, Self::Error> { //! fn try_from(entity: Entity) -> Result<Self, Self::Error> {
//! Ok(Self { //! Ok(Self {
//! id: entity.id, //! id: entity.id.clone(),
//! name: entity.unwrap("name").ok_or(FailedTo::ConvertEntity)?, //! name: entity.unwrap("name").map_err(|_| FailedTo::ConvertEntity)?,
//! price: entity.unwrap("price").ok_or(FailedTo::ConvertEntity)?, //! price: entity.unwrap("price").map_err(|_| FailedTo::ConvertEntity)?,
//! in_stock: entity.unwrap("in_stock").ok_or(FailedTo::ConvertEntity)?, //! in_stock: entity.unwrap("in_stock").map_err(|_| FailedTo::ConvertEntity)?,
//! }) //! })
//! } //! }
//! } //! }

View File

@@ -161,6 +161,24 @@ impl Catalog {
.filter(move |item| item.class == T::class()) .filter(move |item| item.class == T::class())
.map(|item| T::try_from(item.clone()).map_err(|_| FailedTo::ConvertEntity)) .map(|item| T::try_from(item.clone()).map_err(|_| FailedTo::ConvertEntity))
} }
/// Returns an iterator over all entities of a specific class and subclass
/// in the in-memory catalog.
///
/// This is a purely in-memory operation and does not interact with the database.
///
/// # Returns
///
/// An iterator that yields items of type `T` from the in-memory cache.
pub fn list_by_class_and_subclass<T>(&self, subclass: &str) -> impl Iterator<Item = Result<T, FailedTo>>
where
T: EAV,
{
self.items
.values()
.filter(move |item| item.class == T::class())
.filter(move |item| item.subclass == Some(subclass.to_string()))
.map(|item| T::try_from(item.clone()).map_err(|_| FailedTo::ConvertEntity))
}
/// Returns an iterator over entities in the in-memory catalog that match a given /// Returns an iterator over entities in the in-memory catalog that match a given
/// class, attribute, and value. /// class, attribute, and value.
/// ///

View File

@@ -14,6 +14,10 @@ pub struct O {
/// A string identifying the "type" or "class" of the entity (e.g., "product", "user"). /// A string identifying the "type" or "class" of the entity (e.g., "product", "user").
/// This is used to group and query entities of the same kind. /// This is used to group and query entities of the same kind.
pub class: String, pub class: String,
/// A string identifying the "subtype" or "subclass" of the entity (e.g.,
/// "computer", "phone", "customer"). This is used to group and query entities
/// of different subtype but of the same kind or inside the same domain (class)
pub subclass: Option<String>,
/// A map of attribute names to `Attribute` values, holding the actual data /// A map of attribute names to `Attribute` values, holding the actual data
/// of the entity. /// of the entity.
pub attributes: std::collections::HashMap<String, Attribute>, pub attributes: std::collections::HashMap<String, Attribute>,
@@ -34,6 +38,7 @@ impl Entity {
state: EntityState::New, state: EntityState::New,
ref_date: None, ref_date: None,
class: T::class().to_string(), class: T::class().to_string(),
subclass: None,
attributes: std::collections::HashMap::<String, Attribute>::new(), attributes: std::collections::HashMap::<String, Attribute>::new(),
} }
} }
@@ -52,6 +57,11 @@ impl Entity {
self self
} }
pub fn with_subclass(mut self, subclass: &str) -> Self {
self.subclass = Some(subclass.to_string());
self
}
/// Sets the reference date of the entity. /// Sets the reference date of the entity.
/// ///
/// # Arguments /// # Arguments