Skip to content

Instantly share code, notes, and snippets.

@simolus3
Last active November 21, 2023 12:27
Show Gist options
  • Save simolus3/b881d1a5cbb308d549dd484204e25776 to your computer and use it in GitHub Desktop.
Save simolus3/b881d1a5cbb308d549dd484204e25776 to your computer and use it in GitHub Desktop.
Shopping carts in moor
import 'package:moor/moor.dart';
import 'package:moor/moor_vm.dart';
import 'package:rxdart/rxdart.dart';
part 'shopping_cart.g.dart';
class ShoppingCarts extends Table {
IntColumn get id => integer().autoIncrement()();
}
class BuyableItems extends Table {
IntColumn get id => integer().autoIncrement()();
// price, description, idk
}
@DataClassName('ShoppingCartEntry')
class ShoppingCartEntries extends Table {
IntColumn get shoppingCart => integer()();
IntColumn get item => integer()();
}
class CartWithItems {
final ShoppingCart cart;
final List<BuyableItem> items;
CartWithItems(this.cart, this.items);
}
@UseMoor(tables: [ShoppingCarts, BuyableItems, ShoppingCartEntries])
class Database extends _$Database {
@override
final int schemaVersion = 1;
Database(QueryExecutor e) : super(e);
@override
MigrationStrategy get migration {
return MigrationStrategy(
beforeOpen: (_, details) async {
if (details.wasCreated) {
// write some fake items
for (var i = 0; i < 10; i++) {
await into(buyableItems).insert(const BuyableItemsCompanion());
}
}
},
);
}
/// Create a new empty shopping cart
Future<ShoppingCart> createShoppingCart() async {
final id = await into(shoppingCarts).insert(const ShoppingCartsCompanion());
return ShoppingCart(id: id);
}
Future<List<BuyableItem>> loadAllItems() async {
return await select(buyableItems).get();
}
Stream<CartWithItems> watchCart(int id) {
final cartQuery = select(shoppingCarts)
..where((cart) => cart.id.equals(id));
final contentQuery = select(shoppingCartEntries).join(
[
innerJoin(
buyableItems,
buyableItems.id.equalsExp(shoppingCartEntries.item),
),
],
)..where(shoppingCartEntries.shoppingCart.equals(id));
final cartStream = cartQuery.watchSingle();
final contentStream = contentQuery.watch().map((rows) {
return rows.map((row) => row.readTable(buyableItems)).toList();
});
return Observable.combineLatest2(cartStream, contentStream,
(ShoppingCart cart, List<BuyableItem> items) {
return CartWithItems(cart, items);
});
}
Stream<List<CartWithItems>> watchAllCarts() {
// start by watching all carts
final cartStream = Observable(select(shoppingCarts).watch());
return cartStream.switchMap((carts) {
// this method is called whenever the list of carts changes. For each
// cart, we want to load all the items in it.
// (we create a map from id to cart here just for performance reasons)
final idToCart = {for (var cart in carts) cart.id: cart};
final ids = idToCart.keys;
// select all entries that are included in a cart that we found
final entryQuery = select(shoppingCartEntries).join(
[
innerJoin(
buyableItems,
buyableItems.id.equalsExp(shoppingCartEntries.item),
)
],
)..where(isIn(shoppingCartEntries.shoppingCart, ids));
return entryQuery.watch().map((rows) {
// Store the list of entries for each cart, again using maps for faster
// lookups.
final idToItems = <int, List<BuyableItem>>{};
// for each entry (row) that is included in a cart, put it in the map
// of items.
for (var row in rows) {
final item = row.readTable(buyableItems);
final id = row.readTable(shoppingCartEntries).shoppingCart;
idToItems.putIfAbsent(id, () => []).add(item);
}
// finally, all that's left is to merge the map of carts with the map of
// entries
return [
for (var id in ids)
CartWithItems(idToCart[id], idToItems[id] ?? []),
];
});
});
}
Future<void> writeShoppingCart(CartWithItems entry) {
return transaction((_) async {
final cart = entry.cart;
// first, we write the shopping cart
await into(shoppingCarts).insert(cart, orReplace: true);
// replace entries of the cart
await (delete(shoppingCartEntries)
..where((entry) => entry.shoppingCart.equals(cart.id)))
.go();
await into(shoppingCartEntries).insertAll([
for (var item in entry.items)
ShoppingCartEntry(shoppingCart: cart.id, item: item.id),
]);
});
}
}
void main() async {
final db = Database(VMDatabase.memory());
final cart = await db.createShoppingCart();
final availableItems = await db.loadAllItems();
final sub = db.watchCart(cart.id).listen((updated) {
print('Cart ${updated.cart} now consists of ${updated.items}');
});
await db.writeShoppingCart(CartWithItems(cart, availableItems.sublist(0, 3)));
await db.writeShoppingCart(CartWithItems(cart, availableItems.sublist(3, 6)));
await db.writeShoppingCart(CartWithItems(cart, availableItems.sublist(6)));
await Future.delayed(const Duration(seconds: 2));
await sub.cancel();
await db.close();
}
@NicholasBarrett98
Copy link

NicholasBarrett98 commented Nov 19, 2023

I spend all my free time online shopping. I often look for bargains. And after reading this article I learned how to do it more efficiently. Now all my purchases are more thoughtful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment