HealthCare Portal Sinatra App
Model-Side Issues in Validation & Future Functionality of the HealthCare Portal App
While making my HealthCare Portal App, built with Sinatra (short video on how it works can be found here), I found an interesting issue when I tried to render an .erb page after a failed validation.
The Problem:
- My patient’s edit page
patients/edit.erb
is built to show the patient’s current information when you open the edit form. (The<input>
field is set to show the current values saved in the database for the patient’s attributes before they’ve been edited and updated). - In my app, the “name” and “birthdate” attributes of each patient must be valid per the ActiveRecord validation macros I used :
class Patient < ActiveRecord::Base
validates :name, presence: true
validates :birthdate, presence: true
...
end
- Using the “patch” HTTP method in my
PatientsController
(which requires you to mountRack::MethodOverride
in your config.ru), you might expect that if errors are raised when you try to update with invalid fields (i.e. when they’re blank), you could simply print out the validation errors and render thepatients/edit.erb
file with the errors:
class PatientsController < ApplicationController
...
patch "/patients/:id" do
@patient = Patient.find_by(id: params[:id])
if @patient.update(params[:patient]) # << VALIDATION HERE
redirect "/patients/#{patient.id}"
else
@errors = @patient.errors.full_messages
erb :"patients/edit" # EDIT PAGE RERENDER
end
end
...
end
- Then you’d expect the errors to be listed when you render the .erb file. What you might not expect, is that the .erb file would render patient attributes you just changed, which now has bad data, rather than the data drawn from the database.
- In other words, the patient’s name should be what we have in the database, not the bad updated, invalid data, right? (The patient hasn’t been updated to the database, since it failed to save because it was invalid!)
<h1><%= @patient.name %></h1>
<% if @errors %>
<%= @errors.each do |message| %>
<h5><%= message %></h5>
<% end %>
<% end %>
<form method="POST" action="/patients/<%= @patient.id %>">
<input type="hidden" id="hidden" name="_method" value="PATCH">
<h5>Name: </h5>
<input type="text" name="patient[name]" value="<%= @patient.name%>">
...
- But the data will be bad in your
patients/edit.erb
file. Your edit page is rendering “@patient” with the changed “name” and “birthdate” attributes even though they’re not allowed to save to the database. So, why is the edit page showing the data that was invalid and couldn’t be updated? - The problem is model-side, since the instance variable (“@patient”) has been modified and the instance variable is being used in the .erb file (not the data from the database).
The fix:
- There are two quick fixes for this:
- After the validation check, reassign the value of “@patient” from the database right before rendering the erb page:
patch "/patients/:id" do
@patient = Patient.find_by(id: params[:id])
if @patient.update(params[:patient]) # VALIDATION HERE
redirect "/patients/#{patient.id}"
else
@patient = Patient.find_by(id: params[:id]) # REASSIGNED
@errors = @patient.errors.full_messages
erb :"patients/edit" # RENDER @patient FROM DATABASE
end
end
2. Use your own validation and error message and only update if the fields are valid:
patch "/patients/:id" do
@patient = Patient.find_by(id: params[:id])
if params[:patient][:name].blank? # CUSTOM VALIDATION HERE
@error = "Error: Name is a required fields"
erb :"patients/edit"
else
@patient = Patient.update(params[:patient]) # UPDATE
redirect "/patients/#{patient.id}"
end
end
Bada-bing, bada-boom! Either way works. You either get the info from the database again, or wait to modify the instance variable until after you’ve checked the data.
So, there you go. If you want to show errors for bad data when updating, make sure that the info you want to display from the database doesn’t get over-written model-side!
Future Functionality of My App
I included in my program a draw.io set of tables for future expansion of the app.
- Right now, “medications” are simply a string attribute of patients , but it would make sense to make a separate medications table and Medication model, and then set up a
has_many
-has_many
relationship through a join table between patients and medications. - The join table would be called prescriptions and have further attributes like “dosage” and “frequency.” As a join table, it also would have foreign keys for both of the tables it joins, namely the medications and patients tables.
class CreatePrescriptions < ActiveRecord::Migration
def change
create_table :prescriptions do |t|
t.string :dosage
t.string :frequency
t.integer :patient_id
t.integer :medication_id
end
end
end
On the model side, the has_many
-has_many
relationship needs to be set up like so:
class Patient < ActiveRecord::Base
has_many :prescriptions
has_many :medications, through: :prescriptions
...
end
and:
class Medication < ActiveRecord::Base
has_many :prescriptions
has_many :patients, through: :prescriptions
...
end
and also:
class Prescription < ActiveRecord::Base
belongs_to :patient
belongs_to :medication
end
With this in place, a healthcare worker will be able to assign medications to patients through prescriptions, and those medications will be administered at dosages and frequencies tailored to each patient.