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 (
id TEXT PRIMARY KEY,
class TEXT NOT NULL,
subclass TEXT,
ref_date INTEGER
);
CREATE TABLE IF NOT EXISTS attribute (

View File

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

View File

@@ -3,11 +3,13 @@ use crate::*;
pub fn run(row: &rusqlite::Row) -> rusqlite::Result<Entity> {
let id: String = row.get(0)?;
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 {
id,
state: EntityState::Loaded,
class,
subclass,
ref_date,
attributes: std::collections::HashMap::new(),
};
@@ -26,9 +28,10 @@ mod tests {
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.
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.class, "c1");
assert_eq!(entity.subclass, Some("s1".to_string()));
assert_eq!(entity.ref_date, Some(12345));
assert_eq!(entity.state, EntityState::Loaded);
assert!(entity.attributes.is_empty());
@@ -37,9 +40,20 @@ mod tests {
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.
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.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);
}
#[test]

View File

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

View File

@@ -32,8 +32,9 @@
//! and the necessary `From` and `TryFrom` conversions.
//!
//! ```rust,no_run
//! use heave::{EAV, Catalog, Entity, Value, FailedTo, Comparison, Filter};
//! use heave::*;
//! use std::convert::{From, TryFrom};
//! use std::result::Result;
//!
//! // Define a simple struct representing a product.
//! #[derive(Debug, Default, PartialEq, Clone)]
@@ -68,10 +69,10 @@
//!
//! fn try_from(entity: Entity) -> Result<Self, Self::Error> {
//! Ok(Self {
//! id: entity.id,
//! name: entity.unwrap("name").ok_or(FailedTo::ConvertEntity)?,
//! price: entity.unwrap("price").ok_or(FailedTo::ConvertEntity)?,
//! in_stock: entity.unwrap("in_stock").ok_or(FailedTo::ConvertEntity)?,
//! id: entity.id.clone(),
//! name: entity.unwrap("name").map_err(|_| FailedTo::ConvertEntity)?,
//! price: entity.unwrap("price").map_err(|_| 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())
.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
/// 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").
/// This is used to group and query entities of the same kind.
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
/// of the entity.
pub attributes: std::collections::HashMap<String, Attribute>,
@@ -34,6 +38,7 @@ impl Entity {
state: EntityState::New,
ref_date: None,
class: T::class().to_string(),
subclass: None,
attributes: std::collections::HashMap::<String, Attribute>::new(),
}
}
@@ -52,6 +57,11 @@ impl Entity {
self
}
pub fn with_subclass(mut self, subclass: &str) -> Self {
self.subclass = Some(subclass.to_string());
self
}
/// Sets the reference date of the entity.
///
/// # Arguments