Moving along from my previous Intro to Rails study, Rails Intro::Mandalorian, my cohort is approaching the Ruby on Rails deadline week. Since Rails provides more powerful web application frameworks compared to my previous Sinatra Project Build, it allows swift implementation of a more complex model associations and corresponding attributes.
The pandemic that has been lingering all around us since early 2020 has caused an apparent challenge to the current healthcare system. It is a testament to the new modern demand of digital healthcare. The conventional healthcare system is currently overflown with predominantly Covid-19 patients, exceeding their abilities to facilitate care of other medical patient needs. The Minimum Viable Product (MVP) of Plan My MD Visit app is to create a virtual patient system where the automation benefits patient 24/7 seeking virtual medical assistance, improving overall patient well-being. My application carries essential features and minimal attributes of tele-health platforms. Patients make their visits to the clinic, laboratory or hospital only when necessary and/or advised by medical professionals.
I spent most of my time at the inception of this project, thinking who the users are and what their experiences would be. At first, I came up with 5 different models that encapsulate the majority party in the healthcare platform; users as either patients, doctors, nurses, administrators, healthcare teams and healthcare providers. The brainstorming exercise goes hand in hand with my attempt in data searching — which presents its own sets of challenges.
I focused on user experience on the patient side, creating an ease of scheduling and having the capability to access medical assistance. Patients should be able to sort doctors' specialties based on their medical needs, and assemble their own healthcare teams. For example, one patient has atrocious eczema symptoms. She should be able to schedule a virtual appointment with available in-network doctors, and create a dermatology care team which comprises of herself as the patient, doctor, appointment time and other necessary care team's attributes. The patient has a certain ownership of her care team, resulting from the automated system in streamlining health provider workflow.
2. API Searching Saga
Once I envisioned the final outputs, the very next thing would be to acquire a list of doctors and their specialties. Being able to feed my application with this data allows new users (as patients) to sign up and have immediate capability to schedule appointments. API search can be fun and gnarly at the same time. I found success from other developers utilizing BetterDoctor API, and got really excited with their doctors and subsequent information. After spending a day on debugging sessions, I learned BetterDoctor API is officially deprecated. 😢
Fret not! I moved on, spent another day searching for an Open API, and landed on Centers for Medicare and Medicaid Services. Their datasets are officially used on Medicare.gov, and limited to eligible professionals (EPs) listed on Medicare Care Compare. As a result, I had to scratch out the Healthcare Provider model from my original domain modeling. The rest of my models still meet the purpose of fulfilling MVP and lay the groundwork for my Content Management System (CMS). The datasets seed about 450+ healthcare professionals with varying specialties. I had to refresh my memory on how to parse JSON data from REST API from my first capstone project. Thanks to Ruby gems OpenURI and Nokogiri, I successfully created my doctors collection in JSON format. I had to fudge a few doctors' attributes in order to execute model validations properly.
3. Creating Active Record Model
Rails pre-dominantly identifies core components in MVC (Model-View-Controller) paradigm. Most of the logic of object relationships, such as patient has_many doctors, reside in the Active Record models. Controllers allow exposure of our database from these AR model associations and rendered views. This modular approach creates distinction of each MVC responsibility, which outlines the Rails architectural framework patterns.
$rails new plan-my-md-visit
Upon instantiation of the command prompt, Rails executes its conventional file system from app, config, db to Gemfile, and many more. There are 4 main models: User, Patient, Doctor and Healthcare Team with their model associations as follows:
doctor belongs_to :user
doctor has_many :healthcare_teams
doctor has_many :patients, through: healthcare_teams
Rails Generators streamline the effort of setting up models and their table migrations.
$rails g model Doctor user:references gender:string specialty:string hospital:string address:string city:string state:string zipcode:integer --no-test-framework
Similar implementations occurred in the other models. Following building the Active Record associations, testing Rails models in rails console is a necessity for quality control. I am pleased with my basic schema composition.
Validations protect our database, and I always think that it is wise to set validations early (prior to controllers and views). Other developers might think otherwise. I identified a few rudimentary Active Record Validations, mainly to avoid seeding any bad data. Each user should be unique, and in most web applications, there should only be one particular username or email associated with the account. I have no angst towards Elon Musk's son named X Æ A-12, but my application currently only allows alphabetic letters.
classUser<ActiveRecord::Basevalidates:username,presence: true,uniqueness: truevalidates:email,presence: true,uniqueness: truevalidates:firstname,presence: true,format: {without: /[0-9]/,message: "Numbers are not allowed."}validates:lastname,presence: true,format: {without: /[0-9]/,message: "Numbers are not allowed."}...end
Aside from model associations and validations, I implemented the Law of Demeter design principle by adopting the "one dot" rule. For example, in order to retrieve doctor's fullname, the proper code would be doctor.user.fullname following our model associations and their attributes. I encapsulated the logic in the Doctor model, in reference to the object's user reference, and shortened the code to doctor.fullname.
Active Record provides more! I found it extremely useful when implementing AREL (A Relational Algebra) in building optimized queries. It helped me in sorting out the intended outcomes of doctors.json datasets with minimal time complexity. I was able to sort doctors' specialties with this query interface, Doctor.select(:specialty).distinct.order(specialty: :asc).
4. Action Controllers
I took the liberty of developing overall user experience (as patients) with Figma. My previous Designlab's UX & UI courses proved to be useful when creating this basic mid-fidelity wireframing. The exercise helped me to map the different route helpers, HTTP verbs, paths and controller actions following RESTful design principles.
Users as Patients
Upon signing up or logging in, the patient users have access to view their care teams. Overall mappings of the patient users as follows:
Sessions Controller
HTTP Verb
Method
Path
Description
GET
new
/signin
user logging in
POST
create
/signin
user authentication
POST
destroy
/logout
user logging out
Users Controller
HTTP Verb
Method
Path
Description
GET
new
/users/new
render form for new user creation
POST
create
/users/:id
create a new user
GET
show
/users/:id
show a single user
GET
edit
/users/:id/edit
render the form for editing
PATCH
update
/users/:id
update a user
DELETE
destroy
/users/:id
delete a user
Patients Controller
HTTP Verb
Method
Path
Description
GET
new
/patients/new
render form for new patient creation
POST
create
/patients/:id
create a new user patient
GET
show
/patients/:id
user patient main page
Doctors Controller
HTTP Verb
Method
Path
Description
GET
index
/doctors
view doctors with specialty filter
GET
show
/doctors/:id
show a single doctor information
Healthcare Teams Controller
HTTP Verb
Method
Path
Description
GET
new
/select_specialty
user patient select doctor specialty
Healthcare Teams Controller (nested resources)
HTTP Verb
Method
Path
GET
index
/patients/:patient_id/healthcare_teams
GET
new
/patients/:patient_id/healthcare_teams/new
POST
new
/patients/:patient_id/healthcare_teams/new
POST
create
/patients/:patient_id/healthcare_teams/:id
GET
show
/patients/:patient_id/healthcare_teams/:id
There are restrictions for patient users, for instance updating doctor's profile information or viewing other patient users information. My app has to account for such intrusion. In the Application Controller, private methods current_user and current_patient were created to police this access policy. The two methods can be propagated on other controllers when granting certain privileges based on current_user's identity. The following is a current_patient examples of a user.
The admin group should have the greatest privileges when performing Create, Read, Update and Delete (CRUD) functions on User, Patient, Doctor and Healthcare Team models. Doctors might have the capability to update Care Team's attributes such as treatment plans, test results and medications. However, they are not allowed to update patient profiles as they are restricted to administrative access.
Rails.application.routes.drawdo...# Only Admin can see Users Listsresources:users,except: [:index]resources:doctors,only: [:index,:show]# Admin privilegesnamespace:admindoresources:users,only: [:index,:show,:edit,:update,:destroy]resources:patients,only: [:edit,:update]resources:doctors,only: [:edit,:update]resources:healthcare_teams,only: [:edit,:update,:destroy]endend
The application of namespace routes shortened my overall scoping routes, wrapping /users, /patients, /doctors and /healthcare_teams resources under /admin. One finicky note on destroy action. A user is either a patient or a doctor. When deleting a user, their subsequent patient or doctor information should also be deleted from our database.
For standard user authentication (including signup, login and logout), I utilized Ruby gem bcrypt and Active Record's Secure Password class methods. has_secure_password needs to be included in the User model. This implementation provides methods to set and authenticate user's password, following users' table and password_digest attribute.
I challenged myself to set two authentication providers alongside the traditional username and password set up. Ruby gem omniauth provides a gateway to utilizing multi-provider authentication strategies. While GitHub relies on gem omniauth-github, Google depends on gem omniauth-google-oauth2. Gem dotenv-rails is required to securely save PROVIDER_CLIENT_ID and PROVIDER_SECRET. I defined the Rack Middleware in Rails initializer at config/initializers/omniauth.rb.
In the config/routes.rb file, the callback routes get '/auth/:provider/callback', to: 'sessions#omniauth' consolidates both providers (GitHub and Google). The results of authentication hash available in the callback vary from each provider. Two class methods, find_or_create_by_google(auth) and find_or_create_by_github(auth), are provided in the User model and invoked in the Sessions Controller.
The part that requires the least amount of code, and User Interface as its focal point.
Forms forms forms!
Through Flatiron School curriculums, I became well-versed with form_forFormBuilder features for models associated with Active Record models, and form_tag for manual route passing where form params will be submitted. For this project, I decided to put form_with into service. It was introduced in Rails 5.1, and the idea is to merge both implementations of form_tag and form_for. I might as well get comfortable with form_with view helpers and its lexical environment. At first, I was not able to display error messages, and discovered I had to add local: true syntax. Below is a snippet of my /admin/healthcare_teams/:id/edit route. Note on the syntax format on the nested resource, [:admin, @healthcare_team].
While Rails' doctrine is pursuant to convention over configuration, I had to customize one route get '/select_specialty', to: 'healthcare_teams#select_specialty'. I want the patient users to have the ability to sort doctors by specialty when scheduling appointments.
CSS - Oh My!
While it was all fun in rendering many views in order to make my app more interactive, I found myself spending a tremendous amount of time on CSS clean-ups. I am thankful for MaterializeCSS, and its built-in scss and jquery. Though, it gave me a lot of headache with debugging and reading Stack Overflow especially when implementing their CSS classes onto forms, select and button elements. I have no CSS experience, and maybe it's about time to educate myself further via Udemy courses.
7. Lastly, as always — Lessons Learned
My struggle on this project started off when designing the overall user flow, either as patients or doctors. It was an ambitious idea at first, trying to figure out both users simultaneously. My interim cohort leader, Eriberto Guzman, suggested to focus on developing my project from the patient users point of view, and refine for other user groups as my project progresses.
My preliminary Entity Relationship Diagram (ERD) had a flaw in the User model associations. User has_many patients, and user has_many doctors. I realized this during debugging sessions when setting up Admin controllers that I had to refactor the user either as a patient or a doctor. I was relieved to find the only difference on the associations methods was minimal. For example, self.patients.build is one of the methods for has_many association, and it was revised to self.build_patient when has_one was declared.
Refactoring codes will always be a perpetual exercise as a developer. I plan on going back, and restructuring any vulnerabilities residing in my code - but first, need to deploy on Heroku and submit this project for my upcoming project assessment. So long Rails.
The MVP of Plan My MD Visit app is to create a virtual patient system where the automation benefits patient 24/7 seeking virtual medical assistance, improving overall patient well-being.
Plan My MD Visit
Domain Modeling :: Virtual Healthcare
Welcome to my simplistic version of Virtual Healthcare system.
The automation benefits patients 24/7 seeking medical assistance, improving overall patient well-being!
The pandemic that has been lingering all around us since early 2020 has caused an apparent challenge to the current healthcare system. It is a testament to the new modern demand of digital healthcare. The conventional healthcare system is currently overflown with predominantly Covid-19 patients, exceeding their abilities to facilitate care of other medical patient needs.
The Minimum Viable Product (MVP) of Plan My MD Visit app is to create a virtual patient system where the automation benefits patient 24/7 seeking virtual medical assistance, improving overall patient well-being. My application carries essential features and minimal attributes of tele-health platforms. Patients make their visits to the clinic, laboratory or hospital only when necessary and/or advised by…
Post Scriptum:
This is my Module 3 capstone project with Flatiron School. I believe one of the catalyst to becoming a good programmer is to welcome constructive criticism. Feel free to drop a message. 🙂