Example: E-Commerce Catalog
Products, variants, categories, stock, and storefront search. A complete catalog with the data model that makes it fast.
A catalog looks relational but shouldn't be modeled that way on RUAL: products carry everything their page shows. This example covers the model, the storefront, and stock handling that can't double-sell.
The Product Document
Product Document
{
"_meta": {
"cms": 1780531200118,
"created": 1780531200,
"entity": 1,
"expiry": -1,
"guid": "c45797061f2c15a75361042244385e96419d230ab804d7fa08cd52981fd78192",
"removed": 0,
"ums": 1782166695204,
"update_hash": "f1a9c2d47b6035e8419dc0a7532be96481cd30fa",
"updated": 1782166695
},
"category": "apparel",
"image": "/static/products/tshirt-black.webp",
"name": "essential cotton t-shirt",
"price": 2490,
"sku": "TSHIRT-BLK-M",
"status": "active",
"variants": [
{
"size": "m",
"sku": "TSHIRT-BLK-M",
"stock": 14
},
{
"size": "l",
"sku": "TSHIRT-BLK-L",
"stock": 3
}
]
}- Variants live inside the product. A product page reads one document. Never joins (see catalog model).
- Price in cents with
default_divide_by = 100: no float rounding bugs. - Everything stored lowercase so case-sensitive search just works.
The Storefront
- Category page: term filter on
category+status = active, sorted by name, paginated withlimit/offset. - Search box:
query_bool_simple_query_string_fieldonnamewrapped inquery_bool_must, with the active-filter inquery_bool_filterviaquery_and. - Product page: fetch by
skuwithfunction_search_single_result; render variants from the embedded array, disabling out-of-stock sizes in the UI. - Autocomplete: prefix/wildcard query on
namebehind a small API. The pattern in search optimization.
Stock Without Double-Selling
- Checkout decrements stock with
mutations_increment_by_field(-quantity) insidefunction_update_document: mutation transactions process sequentially, so two checkouts can't overwrite each other. - Guard the sale: only decrement when
stock >= quantity; on the failure path, hold the order asbackorderinstead of rejecting the customer. - Restock is the same increment with a positive value. One block, both directions.
Admin Side
- Product management pages scoped
*loggedin+catalog_managercustom scope. See User Authentication. - Price/name changes go through
function_update_document_mutations; images via the asset pipeline in File Upload & Processing. - Discontinuing is a status change, not a delete: old orders keep their embedded snapshots intact.
