diff options
| author | Joe Carstairs <me@joeac.net> | 2024-11-24 15:45:04 +0000 |
|---|---|---|
| committer | Joe Carstairs <me@joeac.net> | 2024-11-24 15:56:01 +0000 |
| commit | 1f5dcf9762a83d5972c0c8dbad22732505bf621c (patch) | |
| tree | 5a1f5edd993733d48016ce2a8a8691b8f446858a /rust/schist_models | |
| parent | 022795ffcaaa0d904c9f911d8a059885c2254278 (diff) | |
Swaps budget updates for budget drips
Diffstat (limited to 'rust/schist_models')
| -rw-r--r-- | rust/schist_models/Cargo.toml | 1 | ||||
| -rw-r--r-- | rust/schist_models/diesel.toml | 9 | ||||
| -rw-r--r-- | rust/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql | 2 | ||||
| -rw-r--r-- | rust/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql | 16 | ||||
| -rw-r--r-- | rust/schist_models/src/budget.rs | 43 | ||||
| -rw-r--r-- | rust/schist_models/src/budget_drip.rs | 14 | ||||
| -rw-r--r-- | rust/schist_models/src/budget_period_unit.rs | 112 | ||||
| -rw-r--r-- | rust/schist_models/src/budget_update.rs | 16 | ||||
| -rw-r--r-- | rust/schist_models/src/category.rs | 7 | ||||
| -rw-r--r-- | rust/schist_models/src/category_balance.rs | 7 | ||||
| -rw-r--r-- | rust/schist_models/src/category_budget.rs | 9 | ||||
| -rw-r--r-- | rust/schist_models/src/lib.rs | 6 | ||||
| -rw-r--r-- | rust/schist_models/src/schema.rs | 14 | ||||
| -rw-r--r-- | rust/schist_models/tests/budget_period_unit.rs | 86 |
14 files changed, 251 insertions, 91 deletions
diff --git a/rust/schist_models/Cargo.toml b/rust/schist_models/Cargo.toml index 6be4e21..a3e5021 100644 --- a/rust/schist_models/Cargo.toml +++ b/rust/schist_models/Cargo.toml @@ -13,4 +13,5 @@ schist_traits = { path = "../schist_traits" } serde = { workspace = true, features = ["derive"] } [dev-dependencies] +libsqlite3-sys = { workspace = true, features = ["bundled"] } schist_queries = { path = "../schist_queries" } diff --git a/rust/schist_models/diesel.toml b/rust/schist_models/diesel.toml new file mode 100644 index 0000000..2b8ebd8 --- /dev/null +++ b/rust/schist_models/diesel.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] + +[migrations_directory] +dir = "/home/joeac/src/schist/rust/schist_models/migrations" diff --git a/rust/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql b/rust/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql index 8cf4b1e..b9b1675 100644 --- a/rust/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql +++ b/rust/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql @@ -1,5 +1,5 @@ DROP TABLE accounts; -DROP TABLE budget_updates; +DROP TABLE budget_drips; DROP TABLE categories; DROP TABLE transaction_categorisations; DROP TABLE category_transfers; diff --git a/rust/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql b/rust/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql index 9038b54..a684ce7 100644 --- a/rust/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql +++ b/rust/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql @@ -7,22 +7,26 @@ CREATE TABLE accounts( opening_date TEXT NOT NULL ); -CREATE TABLE budget_updates( +CREATE TABLE budget_drips( id INTEGER NOT NULL PRIMARY KEY, category_id INTEGER NOT NULL, date TEXT NOT NULL, - new_budget INTEGER NOT NULL, - new_period INTEGER NOT NULL, - UNIQUE (category_id, date), + quantity INTEGER NOT NULL, FOREIGN KEY (category_id) REFERENCES categories (id) ON UPDATE CASCADE - ON DELETE RESTRICT + ON DELETE RESTRICT, + UNIQUE(category_id, date) ); CREATE TABLE categories( id INTEGER NOT NULL PRIMARY KEY, - name TEXT NOT NULL + name TEXT NOT NULL, + balance INTEGER NOT NULL, + balance_date TEXT NOT NULL, + budget_period INTEGER NOT NULL, + budget_period_unit TEXT NOT NULL, + budget_quantity INTEGER NOT NULL ); CREATE TABLE category_transfers( diff --git a/rust/schist_models/src/budget.rs b/rust/schist_models/src/budget.rs deleted file mode 100644 index 03261e5..0000000 --- a/rust/schist_models/src/budget.rs +++ /dev/null @@ -1,43 +0,0 @@ -use serde::Serialize; - -use super::budget_update::BudgetUpdate; - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)] -pub struct Budget { - pub quantity: i32, - pub period: i32, -} - -impl From<&BudgetUpdate> for Budget { - fn from(budget_update: &BudgetUpdate) -> Self { - Self { - quantity: budget_update.new_budget, - period: budget_update.new_period, - } - } -} - -#[cfg(test)] -mod test { - use crate::date_utc::DateUtc; - use schist_traits::nowlike::Nowlike; - - use super::*; - - #[test] - fn when_from_budget_update_then_returns_budget() { - let quantity = 2; - let period = 3; - let budget_update = BudgetUpdate { - id: 0, - category_id: 1, - date: DateUtc::now(), - new_budget: quantity, - new_period: period, - }; - - let budget = Budget::from(&budget_update); - - assert_eq!(budget, Budget { quantity, period }); - } -} diff --git a/rust/schist_models/src/budget_drip.rs b/rust/schist_models/src/budget_drip.rs new file mode 100644 index 0000000..f467ecd --- /dev/null +++ b/rust/schist_models/src/budget_drip.rs @@ -0,0 +1,14 @@ +use derive_builder::Builder; +use diesel::prelude::{Identifiable, Insertable, Queryable, Selectable}; +use serde::{Deserialize, Serialize}; + +use crate::date_utc::DateUtc; + +#[derive(Builder, Queryable, Identifiable, Selectable, Debug, PartialEq, Serialize, Deserialize, Insertable)] +#[diesel(table_name = crate::schema::budget_drips)] +pub struct BudgetDrip { + pub id: i32, + pub category_id: i32, + pub date: DateUtc, + pub quantity: i32, +} diff --git a/rust/schist_models/src/budget_period_unit.rs b/rust/schist_models/src/budget_period_unit.rs new file mode 100644 index 0000000..77d8641 --- /dev/null +++ b/rust/schist_models/src/budget_period_unit.rs @@ -0,0 +1,112 @@ +use core::fmt; +use std::str::FromStr; + +use anyhow::{Result, bail}; +use diesel::{backend::Backend, deserialize::{self, FromSql, FromSqlRow}, expression::AsExpression, serialize::{self, ToSql}, sql_types::Text, sqlite::Sqlite}; +use serde::{Deserialize, Serialize}; + +#[derive( + Eq, + PartialEq, + Serialize, + Deserialize, + Clone, + Debug, + AsExpression, + FromSqlRow, +)] +#[diesel(sql_type=Text)] +pub enum BudgetPeriodUnit { + Day, + Month, + Year, +} + +impl fmt::Display for BudgetPeriodUnit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BudgetPeriodUnit::Day => "DAY".fmt(f), + BudgetPeriodUnit::Month => "MONTH".fmt(f), + BudgetPeriodUnit::Year => "YEAR".fmt(f), + } + } +} + +impl FromStr for BudgetPeriodUnit { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result<Self> { + match s { + "DAY" => Ok(BudgetPeriodUnit::Day), + "MONTH" => Ok(BudgetPeriodUnit::Month), + "YEAR" => Ok(BudgetPeriodUnit::Year), + _ => bail!("\"{}\" is not a valid budget period unit. Valid values are: DAY, MONTH, YEAR", s), + } + } +} + +impl ToSql<Text, Sqlite> for BudgetPeriodUnit { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, Sqlite>, + ) -> serialize::Result { + out.set_value(self.to_string()); + Ok(serialize::IsNull::No) + } +} + +impl FromSql<Text, Sqlite> for BudgetPeriodUnit { + fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<BudgetPeriodUnit> { + let str = <String as FromSql<Text, Sqlite>>::from_sql(bytes)?; + Ok(str.parse()?) + } +} + +#[cfg(test)] +mod test { + use anyhow::Result; + + use super::BudgetPeriodUnit; + + #[test] + fn display_when_day_then_day() { + assert_eq!("DAY", format!("{}", BudgetPeriodUnit::Day)); + } + + #[test] + fn display_when_month_then_month() { + assert_eq!("MONTH", format!("{}", BudgetPeriodUnit::Month)); + } + + #[test] + fn display_when_year_then_year() { + assert_eq!("YEAR", format!("{}", BudgetPeriodUnit::Year)); + } + + #[test] + fn from_str_when_day_then_day() { + let result: Result<BudgetPeriodUnit> = "DAY".parse(); + assert!(result.is_ok()); + assert_eq!(BudgetPeriodUnit::Day, result.unwrap()); + } + + #[test] + fn from_str_when_month_then_month() { + let result: Result<BudgetPeriodUnit> = "MONTH".parse(); + assert!(result.is_ok()); + assert_eq!(BudgetPeriodUnit::Month, result.unwrap()); + } + + #[test] + fn from_str_when_year_then_year() { + let result: Result<BudgetPeriodUnit> = "YEAR".parse(); + assert!(result.is_ok()); + assert_eq!(BudgetPeriodUnit::Year, result.unwrap()); + } + + #[test] + fn from_str_when_sentence_case_day_then_err() { + let result: Result<BudgetPeriodUnit> = "Day".parse(); + assert!(result.is_err()); + } +} diff --git a/rust/schist_models/src/budget_update.rs b/rust/schist_models/src/budget_update.rs deleted file mode 100644 index 86645cb..0000000 --- a/rust/schist_models/src/budget_update.rs +++ /dev/null @@ -1,16 +0,0 @@ -use derive_builder::Builder; -use diesel::prelude::*; -use serde::Serialize; - -use crate::{category::Category, date_utc::DateUtc}; - -#[derive(Builder, Clone, Serialize, Queryable, Identifiable, Selectable, Associations, Debug, PartialEq, Eq, Insertable)] -#[diesel(table_name = crate::schema::budget_updates)] -#[diesel(belongs_to(Category))] -pub struct BudgetUpdate { - pub id: i32, - pub category_id: i32, - pub date: DateUtc, - pub new_budget: i32, - pub new_period: i32, -} diff --git a/rust/schist_models/src/category.rs b/rust/schist_models/src/category.rs index 55c7b97..c85ff51 100644 --- a/rust/schist_models/src/category.rs +++ b/rust/schist_models/src/category.rs @@ -2,9 +2,16 @@ use derive_builder::Builder; use diesel::prelude::*; use serde::{Deserialize, Serialize}; +use crate::{budget_period_unit::BudgetPeriodUnit, date_utc::DateUtc}; + #[derive(Builder, Queryable, Identifiable, Selectable, Debug, PartialEq, Serialize, Deserialize, Insertable)] #[diesel(table_name = crate::schema::categories)] pub struct Category { pub id: i32, pub name: String, + pub balance: i32, + pub balance_date: DateUtc, + pub budget_period: i32, + pub budget_period_unit: BudgetPeriodUnit, + pub budget_quantity: i32, } diff --git a/rust/schist_models/src/category_balance.rs b/rust/schist_models/src/category_balance.rs deleted file mode 100644 index 796b40b..0000000 --- a/rust/schist_models/src/category_balance.rs +++ /dev/null @@ -1,7 +0,0 @@ -use serde::Serialize; - -#[derive(Serialize)] -pub struct CategoryBalance { - pub category_id: i32, - pub balance: i32, -} diff --git a/rust/schist_models/src/category_budget.rs b/rust/schist_models/src/category_budget.rs deleted file mode 100644 index 9796261..0000000 --- a/rust/schist_models/src/category_budget.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde::Serialize; - -use super::budget::Budget; - -#[derive(Serialize)] -pub struct CategoryBudget { - pub category_id: i32, - pub budget: Option<Budget>, -} diff --git a/rust/schist_models/src/lib.rs b/rust/schist_models/src/lib.rs index 9e60e80..1712c36 100644 --- a/rust/schist_models/src/lib.rs +++ b/rust/schist_models/src/lib.rs @@ -1,9 +1,7 @@ pub mod account; -pub mod budget; -pub mod budget_update; +pub mod budget_drip; +pub mod budget_period_unit; pub mod category; -pub mod category_balance; -pub mod category_budget; pub mod category_transfer; pub mod date_utc; pub mod datetime_utc; diff --git a/rust/schist_models/src/schema.rs b/rust/schist_models/src/schema.rs index 372f8f3..5164d07 100644 --- a/rust/schist_models/src/schema.rs +++ b/rust/schist_models/src/schema.rs @@ -10,12 +10,11 @@ diesel::table! { } diesel::table! { - budget_updates (id) { + budget_drips (id) { id -> Integer, category_id -> Integer, date -> Text, - new_budget -> Integer, - new_period -> Integer, + quantity -> Integer, } } @@ -23,6 +22,11 @@ diesel::table! { categories (id) { id -> Integer, name -> Text, + balance -> Integer, + balance_date -> Text, + budget_period -> Integer, + budget_period_unit -> Text, + budget_quantity -> Integer, } } @@ -57,14 +61,14 @@ diesel::table! { } } -diesel::joinable!(budget_updates -> categories (category_id)); +diesel::joinable!(budget_drips -> categories (category_id)); diesel::joinable!(transaction_categorisations -> categories (category_id)); diesel::joinable!(transaction_categorisations -> transactions (transaction_id)); diesel::joinable!(transactions -> accounts (account_id)); diesel::allow_tables_to_appear_in_same_query!( accounts, - budget_updates, + budget_drips, categories, category_transfers, transaction_categorisations, diff --git a/rust/schist_models/tests/budget_period_unit.rs b/rust/schist_models/tests/budget_period_unit.rs new file mode 100644 index 0000000..6e8928a --- /dev/null +++ b/rust/schist_models/tests/budget_period_unit.rs @@ -0,0 +1,86 @@ +mod common; + +use anyhow::Result; +use common::test_context::TestContext; +use diesel::{sql_query, Connection, ExpressionMethods, QueryDsl, RunQueryDsl, Selectable, SelectableHelper, SqliteConnection}; +use diesel::prelude::{Insertable, Queryable}; +use schist_models::budget_period_unit::BudgetPeriodUnit; + +diesel::table! { + budget_period_unit_test (id) { + id -> Integer, + budget_period_unit -> Text, + } +} + +#[derive(Insertable, Queryable, Selectable, Clone)] +#[diesel(table_name = budget_period_unit_test)] +pub struct BudgetPeriodUnitTest { + pub id: i32, + pub budget_period_unit: BudgetPeriodUnit, +} + +fn new_test_context() -> Result<TestContext> { + let test_context = TestContext::new(); + let connection = &mut SqliteConnection::establish(&test_context.db_url)?; + let query = "CREATE TABLE budget_period_unit_test(id INTEGER PRIMARY KEY, budget_period_unit TEXT);"; + sql_query(query).execute(connection)?; + Ok(test_context) +} + +fn insert_budget_period_unit(id: i32, budget_period_unit: &BudgetPeriodUnit, connection: &mut SqliteConnection) -> Result<usize> { + let model = BudgetPeriodUnitTest { + id, + budget_period_unit: budget_period_unit.clone(), + }; + let num_rows_inserted = diesel::insert_into(budget_period_unit_test::dsl::budget_period_unit_test) + .values(&[model]) + .execute(connection)?; + Ok(num_rows_inserted) +} + +fn get_budget_period_unit(id: i32, connection: &mut SqliteConnection) -> Result<BudgetPeriodUnitTest> { + let budget_period_unit: Vec<BudgetPeriodUnitTest> = budget_period_unit_test::dsl::budget_period_unit_test + .select(BudgetPeriodUnitTest::as_select()) + .filter(budget_period_unit_test::id.eq(id)) + .load(connection)?; + Ok(budget_period_unit[0].clone()) +} + +#[test] +fn when_insert_day_and_select_then_returns_day() { + let test_context = new_test_context().unwrap(); + let connection = &mut SqliteConnection::establish(&test_context.db_url).unwrap(); + + let budget_period_unit = BudgetPeriodUnit::Day; + let num_rows_inserted = insert_budget_period_unit(0, &budget_period_unit, connection); + + assert!(num_rows_inserted.is_ok()); + let num_rows_inserted = num_rows_inserted.unwrap(); + assert_eq!(num_rows_inserted, 1); + + let budget_period_unit_returned = get_budget_period_unit(0, connection); + + assert!(budget_period_unit_returned.is_ok()); + let budget_period_unit_returned = budget_period_unit_returned.unwrap(); + assert_eq!(BudgetPeriodUnit::Day, budget_period_unit_returned.budget_period_unit); +} + +#[test] +fn when_insert_month_and_select_then_returns_month() { + let test_context = new_test_context().unwrap(); + let connection = &mut SqliteConnection::establish(&test_context.db_url).unwrap(); + + let budget_period_unit = BudgetPeriodUnit::Month; + let num_rows_inserted = insert_budget_period_unit(0, &budget_period_unit, connection); + + assert!(num_rows_inserted.is_ok()); + let num_rows_inserted = num_rows_inserted.unwrap(); + assert_eq!(num_rows_inserted, 1); + + let budget_period_unit_returned = get_budget_period_unit(0, connection); + + assert!(budget_period_unit_returned.is_ok()); + let budget_period_unit_returned = budget_period_unit_returned.unwrap(); + assert_eq!(BudgetPeriodUnit::Month, budget_period_unit_returned.budget_period_unit); +} |
