Last active
June 14, 2020 02:01
-
-
Save itarato/6968eb972b36ed0a8ec3fdd7076b6901 to your computer and use it in GitHub Desktop.
Feature migration framework brainstorm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::cell::{RefCell}; | |
/**************************************** | |
* Abstractions | |
*/ | |
trait Task { | |
fn get_name(&self) -> String; | |
fn get_description(&self) -> String; | |
fn is_complete(&self) -> bool; | |
fn can_autocomplete(&self) -> bool; | |
fn autocomplete(&mut self) -> Result<(), String>; | |
fn progress(&self) -> f64; | |
} | |
trait Migration { | |
fn get_name(&self) -> String; | |
fn get_description(&self) -> String; | |
fn progress(&self) -> f64 { self.tasks().iter().map(|t| t.borrow().progress()).sum::<f64>() / (self.tasks().len() as f64) } | |
fn tasks(&self) -> &Vec<RefCell<Box<dyn Task>>>; | |
fn is_complete(&self) -> bool { | |
self.tasks().iter().all(|task| task.borrow().is_complete()) | |
} | |
} | |
/**************************************** | |
* Concrete impl | |
*/ | |
struct GenericMigration { | |
tasks: Vec<RefCell<Box<dyn Task>>>, | |
name: String, | |
desc: String, | |
} | |
impl GenericMigration { | |
fn new(name: String, desc: String) -> Self { | |
Self { | |
name, desc, tasks: Default::default() | |
} | |
} | |
fn add_task(&mut self, task: Box<dyn Task>) { | |
self.tasks.push(RefCell::new(task)); | |
} | |
} | |
impl Migration for GenericMigration { | |
fn get_name(&self) -> String { | |
self.name.clone() | |
} | |
fn get_description(&self) -> String { | |
self.desc.clone() | |
} | |
fn tasks(&self) -> &Vec<RefCell<Box<dyn Task>>> { | |
&self.tasks | |
} | |
} | |
struct AcknowledgableTask { | |
name: String, | |
desc: String, | |
completed: bool, | |
} | |
impl AcknowledgableTask { | |
fn new(name: String, desc: String) -> Self { | |
Self { | |
name, desc, completed: false | |
} | |
} | |
} | |
impl Task for AcknowledgableTask { | |
fn get_name(&self) -> String { | |
self.name.clone() | |
} | |
fn get_description(&self) -> String { | |
self.desc.clone() | |
} | |
fn is_complete(&self) -> bool { | |
self.completed | |
} | |
fn can_autocomplete(&self) -> bool { | |
true | |
} | |
fn autocomplete(&mut self) -> Result<(), String> { | |
self.completed = true; | |
Ok(()) | |
} | |
fn progress(&self) -> f64 { | |
if self.completed { | |
1.0 | |
} else { | |
0.0 | |
} | |
} | |
} | |
trait Job { | |
fn run(&mut self); | |
fn progress(&self) -> f64; | |
fn is_complete(&self) -> bool; | |
} | |
struct DummyJob; | |
impl Job for DummyJob { | |
fn run(&mut self) {} | |
fn progress(&self) -> f64 { 1.0 } | |
fn is_complete(&self) -> bool { true } | |
} | |
struct JobTask { | |
name: String, | |
desc: String, | |
job: RefCell<Box<dyn Job>>, | |
} | |
impl JobTask { | |
fn new(name: String, desc: String, job: Box<dyn Job>) -> Self { | |
Self { | |
name, desc, job: RefCell::new(job) | |
} | |
} | |
} | |
impl Task for JobTask { | |
fn get_name(&self) -> String { | |
self.name.clone() | |
} | |
fn get_description(&self) -> String { | |
self.desc.clone() | |
} | |
fn is_complete(&self) -> bool { | |
self.job.borrow().is_complete() | |
} | |
fn can_autocomplete(&self) -> bool { | |
true | |
} | |
fn autocomplete(&mut self) -> Result<(), String> { | |
self.job.borrow_mut().run(); | |
Ok(()) | |
} | |
fn progress(&self) -> f64 { | |
self.job.borrow().progress() | |
} | |
} | |
struct ChoiceTask { | |
name: String, | |
desc: String, | |
choices: Vec<RefCell<Box<dyn Task>>>, | |
pick: Option<usize>, | |
} | |
impl ChoiceTask { | |
fn new(name: String, desc: String, choices: Vec<RefCell<Box<dyn Task>>>) -> Self { | |
Self { name, desc, choices, pick: None } | |
} | |
fn pick(&mut self, nth: usize) -> Option<&mut RefCell<Box<dyn Task>>> { | |
if self.choices.len() < nth { | |
return None; | |
} | |
self.pick = Some(nth); | |
self.choices.iter_mut().nth(nth) | |
} | |
} | |
impl Task for ChoiceTask { | |
fn get_name(&self) -> String { | |
self.name.clone() | |
} | |
fn get_description(&self) -> String { | |
self.desc.clone() | |
} | |
fn is_complete(&self) -> bool { | |
self.choices.iter().any(|choice| choice.borrow().is_complete()) | |
} | |
fn can_autocomplete(&self) -> bool { | |
false | |
} | |
fn autocomplete(&mut self) -> Result<(), String> { | |
Err("Cannot autocomplete a choice".into()) | |
} | |
fn progress(&self) -> f64 { | |
if self.pick.is_none() { | |
0.0 | |
} else { | |
self.choices[self.pick.unwrap()].borrow().progress() | |
} | |
} | |
} | |
/**************************************** | |
* Main | |
*/ | |
fn main() { | |
let mut gm = GenericMigration::new("Example basic migration".into(), "Simple things to enable".into()); | |
let at = AcknowledgableTask::new("Approve task".into(), "Approve these changes: ...".into()); | |
gm.add_task(Box::new(at)); | |
let jt = JobTask::new("Migration task".into(), "Running a database migration".into(), Box::new(DummyJob{})); | |
gm.add_task(Box::new(jt)); | |
for task in gm.tasks() { | |
if task.borrow().can_autocomplete() { | |
let _ = task.borrow_mut().autocomplete(); | |
} | |
} | |
let ct1 = AcknowledgableTask::new("OptionA".into(), "Simple thing".into()); | |
let ct2 = AcknowledgableTask::new("OptionA".into(), "Simple thing".into()); | |
let mut ct = ChoiceTask::new("Choice task".into(), "Pick one of the 2 options".into(), vec![ | |
RefCell::new(Box::new(ct1)), | |
RefCell::new(Box::new(ct2)), | |
]); | |
let picked = ct.pick(1).unwrap(); | |
if picked.borrow().can_autocomplete() { | |
let _ = picked.borrow_mut().autocomplete(); | |
} | |
} | |
/**************************************** | |
* TEST: Migration | |
*/ | |
#[cfg(test)] | |
mod migration_tests { | |
use super::*; | |
#[test] | |
fn test_get_name_and_description() { | |
let m = GenericMigration::new("MName".into(), "MDesc".into()); | |
assert_eq!("MName".to_owned(), m.get_name()); | |
assert_eq!("MDesc".to_owned(), m.get_description()); | |
} | |
#[test] | |
fn test_is_complete() { | |
let m = GenericMigration::new("MName".into(), "MDesc".into()); | |
assert!(m.is_complete()); | |
} | |
#[test] | |
fn test_is_complete_with_task() { | |
let mut m = GenericMigration::new("MName".into(), "MDesc".into()); | |
let at = AcknowledgableTask::new("Approve task".into(), "Approve these changes: ...".into()); | |
m.add_task(Box::new(at)); | |
assert!(!m.is_complete()); | |
let _ = m.tasks()[0].borrow_mut().autocomplete(); | |
assert!(m.is_complete()); | |
} | |
#[test] | |
fn test_progress() { | |
let mut m = GenericMigration::new("MName".into(), "MDesc".into()); | |
let at1 = AcknowledgableTask::new("Approve task".into(), "Approve these changes: ...".into()); | |
let at2 = AcknowledgableTask::new("Approve task".into(), "Approve these changes: ...".into()); | |
m.add_task(Box::new(at1)); | |
m.add_task(Box::new(at2)); | |
assert_eq!(0.0f64, m.progress()); | |
let _ = m.tasks()[0].borrow_mut().autocomplete(); | |
assert_eq!(0.5f64, m.progress()); | |
let _ = m.tasks()[1].borrow_mut().autocomplete(); | |
assert_eq!(1.0f64, m.progress()); | |
} | |
#[test] | |
fn test_tasks() { | |
let mut m = GenericMigration::new("MName".into(), "MDesc".into()); | |
let at1 = AcknowledgableTask::new("Name1".into(), "Desc1".into()); | |
let at2 = AcknowledgableTask::new("Name2".into(), "Desc2".into()); | |
m.add_task(Box::new(at1)); | |
m.add_task(Box::new(at2)); | |
assert_eq!(2, m.tasks().len()); | |
assert_eq!("Name1".to_owned(), m.tasks()[0].borrow().get_name()); | |
assert_eq!("Name2".to_owned(), m.tasks()[1].borrow().get_name()); | |
} | |
} | |
/**************************************** | |
* TEST: Task | |
*/ | |
#[cfg(test)] | |
mod task_tests { | |
use super::*; | |
#[test] | |
fn test_autocomplete() { | |
let mut t = AcknowledgableTask::new("Name1".into(), "Desc1".into()); | |
assert!(!t.is_complete()); | |
assert!(t.autocomplete().is_ok()); | |
assert!(t.is_complete()); | |
} | |
#[test] | |
fn test_can_autocomplete() { | |
let t = AcknowledgableTask::new("Name1".into(), "Desc1".into()); | |
assert!(t.can_autocomplete()); | |
let t = ChoiceTask::new("Name".into(), "Desc".into(), vec![]); | |
assert!(!t.can_autocomplete()); | |
} | |
#[test] | |
fn test_get_desc() { | |
let t = AcknowledgableTask::new("Name1".into(), "Desc1".into()); | |
assert_eq!("Desc1".to_owned(), t.get_description()); | |
} | |
#[test] | |
fn test_get_name() { | |
let t = AcknowledgableTask::new("Name1".into(), "Desc1".into()); | |
assert_eq!("Name1".to_owned(), t.get_name()); | |
} | |
#[test] | |
fn test_is_complete() { | |
let mut t = AcknowledgableTask::new("Name1".into(), "Desc1".into()); | |
assert!(!t.is_complete()); | |
assert!(t.autocomplete().is_ok()); | |
assert!(t.is_complete()); | |
} | |
#[test] | |
fn test_progress() { | |
let mut t = AcknowledgableTask::new("Name1".into(), "Desc1".into()); | |
assert_eq!(0.0f64, t.progress()); | |
assert!(t.autocomplete().is_ok()); | |
assert_eq!(1.0f64, t.progress()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment