Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created schema for flexible and extensible forms #11

Merged
merged 9 commits into from
Sep 11, 2024
3 changes: 3 additions & 0 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
pub mod models;

use std::sync::Arc;
use std::time::Duration;

use anyhow::Context;
Expand Down
181 changes: 181 additions & 0 deletions src/app/models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use mongodb::bson::{oid::ObjectId, DateTime};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct User {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
id: Option<ObjectId>,
first_name: String,
last_name: String,
national_health_identifer: String,
email_address: String,
hashed_password: String,
is_patient: bool,
caregivers: Vec<ObjectId>,
form_templates: Vec<Form>,
form_filled: Vec<Form>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda confused why form_templates and form_filled are distinct when instances of filled-out forms are stored as Event::FormSubmitted on a given Form instance; presumably this is just out of date?

}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Form {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
id: Option<ObjectId>,
title: String,
created_by: ObjectId,
created_at: DateTime,
questions: Vec<Question>,
events: Vec<Event>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having Event::FormSubmitted in a list on a given form rather than storing instances of filled-out forms separately (and querying them directly) feels like it could get a bit awkward to work with on the client?

The alternative would be e.g. have three collections, of Users, Forms, and FormResponses respectively, or similar, where Form is a collection of questions and FormResponse references a given Form and contains answers

Although now that I say that I realize that it'd also be kind of awkward given that we're working with Mongo rather than a traditional relational DB - although Mongo does still have $lookup and such. After staring at it for long enough I've concluded that this PR's approach might be the best approach after all 😅

}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum Event {
FormSubmitted(FormSubmitted),
QuestionEdited(QuestionEdited),
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct QuestionEdited {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will questions being added/removed (so that the total number of questions changes) work?

If a given question is edited in such a way that it has a different semantic meaning (i.e. we just replace it entirely), is there any reason to retain the same id rather than just treating it as a deletion + a creation? Is this intended specifically for cases where questions are edited while keeping the same meaning (e.g. typo corrections, or rephrasing the same general question, etc)?

question_id: ObjectId,
former_question: Question,
new_question: Question,
edited_by: ObjectId,
edited_at: DateTime,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct FormSubmitted {
answers: Vec<QuestionAndAnswer>,
submitted_by: ObjectId,
submitted_at: DateTime,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum Question {
Multichoice(MultichoiceQuestion),
Slider(SliderQuestion),
FreeForm(FreeFormQuestion),
}

pub type MultichoiceAnswer = ObjectId;
pub type SliderAnswer = f64;
pub type FreeFormAnswer = String;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it necessary to retype these basic values? They have no close conflicts.


#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum QuestionAndAnswer {
Multichoice(MultichoiceQuestion, MultichoiceAnswer),
Slider(SliderQuestion, SliderAnswer),
FreeForm(FreeFormQuestion, FreeFormAnswer),
}

// In the template, the answer will be set to the default
// it is when the form is opened
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is out of date given that the answer is no longer stored on FreeFormQuestion.

#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct FreeFormQuestion {
id: ObjectId,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be serde-renamed to _id, assuming that we need a unique id at all. Same goes for SliderQuestion, MultichoiceQuestion, and MultichoiceQuestionOption.

title: String,
max_length: u64,
min_length: u64,
}

// In the template, the answer will store the default value
// for when the form is opened
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is out of date given that the answer is no longer stored on SliderQuestion.

#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct SliderQuestion {
id: ObjectId,
title: String,
units: Option<String>,
low: f64,
high: f64,
step: f64,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct MultichoiceQuestion {
id: ObjectId,
title: String,
options: Vec<MultichoiceQuestionOption>,
min_selected: u64,
max_selected: u64,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct MultichoiceQuestionOption {
name: String,
id: ObjectId,
}

fn _example() {
let _form: Form = Form {
id: Some(ObjectId::new()),
title: String::from("Tremors"),
created_by: ObjectId::new(),
created_at: DateTime::now(),
questions: vec![
Question::Multichoice(MultichoiceQuestion {
id: ObjectId::new(),
title: String::from("How many times have you experienced this in the last week?"),
options: vec![MultichoiceQuestionOption {
name: String::from("Once"),
id: ObjectId::new(),
}],
min_selected: 1,
max_selected: 2,
}),
Question::FreeForm(FreeFormQuestion {
id: ObjectId::new(),
title: String::from("Is there anything else you would like to add?"),
max_length: 200,
min_length: 0,
}),
],
events: vec![
Event::QuestionEdited(QuestionEdited {
question_id: ObjectId::new(),
former_question: Question::FreeForm(FreeFormQuestion {
id: ObjectId::new(),
title: String::from("How are you feeling this week?"),
max_length: 100,
min_length: 10,
}),
new_question: Question::FreeForm(FreeFormQuestion {
id: ObjectId::new(),
title: String::from("Is there anything else you would like to add?"),
max_length: 200,
min_length: 0,
}),
edited_at: DateTime::now(),
edited_by: ObjectId::new(),
}),
Event::FormSubmitted(FormSubmitted {
answers: vec![
QuestionAndAnswer::Multichoice(
MultichoiceQuestion {
id: ObjectId::new(),
title: String::from(
"How many times have you experienced this in the last week?",
),
options: vec![MultichoiceQuestionOption {
name: String::from("Once"),
id: ObjectId::new(),
}],
min_selected: 1,
max_selected: 2,
},
ObjectId::new(),
),
QuestionAndAnswer::FreeForm(
FreeFormQuestion {
id: ObjectId::new(),
title: String::from("Is there anything else you would like to add?"),
max_length: 200,
min_length: 0,
},
String::from("I wasn't able to press the elevator buttons this morning"),
),
],
submitted_at: DateTime::now(),
submitted_by: ObjectId::new(),
}),
],
};
}
Loading