doc: add an example to showcase how to work with class and subclass
This commit is contained in:
227
01.workspace/heave/examples/working_with_many_types.rs
Normal file
227
01.workspace/heave/examples/working_with_many_types.rs
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
use heave::*;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
struct Laptop {
|
||||||
|
pub id: String,
|
||||||
|
pub model: String,
|
||||||
|
pub price: u64,
|
||||||
|
}
|
||||||
|
struct Display {
|
||||||
|
pub id: String,
|
||||||
|
pub model: String,
|
||||||
|
pub resolution: f64,
|
||||||
|
pub price: u64,
|
||||||
|
}
|
||||||
|
struct Mouse {
|
||||||
|
pub id: String,
|
||||||
|
pub model: String,
|
||||||
|
pub wireless: bool,
|
||||||
|
pub price: u64,
|
||||||
|
}
|
||||||
|
enum Product {
|
||||||
|
None,
|
||||||
|
Laptop(Laptop),
|
||||||
|
Display(Display),
|
||||||
|
Mouse(Mouse),
|
||||||
|
}
|
||||||
|
impl EAV for Product {
|
||||||
|
fn class() -> &'static str {
|
||||||
|
"product"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Entity> for Product {
|
||||||
|
fn from(value: Entity) -> Self {
|
||||||
|
if let Some(ref subclass) = value.subclass {
|
||||||
|
match subclass.as_ref() {
|
||||||
|
"laptop" => Product::Laptop(Laptop {
|
||||||
|
id: value.id.clone(),
|
||||||
|
model: value.unwrap("model").expect("model is mandatory"),
|
||||||
|
price: value.unwrap("price").expect("price is mandatory"),
|
||||||
|
}),
|
||||||
|
"display" => Product::Display(Display {
|
||||||
|
id: value.id.clone(),
|
||||||
|
model: value.unwrap("model").expect("model is mandatory"),
|
||||||
|
price: value.unwrap("price").expect("price is mandatory"),
|
||||||
|
resolution: value.unwrap("resolution").expect("resolution is mandatory"),
|
||||||
|
}),
|
||||||
|
"mouse" => Product::Mouse(Mouse {
|
||||||
|
id: value.id.clone(),
|
||||||
|
model: value.unwrap("model").expect("model is mandatory"),
|
||||||
|
price: value.unwrap("price").expect("price is mandatory"),
|
||||||
|
wireless: value.unwrap("wireless").expect("wireless is mandatory"),
|
||||||
|
}),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Product::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Product> for Entity {
|
||||||
|
fn from(value: Product) -> Self {
|
||||||
|
match value {
|
||||||
|
Product::Laptop(value) => Entity::new::<Product>()
|
||||||
|
.with_id(&value.id)
|
||||||
|
.with_subclass("laptop")
|
||||||
|
.with_attribute("model", value.model)
|
||||||
|
.with_attribute("price", value.price),
|
||||||
|
Product::Display(value) => Entity::new::<Product>()
|
||||||
|
.with_id(&value.id)
|
||||||
|
.with_subclass("display")
|
||||||
|
.with_attribute("model", value.model)
|
||||||
|
.with_attribute("resolution", value.resolution)
|
||||||
|
.with_attribute("price", value.price),
|
||||||
|
Product::Mouse(value) => Entity::new::<Product>()
|
||||||
|
.with_id(&value.id)
|
||||||
|
.with_subclass("mouse")
|
||||||
|
.with_attribute("model", value.model)
|
||||||
|
.with_attribute("wireless", value.wireless)
|
||||||
|
.with_attribute("price", value.price),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn main() -> Result<(), FailedTo> {
|
||||||
|
let db_path = "working_with_many_types.db";
|
||||||
|
|
||||||
|
// Clean up previous runs if file exists
|
||||||
|
if Path::new(db_path).exists() {
|
||||||
|
std::fs::remove_file(db_path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Initialize and Persist Data
|
||||||
|
println!("== 1. Storing different product types ==");
|
||||||
|
let mut catalog = Catalog::new(db_path);
|
||||||
|
catalog.init()?;
|
||||||
|
|
||||||
|
let products_to_add = vec![
|
||||||
|
Product::Laptop(Laptop {
|
||||||
|
id: "laptop_01".to_string(),
|
||||||
|
model: "Titan".to_string(),
|
||||||
|
price: 1500,
|
||||||
|
}),
|
||||||
|
Product::Display(Display {
|
||||||
|
id: "display_01".to_string(),
|
||||||
|
model: "CrystalClear".to_string(),
|
||||||
|
resolution: 4.0, // 4K
|
||||||
|
price: 600,
|
||||||
|
}),
|
||||||
|
Product::Mouse(Mouse {
|
||||||
|
id: "mouse_01".to_string(),
|
||||||
|
model: "SwiftClick".to_string(),
|
||||||
|
wireless: true,
|
||||||
|
price: 80,
|
||||||
|
}),
|
||||||
|
Product::Laptop(Laptop {
|
||||||
|
id: "laptop_02".to_string(),
|
||||||
|
model: "Nomad".to_string(),
|
||||||
|
price: 950,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
catalog.insert_many(products_to_add)?;
|
||||||
|
catalog.persist()?;
|
||||||
|
println!("✅ 4 products saved to the database.\n");
|
||||||
|
|
||||||
|
// 2. Load data using filters
|
||||||
|
println!("== 2. Loading products using class and subclass filters ==");
|
||||||
|
|
||||||
|
// Load only laptops
|
||||||
|
let mut laptop_catalog = Catalog::new(db_path);
|
||||||
|
let laptop_filter = Filter::new()
|
||||||
|
.with_class(Product::class())
|
||||||
|
.with_subclass("laptop");
|
||||||
|
|
||||||
|
laptop_catalog.load_by_filter(&laptop_filter)?;
|
||||||
|
|
||||||
|
let laptops: Vec<Product> = laptop_catalog.list_by_class().map(|p| p.unwrap()).collect();
|
||||||
|
|
||||||
|
println!("✅ Loaded {} laptop(s) using filter.", laptops.len());
|
||||||
|
assert_eq!(laptops.len(), 2);
|
||||||
|
for p in laptops {
|
||||||
|
if let Product::Laptop(laptop) = p {
|
||||||
|
println!(
|
||||||
|
" - Laptop: {} ({}) - ${}",
|
||||||
|
laptop.id, laptop.model, laptop.price
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("== 3. Loading products using attribute filters ==");
|
||||||
|
// Load expensive products (price > 1000)
|
||||||
|
let mut expensive_catalog = Catalog::new(db_path);
|
||||||
|
let expensive_filter = Filter::new()
|
||||||
|
.with_class(Product::class())
|
||||||
|
.with_unsigned_int("price", Comparison::Greater, 1000);
|
||||||
|
|
||||||
|
expensive_catalog.load_by_filter(&expensive_filter)?;
|
||||||
|
|
||||||
|
let expensive_products: Vec<Product> = expensive_catalog
|
||||||
|
.list_by_class()
|
||||||
|
.map(|p| p.unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"✅ Loaded {} product(s) with price > $1000.",
|
||||||
|
expensive_products.len()
|
||||||
|
);
|
||||||
|
assert_eq!(expensive_products.len(), 1);
|
||||||
|
|
||||||
|
for p in expensive_products {
|
||||||
|
match p {
|
||||||
|
Product::Laptop(laptop) => {
|
||||||
|
println!(
|
||||||
|
" - Found Laptop: {} ({}) - ${}",
|
||||||
|
laptop.id, laptop.model, laptop.price
|
||||||
|
);
|
||||||
|
assert_eq!(laptop.id, "laptop_01");
|
||||||
|
}
|
||||||
|
_ => panic!("Expected a laptop!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("== 4. Loading all product types ==");
|
||||||
|
let mut all_products_catalog = Catalog::new(db_path);
|
||||||
|
let all_products_filter = Filter::new().with_class(Product::class());
|
||||||
|
all_products_catalog.load_by_filter(&all_products_filter)?;
|
||||||
|
|
||||||
|
let all_products: Vec<Product> = all_products_catalog
|
||||||
|
.list_by_class()
|
||||||
|
.map(|p| p.unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
println!("✅ Loaded {} total products.", all_products.len());
|
||||||
|
assert_eq!(all_products.len(), 4);
|
||||||
|
|
||||||
|
for p in all_products {
|
||||||
|
match p {
|
||||||
|
Product::Laptop(laptop) => {
|
||||||
|
println!(
|
||||||
|
" - Found Laptop: {} ({}) - ${}",
|
||||||
|
laptop.id, laptop.model, laptop.price
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Product::Display(display) => {
|
||||||
|
println!(
|
||||||
|
" - Found Display: {} ({}) - ${}",
|
||||||
|
display.id, display.model, display.price
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Product::Mouse(mouse) => {
|
||||||
|
println!(
|
||||||
|
" - Found Mouse: {} ({}) - ${}",
|
||||||
|
mouse.id, mouse.model, mouse.price
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Product::None => panic!("Product::None should not be loaded"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Clean up the created database file
|
||||||
|
std::fs::remove_file(db_path).unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user