Plan My MD Visit Part 1
Tags: ruby, rails, css
Published: 2021-01-19 04:24:21

Happy New Year 2021, fellow devs!

Moving along from my previous Intro to Rails study, [Rails Intro::Mandalorian](https://dev.to/codinghall/rails-intro-mandalorian-1ppp), 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](https://dev.to/codinghall/sinatra-x-street-fighter-1hgl), 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. 

### Table Of Contents

[1. User Story](#chapter-1)
[2. API Searching Saga](#chapter-2)
[3. Creating Active Record Model](#chapter-3)
[4. Action Controllers](#chapter-4)
[5. Authentication and Authorization Terms](#chapter-5)
[6. Action Views](#chapter-6)
[7. Lastly, as always - Lessons Learned](#chapter-7)

### 1. User Story <a name="chapter-1"></a>

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. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/a6s406uulnu3cm529yqw.png)

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 <a name="chapter-2"></a>

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](https://developer.betterdoctor.com/), 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](https://data.cms.gov/provider-data/dataset/mj5m-pzi6). Their datasets are officially used on [Medicare.gov](https://www.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](https://dev.to/codinghall/api-cli-space-exploration-108f). Thanks to Ruby gems [OpenURI](https://github.com/ruby/open-uri) and [Nokogiri](https://github.com/sparklemotion/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.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/yqz7ahsm9vt214so6ttj.gif)

### 3. Creating Active Record Model <a name="chapter-3"></a>

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.

```terminal
$ 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:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/myyi8r0f32b1fsg9iche.jpg)

user `has_one` :patient
user `has_one` :doctor

patient `belongs_to` :user 
patient `has_many` :healthcare_teams
patient `has_many` :doctors, `through:` :healthcare_teams

healthcare_team `belongs_to` :patient
healthcare_team `belongs_to` :doctor

doctor `belongs_to` :user
doctor `has_many` :healthcare_teams 
doctor `has_many` :patients, through: healthcare_teams

[Rails Generators](https://guides.rubyonrails.org/generators.html) streamline the effort of setting up models and their table migrations.

```terminal
$ 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.

```ruby 
ActiveRecord::Schema.define(version: 2021_01_04_042023) do
	create_table "users", force: :cascade do |t|
    t.string "firstname"
    t.string "lastname"
    t.string "username"
    t.string "email"
    t.string "password_digest"
    t.boolean "admin", default: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "patients", force: :cascade do |t|
    t.integer "user_id"
    t.text "medical_record"
    t.text "test_results"
    t.text "medications"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

	create_table "doctors", force: :cascade do |t|
    t.integer "user_id"
    t.string "gender"
    t.string "specialty"
    t.string "hospital"
    t.string "address"
    t.string "city"
    t.string "state"
    t.integer "zipcode"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "healthcare_teams", force: :cascade do |t|
    t.integer "patient_id"
    t.integer "doctor_id"
    t.datetime "appointment"
    t.text "test_result"
    t.text "treatment_plans"
    t.text "prescriptions"
    t.decimal "billing"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
end
```

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.

```ruby 
class User < ActiveRecord::Base
    
    validates :username, presence: true, uniqueness: true 
    validates :email, presence: true, uniqueness: true
    validates :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](https://en.wikipedia.org/wiki/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`.

```ruby 
class Doctor < ActiveRecord::Base
    
    ...

    def fullname 
        self.user ? self.user.fullname : nil 
    end
end 
```

Active Record provides more! I found it extremely useful when implementing [AREL (A Relational Algebra)](https://guides.rubyonrails.org/active_record_querying.html) 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 <a name="chapter-4"></a>

I took the liberty of developing overall user experience (as patients) with [Figma](https://www.figma.com/). My previous [Designlab's UX & UI courses](https://trydesignlab.com/) 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.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4o3lgie540m4c7mspcw6.png)


**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.

```ruby
class PatientsController < ApplicationController
 
    helper_method :current_user, :current_patient

    def show  
        @patient = Patient.find_by(id: params[:id])
        
        if @patient != current_patient
            flash[:alert] = "Error URL path."
            redirect_to patient_path(current_patient)
        end 
    end 

    ...
        
end
```

**Users as Admins**

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.

```ruby
Rails.application.routes.draw do
  
  ...

  # Only Admin can see Users Lists
  resources :users, except: [:index]  
  
  resources :doctors, only: [:index, :show] 

  # Admin privileges
  namespace :admin do 
    resources :users, only: [:index, :show, :edit, :update, :destroy]
    resources :patients, only: [:edit, :update]
    resources :doctors, only: [:edit, :update]
    resources :healthcare_teams, only: [:edit, :update, :destroy]
  end 
end
```

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.

```ruby
class Admin::UsersController < ApplicationController
    
    ...

    def destroy
        @patient = @user.patient 
        @doctor = @user.doctor 
        
        if !@patient.nil?
            @user.destroy
            @patient.destroy
        else
            @user.destroy
            @doctor.destroy
        end 

        flash[:notice] = "User deleted."
        redirect_to admin_users_path
    end 

end
```

### 5. Authentication and Authorization Terms <a name="chapter-5"></a>

For standard user authentication (including signup, login and logout), I utilized Ruby gem `bcrypt` and Active Record's [Secure Password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html) 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](https://github.com/omniauth/omniauth) relies on gem `omniauth-github`, [Google](https://developers.google.com/identity/protocols/oauth2) 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`.

```ruby
Rails.application.config.middleware.use OmniAuth::Builder do
    provider :developer unless Rails.env.production?
    provider :github, ENV['GITHUB_KEY'], ENV['GITHUB_SECRET']
    provider :google_oauth2, ENV["GOOGLE_KEY"],ENV["GOOGLE_SECRET"], skip_jwt: true
end
```

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`.

```ruby
class SessionsController < ApplicationController
    
    ...

    def omniauth
        if auth_hash != nil && auth_hash.provider == "google_oauth2"
            @user = User.find_or_create_by_google(auth)
        elsif auth_hash != nil && auth_hash.provider == "github"
            @user = User.find_or_create_by_github(auth)
        end 

        session[:user_id] = @user.id
        @current_patient = Patient.find_or_create_by(user_id: session[:user_id])
    
    ...

    end 

    private 

        def auth
            request.env['omniauth.auth']
        end 

end
```

### 6. Action Views <a name="chapter-6"></a>

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_for` [FormBuilder](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html) 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](https://api.rubyonrails.org/v6.1.0/classes/ActionView/Helpers/FormHelper.html#method-i-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]`.

```ruby
<%= render partial: '/errors', locals: { record: @healthcare_team } %>

<%= form_with model: [:admin, @healthcare_team], local: true do |f| %>
   <%= f.label :appointment, "Appointment" %>
   <%= f.datetime_field :appointment %>
 
   <%= f.label :test_result, "Test Result" %>
   <%= f.text_area :test_result %>
 
   <%= f.label :treatment_plans, "Treatment Plans" %>
   <%= f.text_area :treatment_plans %>
  
   <%= f.label :prescriptions, "Prescriptions" %>
   <%= f.text_area :prescriptions %>
   
   <%= f.label :billing, "Billing" %>
   <%= f.number_field :billing, min: 0, step: 0.01 %>

   <%= f.submit "Update Care Team" %>

<% end %>
```

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.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9y29o8dpocm976cv7hpq.gif)

**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](https://materializecss.com/), 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.

{% youtube 0cRPsyLgx88 %}

### 7. Lastly, as always — Lessons Learned <a name="chapter-7"></a>

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.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2p6jp65lypz7m05ntsgn.png)

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.

{% github fentyhall/PlanMyMDVisit %}

***

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. 🙂

Keep Calm, and Code On.

External Sources:
[Centers for Medicare and Medicaid Services](https://data.cms.gov/provider-data/dataset/mj5m-pzi6)
[Simple Calendar](https://github.com/excid3/simple_calendar)
[MaterializeCSS](https://materializecss.com/)
[Unsplash](https://unsplash.com/)
[Google Authentication Strategy for Rails 5 Application](https://medium.com/swlh/google-authentication-strategy-for-rails-5-application-cd37947d2b1b)

***
<center>[**fentybit**](https://fentybit.me/) | [GitHub](https://github.com/fentybit) | [Twitter](https://twitter.com/fentybit) | [LinkedIn](https://www.linkedin.com/in/fentybit/)</center>