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
{
"_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.
Next Steps
Storage Examples The catalog and order models in full. Inventory Tracker Stock-focused variant of this example. Creating a REST API Expose the catalog to other systems.Frequently asked
How do I model product variants in RUAL storage?
Keep variants inside the product document as an embedded array, so a product page reads one document and never joins. Render the variants from that array and disable out-of-stock sizes in the UI.
How do I prevent double-selling stock in RUAL?
Decrement stock with mutations_increment_by_field inside function_update_document, guarded by a stock >= quantity check. Mutation transactions process sequentially per document, so two checkouts cannot overwrite each other, and on the failure path you hold the order as backorder instead of rejecting the customer.
Why store prices in cents in a RUAL catalog?
Storing the price in cents with default_divide_by = 100 avoids float rounding bugs, and the display divides it back. The same model stores names in lowercase so case-sensitive search just works.