drift/docs/lib/snippets/many_to_many_relationships....

160 lines
5.1 KiB
Dart

import 'package:drift/drift.dart';
import 'package:rxdart/rxdart.dart';
part 'many_to_many_relationships.g.dart';
// #docregion buyable_items
class BuyableItems extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get description => text()();
IntColumn get price => integer()();
// we could add more columns as we wish.
}
// #enddocregion buyable_items
// #docregion cart_tables
class ShoppingCarts extends Table {
IntColumn get id => integer().autoIncrement()();
// we could also store some further information about the user creating
// this cart etc.
}
@DataClassName('ShoppingCartEntry')
class ShoppingCartEntries extends Table {
// id of the cart that should contain this item.
IntColumn get shoppingCart => integer().references(ShoppingCarts, #id)();
// id of the item in this cart
IntColumn get item => integer().references(BuyableItems, #id)();
// again, we could store additional information like when the item was
// added, an amount, etc.
}
// #enddocregion cart_tables
// #docregion cart
/// Represents a full shopping cart with all its items.
class CartWithItems {
final ShoppingCart cart;
final List<BuyableItem> items;
CartWithItems(this.cart, this.items);
}
// #enddocregion cart
@DriftDatabase(tables: [BuyableItems, ShoppingCarts, ShoppingCartEntries])
class Db extends _$Db {
Db(QueryExecutor e) : super(e);
@override
int get schemaVersion => 1;
// #docregion writeShoppingCart
Future<void> writeShoppingCart(CartWithItems entry) {
return transaction(() async {
final cart = entry.cart;
// first, we write the shopping cart
await into(shoppingCarts).insert(cart, mode: InsertMode.replace);
// we replace the entries of the cart, so first delete the old ones
await (delete(shoppingCartEntries)
..where((entry) => entry.shoppingCart.equals(cart.id)))
.go();
// And write the new ones
for (final item in entry.items) {
await into(shoppingCartEntries)
.insert(ShoppingCartEntry(shoppingCart: cart.id, item: item.id));
}
});
}
// #enddocregion writeShoppingCart
// #docregion createEmptyCart
Future<CartWithItems> createEmptyCart() async {
final id = await into(shoppingCarts).insert(const ShoppingCartsCompanion());
final cart = ShoppingCart(id: id);
// we set the items property to [] because we've just created the cart - it
// will be empty
return CartWithItems(cart, []);
}
// #enddocregion createEmptyCart
// #docregion watchCart
Stream<CartWithItems> watchCart(int id) {
// load information about the cart
final cartQuery = select(shoppingCarts)
..where((cart) => cart.id.equals(id));
// and also load information about the entries in this cart
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) {
// we join the shoppingCartEntries with the buyableItems, but we
// only care about the item here.
return rows.map((row) => row.readTable(buyableItems)).toList();
});
// now, we can merge the two queries together in one stream
return Rx.combineLatest2(cartStream, contentStream,
(ShoppingCart cart, List<BuyableItem> items) {
return CartWithItems(cart, items);
});
}
// #enddocregion watchCart
// #docregion watchAllCarts
Stream<List<CartWithItems>> watchAllCarts() {
// start by watching all carts
final cartStream = select(shoppingCarts).watch();
return cartStream.switchMap((carts) {
// this method is called whenever the list of carts changes. For each
// cart, now 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 any cart that we found
final entryQuery = select(shoppingCartEntries).join(
[
innerJoin(
buyableItems,
buyableItems.id.equalsExp(shoppingCartEntries.item),
)
],
)..where(shoppingCartEntries.shoppingCart.isIn(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 (final 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] ?? []),
];
});
});
}
// #enddocregion watchAllCarts
}