This crate tries to model the very basics of the bookkeeping activity. it is a new rustacean's first open source crate.
A book contains - accounts, - units (may represent currencies) - and transactions, which in turn, contain moves.
``rust
// Yo, welcome to the
bookkeeping` crate.
use bookkeeping::*;
// You may be wondering why a crate tutorial starts with "Yo".
// Well, there's no good explanation for that. It is what it is.
// This crate aims to provide a neat API and amusing documentation. // If you bear it through the tutorial, you should be able to start // bookkeeping (that's a noun) in no-time. // // In case you didn't know, bookkeeping is about keeping record of money // moving around. So, our goal in this tutorial is to teach you how to // keep records of money moving around using this crate.
// The first thing you should know is that all records are stored in // books. So, here's a new book: let mut book = Book::<(), (), (), (), ()>::new(()); // "What are are all of these extra types?" — you must be wondering. // Let me explain. You know what — I won't explain that right now. // The important part is that we have a book. // In that book, we can store accounts, units, transactions and moves. // And doing all of that, is quite simple. So let's get to it.
// Let's start by adding an account for some income channel: let incomekey = book.newaccount(()); // "What's that extra..." — we will get to that. Trust me. // The important part is that we have an account. // Actually, the book has an account. What we own is an account key. // We will later use this key to reference this account.
// It's nice that we have an account, so let's have another one! let bankkey = book.newaccount(()); // And now, that we have two accounts, we can move money around. // Which is exciting. I know. But, actually, we can't do that yet. // Because we don't have any units. So let's talk about units.
// Units may represent currencies. Or cryptocurrencies. Or units of
// distance or volume... But if we're honest, they will usually
// represent some form of money. Yet, it's not this crate's scope to
// make such decisions. As far as this crate is concerned — they're just
// units. We'll talk more about this later.
let usdkey = book.newunit(());
// Look — we have dollars! US dollars (USD). And that ()
argument —
// thank you for being patient and not mentioning it.
// Now that we have two accounts and a unit, we can move money around. // Which is exciting. I know. But, actually, we can't do that yet. // Because, in this crate moving money around is represented by moves. // So, we know that we need a move. But... moves are not directly inside // a book — they live inside transactions. So we'll make a transaction // to hold the move: book.insert_transaction(0, ());
// That 0
argument is the index in which to insert the transaction
// into the book. You see, a book holds a single ordered collection of
// transactions. So... this created a new transaction and inserted it
// into the book at index 0
.
// So the book now has two accounts, one unit and one transaction. // So, now we can move money around. Which is exciting. I know. // But, actually, we can't do that yet. Cool motif, huh? We now need a // sum. "A what—now?" you ask? A sum. Look: let mut sum = Sum::new(); sum.setamountforunit(2000, usdkey); // We have created a sum and set the amount of a specific unit in it. // "Wait — support for multiple units?" (that's you, asking). // Yes. Sums support multiple units. Thank Joe for that. It's his idea. // We'll get to using multiple units later. For now, this sum represents // a hundred USD.
// So now the book contains two accounts, one unit and one transaction
// and also a sum that we own directly. So now we can move money around.
// Exciting, isn't it? And this is as far as this motif goes.
// Because now we really can move money around. Look:
book.insertmove(0, 0, incomekey, bank_key, sum, ());
// What this did is created a new move and inserted it into the existing
// transaction that is at index 0
. We only have one transaction, so
// that's where it is. And the move was inserted at index 0
in the
// transaction. You see — moves in a transaction are orderd. So, it's
// kind of like this:
//
// - book
// 0. transaction
// 0. move
//
// The move is of 100 dollars from the income account and to the bank
// account. What a miracle...
// As you may have guessed, you may add more accounts, units, // transactions and moves. Here are three points to get in your mind. // You may know best how to do that. // - Accounts and units are referenced by keys. // - Transactions and moves are referenced by indexes. // - The index of a transaction is its index in the whole book. // - The index of a move is its index in the transaction it's inside of. // OK, those are actually four points.
// At this point (no pun intended), we would like to see the balance of
// the accounts. Well, I would — and I'm writing this tutorial, so:
let balance = book.accountbalanceattransaction(incomekey, 0);
asserteq!(
balance.amounts().collect::
// Let's move more money around, just to confirm our understanding:
let walletkey = book.newaccount(());
// This created a new account that represents a wallet.
book.inserttransaction(1, ());
// This created a new empty transaction and inserted it at index 1
.
let mut sum = Sum::new();
sum.setamountforunit(100, usdkey);
book.insertmove(1, 0, bankkey, walletkey, sum, ());
// Created and inserted a move of 100 USD from the bank account to the
// wallet account. Isn't this fun?
// Now, let's see some balances, using the index of this most recent
// transaction:
let balance = book.accountbalanceattransaction(incomekey, 1);
asserteq!(
balance.amounts().collect::
// Now, let's insert a new transaction between the two existing ones:
book.inserttransaction(1, ());
let mut sum = Sum::new();
sum.setamountforunit(1000, usdkey);
book.insertmove(1, 0, incomekey, bankkey, sum, ());
// And look at a running balance of the bank account:
let bankrunningbalance: Vec
// So far, we've learned a few methods that insert data into the book // and one that calculates a balance. You may have noticed that in order // to call [Book::accountbalanceat_transaction], we need to have both // a key of an existing account and an index of an existing transaction. // So, how can we obtain these? This way for accounts and units: let _accounts: Vec<(AccountKey, &Account<()>)> = book.accounts().collect(); let _units: Vec<(UnitKey, &Unit<()>)> = book.units().collect(); // Note that the order of the iterator returned from [Book::accounts] is // undefined. And this way for transactions: let _transactions: Vec<(usize, &Transaction<(), ()>)> = book.transactions().enumerate().collect(); ```
rust
use bookkeeping::*;
// It's probably time to explain all those `()` arguments that we've so
// far been patient regarding. This crate allows arbitrary data to be
// attached/added/included in the book itself and all records in it:
// accounts, units, moves and transactions.
//
// When creating a book, the _types_ of these metadata must be provided.
// So far, `()` has been provided as the metadata type for all records.
// Let's define some non-`()` metadata types:
struct AccountMetadata {
id: u8,
name: &'static str,
}
struct UnitMetadata {
currency_code: &'static str,
decimal_places: u8,
}
let mut book: Book<u8, AccountMetadata, UnitMetadata, (), &str> =
Book::new(5);
// In order, the types of metadata that are defined in this example are:
//
// - For the book itself, just a `u8`. Perhaps in your system, books are
// identified with merely an integer.
// - For each account, there's an `id` of a `u8` and a `name` of a
// `&'static str`.
// - With units, we'd like to represent currencies.
// - For moves, we seem to not require metadata in this example.
// - Transactions have merely a `&str` that perhaps is used as a note.
//
// Now, let's see how these metadata types are used:
assert_eq!(book.metadata(), &5);
// Alright!
let wallet_key = book.new_account(AccountMetadata {
id: 7,
name: "Wallet",
});
let bank_key = book.new_account(AccountMetadata {
id: 8,
name: "Bank",
});
assert_eq!(&book.get_account(wallet_key).metadata().id, &7);
assert_eq!(&book.get_account(wallet_key).metadata().name, &"Wallet");
assert_eq!(&book.get_account(bank_key).metadata().id, &8);
assert_eq!(&book.get_account(bank_key).metadata().name, &"Bank");
// Cool!
let usd_key = book.new_unit(UnitMetadata {
currency_code: "USD",
decimal_places: 2,
});
assert_eq!(&book.get_unit(usd_key).metadata().currency_code, &"USD");
assert_eq!(book.get_unit(usd_key).metadata().decimal_places, 2);
// Sweet!
book.insert_transaction(0, "Withdrawal");
assert_eq!(
book.transactions().nth(0).unwrap().metadata(),
&"Withdrawal"
);
// Rad!
book.insert_move(0, 0, bank_key, wallet_key, Sum::new(), ());
assert_eq!(
book.transactions()
.nth(0)
.unwrap()
.moves()
.nth(0)
.unwrap()
.metadata(),
&(),
);
// Dope!