|
@@ -22,7 +22,7 @@
|
|
|
//! never need to interact with any of them!
|
|
|
//!
|
|
|
//! ### Examples
|
|
|
-//!
|
|
|
+//! #### KV-store
|
|
|
//! For the simplest kind of database schema, a key-value store, one possible microrm
|
|
|
//! implementation of it might look like the following:
|
|
|
//!
|
|
@@ -74,7 +74,142 @@
|
|
|
//!
|
|
|
//! # Ok(())
|
|
|
//! # }
|
|
|
+//! ```
|
|
|
+//!
|
|
|
+//! #### Simple e-commerce schema
|
|
|
+//!
|
|
|
+//! The following is an example of what a simple e-commerce website's schema might look like,
|
|
|
+//! tracking products, users, and orders.
|
|
|
+//!
|
|
|
+//! ```rust
|
|
|
+//! use microrm::prelude::*;
|
|
|
+//!
|
|
|
+//! #[derive(Entity)]
|
|
|
+//! pub struct ProductImage {
|
|
|
+//! // note that because this references an entity's autogenerated ID type,
|
|
|
+//! // this is a foreign key. if the linked product is deleted, the linked
|
|
|
+//! // ProductImages will also be deleted.
|
|
|
+//! pub product: ProductID,
|
|
|
+//! pub img_data: Vec<u8>,
|
|
|
+//! }
|
|
|
+//!
|
|
|
+//! #[derive(Entity, Clone)]
|
|
|
+//! pub struct Product {
|
|
|
+//! #[key]
|
|
|
+//! pub title: String,
|
|
|
+//! pub longform_body: String,
|
|
|
+//! pub images: microrm::RelationMap<ProductImage>,
|
|
|
+//! pub cost: f64,
|
|
|
+//! }
|
|
|
+//!
|
|
|
+//! pub struct CustomerOrders;
|
|
|
+//! impl microrm::Relation for CustomerOrders {
|
|
|
+//! type Domain = Customer;
|
|
|
+//! type Range = Order;
|
|
|
+//! const NAME: &'static str = "CustomerOrders";
|
|
|
+//! // at most one customer per order
|
|
|
+//! const INJECTIVE: bool = true;
|
|
|
+//! }
|
|
|
+//!
|
|
|
+//! #[derive(Entity)]
|
|
|
+//! pub struct Customer {
|
|
|
+//! pub orders: microrm::RelationDomain<CustomerOrders>,
|
|
|
+//!
|
|
|
+//! #[key]
|
|
|
+//! pub email: String,
|
|
|
+//!
|
|
|
+//! #[unique]
|
|
|
+//! pub legal_name: String,
|
|
|
+//!
|
|
|
+//! #[elide]
|
|
|
+//! pub password_salt: String,
|
|
|
+//! #[elide]
|
|
|
+//! pub password_hash: String,
|
|
|
+//!
|
|
|
+//! }
|
|
|
+//!
|
|
|
+//! #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
|
|
+//! pub enum OrderState {
|
|
|
+//! AwaitingPayment,
|
|
|
+//! PaymentReceived { confirmation: String },
|
|
|
+//! ProductsReserved,
|
|
|
+//! Shipped { tracking_no: String },
|
|
|
+//! OnHold { reason: String },
|
|
|
+//! }
|
|
|
+//!
|
|
|
+//! #[derive(Entity)]
|
|
|
+//! pub struct Order {
|
|
|
+//! pub order_state: microrm::Serialized<Vec<OrderState>>,
|
|
|
+//! pub customer: microrm::RelationRange<CustomerOrders>,
|
|
|
+//! pub shipping_address: String,
|
|
|
+//!
|
|
|
+//! pub billing_address: Option<String>,
|
|
|
+//!
|
|
|
+//! // we'll assume for now that there's no product multiplicities
|
|
|
+//! pub contents: microrm::RelationMap<Product>,
|
|
|
+//! }
|
|
|
+//!
|
|
|
+//! #[derive(Database)]
|
|
|
+//! pub struct ECommerceDB {
|
|
|
+//! pub products: microrm::IDMap<Product>,
|
|
|
+//! pub customers: microrm::IDMap<Customer>,
|
|
|
+//! pub orders: microrm::IDMap<Order>,
|
|
|
+//! }
|
|
|
+//! # fn main() -> Result<(), microrm::Error> {
|
|
|
+//! // open a database instance
|
|
|
+//! let db = ECommerceDB::open_path(":memory:")?;
|
|
|
+//!
|
|
|
+//! // add an example product
|
|
|
+//! let widget1 = db.products.insert_and_return(Product {
|
|
|
+//! title: "Widget Title Here".into(),
|
|
|
+//! longform_body: "The first kind of widget that WidgetCo produces.".into(),
|
|
|
+//! cost: 100.98,
|
|
|
+//! images: Default::default()
|
|
|
+//! })?;
|
|
|
//!
|
|
|
+//! // add an image for the product
|
|
|
+//! widget1.images.insert(ProductImage {
|
|
|
+//! product: widget1.id(),
|
|
|
+//! img_data: [/* image data goes here */].into(),
|
|
|
+//! });
|
|
|
+//!
|
|
|
+//! // sign us up for this most excellent ecommerce website
|
|
|
+//! let customer1 = db.customers.insert_and_return(Customer {
|
|
|
+//! email: "your@email.here".into(),
|
|
|
+//! legal_name: "Douglas Adams".into(),
|
|
|
+//! password_salt: "pepper".into(),
|
|
|
+//! password_hash: "browns".into(),
|
|
|
+//!
|
|
|
+//! orders: Default::default(),
|
|
|
+//! })?;
|
|
|
+//!
|
|
|
+//! // put in an order for the widget!
|
|
|
+//! let mut order1 = db.orders.insert_and_return(Order {
|
|
|
+//! order_state: vec![OrderState::AwaitingPayment].into(),
|
|
|
+//! customer: Default::default(),
|
|
|
+//! shipping_address: "North Pole, Canada, H0H0H0".into(),
|
|
|
+//! billing_address: None,
|
|
|
+//! contents: Default::default(),
|
|
|
+//! })?;
|
|
|
+//! order1.contents.connect_to(widget1.id())?;
|
|
|
+//! order1.customer.connect_to(customer1.id())?;
|
|
|
+//!
|
|
|
+//! // Now get all products that customer1 has ever ordered
|
|
|
+//! let all_ordered = customer1.orders.join(Order::Contents).get()?;
|
|
|
+//! assert_eq!(all_ordered, vec![widget1]);
|
|
|
+//!
|
|
|
+//! // process the payment for our order by updating the entity
|
|
|
+//! order1.order_state.as_mut().push(
|
|
|
+//! OrderState::PaymentReceived {
|
|
|
+//! confirmation: "money received in full, promise".into()
|
|
|
+//! }
|
|
|
+//! );
|
|
|
+//!
|
|
|
+//! // now synchronize the entity changes back into the database
|
|
|
+//! order1.sync()?;
|
|
|
+//!
|
|
|
+//! # Ok(())
|
|
|
+//! # }
|
|
|
//! ```
|
|
|
|
|
|
#![warn(missing_docs)]
|
|
@@ -90,6 +225,7 @@ pub mod db;
|
|
|
mod query;
|
|
|
pub mod schema;
|
|
|
|
|
|
+pub use schema::index::Index;
|
|
|
pub use schema::relation::{Relation, RelationDomain, RelationMap, RelationRange};
|
|
|
pub use schema::{IDMap, Serialized, Stored};
|
|
|
|