Ruby on Rails is a web
application framework that uses Ruby to help build web applications.
It was created by David Heinemeier Hansson in 2004 when he extracted
the core from an existing web application (called Basecamp) and
generalized it so that it could be used to build other applications.
It is still actively maintained and is currently at version 3.0.5,
which was release near the end of February, 2011. (Version 3.0.6
was released on April 6th. This version is now available in
~donald/pub/rails. The documentation below, however,
still refers to version 3.0.5 in some places.)
Some of the more notable aspects of this framework include:
A typical interaction in the context of a Rails web application is outlined below and illustrated in the figure on the right.
We don't have time to discuss or perform an installation. Refer to the Ruby on Rails Website or Section 1.2 of the Ruby on Rails Tutorial. A private copy of version 3.0.5 of Rails is already installed on the Computer Science machines at MUN and should be accessible with the command (notice the preceding dot):
$ . /users/cs/grad/donald/pub/rails/setpath
This places the directory /users/cs/grad/donald/pub/rails/bin
at the front of your PATH environment variable so that all
the commands (especially the rails command) can be executed
without having to type their full path name. To test whether everything
is setup right, try the following commands:
$ which rails
/users/cs/grad/donald/pub/rails/bin/rails
$ rails -v
Rails 3.0.5
$ rails
Usage:
rails new APP_PATH [options]
Options:
-r, [--ruby=PATH] # Path to the Ruby binary of your choice
# Default: /users/cs/grad/donald/pub/rails/bin/ruby
-d, [--database=DATABASE] # Preconfigure for selected database
# (options: mysql/oracle/postgresql/sqlite3/frontbase/ibm_db)
# Default: sqlite3
-b, [--builder=BUILDER] # Path to an application builder (can be a filesystem path or URL)
-m, [--template=TEMPLATE] # Path to an application template (can be a filesystem path or URL)
[--dev] # Setup the application with Gemfile pointing to your Rails checkout
[--edge] # Setup the application with Gemfile pointing to Rails repository
[--skip-gemfile] # Don't create a Gemfile
-O, [--skip-active-record] # Skip Active Record files
-T, [--skip-test-unit] # Skip Test::Unit files
-J, [--skip-prototype] # Skip Prototype files
-G, [--skip-git] # Skip Git ignores and keeps
Runtime options:
-f, [--force] # Overwrite files that already exist
-p, [--pretend] # Run but do not make any changes
-q, [--quiet] # Supress status output
-s, [--skip] # Skip files that already exist
Rails options:
-v, [--version] # Show Rails version number and quit
-h, [--help] # Show this help message and quit
Description:
The 'rails new' command creates a new Rails application with a default
directory structure and configuration at the path you specify.
Example:
rails new ~/Code/Ruby/weblog
This generates a skeletal Rails installation in ~/Code/Ruby/weblog.
See the README in the newly created application to get going.
NOTE: Conventional IDEs are typically shunned by the Rails developer community in favour of a terminal command line and a good text editor. There are several guides online to help you understand how to use the command line
For this introduction, we'll generate a very, very simple Electronic
Medical Record (EMR) application. We can use the new
subcommand of the rails command to generate the
foundation for the application.
$ rails new emr
create
create README
create Rakefile
create config.ru
create .gitignore
create Gemfile
create app
create app/controllers/application_controller.rb
create app/helpers/application_helper.rb
create app/mailers
create app/models
create app/views/layouts/application.html.erb
create config
create config/routes.rb
create config/application.rb
create config/environment.rb
create config/environments
create config/environments/development.rb
create config/environments/production.rb
create config/environments/test.rb
create config/initializers
create config/initializers/backtrace_silencers.rb
create config/initializers/inflections.rb
create config/initializers/mime_types.rb
create config/initializers/secret_token.rb
create config/initializers/session_store.rb
create config/locales
create config/locales/en.yml
create config/boot.rb
create config/database.yml
create db
create db/seeds.rb
create doc
create doc/README_FOR_APP
create lib
create lib/tasks
create lib/tasks/.gitkeep
create log
create log/server.log
create log/production.log
create log/development.log
create log/test.log
create public
create public/404.html
create public/422.html
create public/500.html
create public/favicon.ico
create public/index.html
create public/robots.txt
create public/images
create public/images/rails.png
create public/stylesheets
create public/stylesheets/.gitkeep
create public/javascripts
create public/javascripts/application.js
create public/javascripts/controls.js
create public/javascripts/dragdrop.js
create public/javascripts/effects.js
create public/javascripts/prototype.js
create public/javascripts/rails.js
create script
create script/rails
create test
create test/fixtures
create test/functional
create test/integration
create test/performance/browsing_test.rb
create test/test_helper.rb
create test/unit
create tmp
create tmp/sessions
create tmp/sockets
create tmp/cache
create tmp/pids
create vendor/plugins
create vendor/plugins/.gitkeep
This command creates an emr directory and creates
several more files and subdirectories. We can change to the
emr directory and see them:
$ cd emr $ ls -F Gemfile Rakefile config/ db/ lib/ public/ test/ vendor/ README app/ config.ru doc/ log/ script/ tmp/
The README file contains many helpful details
regarding the files and subdirectories generated as well as some
hints on what to do next and how to debug your application.
The most important subdirectories we'll be dealing with is the
app subdirectory which contains subsubdirectories
that contain the files for the models, views and controllers
and the db subdirectory which contains the database
as well as files related to the creation and modification of the
database schema.
Although we haven't actually created any real application code yet,
we can still start a webserver so that we can view a welcome page.
This can be done with the server
subcommand of the rails command:
$ rails server [2011-04-01 17:35:56] INFO WEBrick 1.3.1 [2011-04-01 17:35:56] INFO ruby 1.9.2 (2011-02-18) [i686-linux] [2011-04-01 17:35:56] INFO WEBrick::HTTPServer#start: pid=7428 port=3000
Many log messages will be displayed in this window as you access your application from a web browser. Viewing these log messages can be helpful when diagnosing problems with the web application.
If you get a message about the port already in use, a server
is already running on that machine using that port (probably
a server you started earlier). Either kill that server or
use a different port, for example: rails server -p 3001.
When you are finished, remember to kill the server by pressing
Ctrl-C in this terminal window. Or you can find
the process ID using the ps and kill it off manually.
When the server has been started we can access it through
our web browser by visiting the URL: http://localhost:3000.
We'll open a new command line window (leaving the window
that we used to start the server) and set up our path using the
. /users/cs/grad/donald/pub/rails/setpath command again.
Central to any EMR is the patients under a physicians care. We'll use
the generate subcommand of the rails command
to generate the scaffolding of a model-view-controller that will be
used to interact with the Patients in the database. After changing
back into the emr directory, we issue the command:
$ rails generate scaffold Patient name:string address:text dob:date mcp_number:string
invoke active_record
create db/migrate/20110329194309_create_patients.rb
create app/models/patient.rb
invoke test_unit
create test/unit/patient_test.rb
create test/fixtures/patients.yml
route resources :patients
invoke scaffold_controller
create app/controllers/patients_controller.rb
invoke erb
create app/views/patients
create app/views/patients/index.html.erb
create app/views/patients/edit.html.erb
create app/views/patients/show.html.erb
create app/views/patients/new.html.erb
create app/views/patients/_form.html.erb
invoke test_unit
create test/functional/patients_controller_test.rb
invoke helper
create app/helpers/patients_helper.rb
invoke test_unit
create test/unit/helpers/patients_helper_test.rb
invoke stylesheets
create public/stylesheets/scaffold.css
The contents of the controller and the corresponding views are shown below, in abbreviated form. Note the following observations:
.html.erb view template files.
.html.erb files are similar to HTML, but
typically contain small amounts of embedded ruby in them
(hence the .erb suffix). The ruby is written
between <% ... %> delimiters
or <%= ... %> delimiters (the
latter pair of delimiters will substitute the result of the ruby
expression directly into the HTML template.)
index action of the controller, the
@patients array contains a list of all the patients
in the database. In the index.html.erb file, we
iterate over this array to display the details associated with
each patient in an HTML table.
# app/controllers/patients_controller.rb
class PatientsController < ApplicationController
# Get a list of all patients
def index
@patients = Patient.all
...
end
# Show details of one patient
def show
@patient = Patient.find(params[:id])
...
end
# Generate a form to create a new patient
def new
@patient = Patient.new(params[:patient])
...
end
# Generate a form to update a patient
def edit
@patient = Patient.find(params[:id])
...
end
# Create a new patient in the database
def create
@patient = Patient.new(params[:patient])
...
if @patient.save
...
end
# Update an existing patient in the database.
def update
@patient = Patient.find(params[:id])
...
end
# Remove a patient from the database.
def destroy
@patient = Patient.find(params[:id])
@patient.destroy
...
end
end
<!-- app/views/patient/index.html.erb -->
<h1>Listing patients</h1>
<table>
<tr>
<th>Name</th>
...
</tr>
<% @patients.each do |patient| %>
<tr>
<td><%= patient.name %></td>
...
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Patient', new_patient_path %>
<!-- app/views/patient/show.html.erb -->
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @patient.name %>
...
</p>
<%= link_to 'Edit', edit_patient_path(@patient) %> |
<%= link_to 'Back', patients_path %>
<!-- app/views/patient/new.html.erb -->
<h1>New patient</h1>
<%= render 'form' %>
<%= link_to 'Back', patients_path %>
<!-- app/views/patient/edit.html.erb -->
<h1>Editing patient</h1>
<%= render 'form' %>
<%= link_to 'Show', @patient %> |
<%= link_to 'Back', patients_path %>
Both the new.html.erb and edit.html.erb
views use a common chuck of HTML representing the form to be displayed
to the physician. This common code is called a partial in
rails and is located in the file _form.html.erb in the
app/patient/views subdirectory. It is rendered with
the render command in the new.html.erb
and edit.html.erb views. (Note that we add
a :start_year => 1880 hash parameter to the
:dob attribute to allow older patients to be represented
in the database.)
<!-- app/views/patient/_form.html.erb -->
<%= form_for(@patient) do |f| %>
<% if @patient.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@patient.errors.count, "error") %> prohibited this patient from being saved:</h2>
<ul>
<% @patient.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :address %><br />
<%= f.text_area :address, :rows => 4, :cols => 20 %>
</div>
<div class="field">
<%= f.label :dob %><br />
<%= f.date_select :dob, :start_year => 1900, :end_year => 2011 %>
</div>
<div class="field">
<%= f.label :mcp_number %><br />
<%= f.text_field :mcp_number %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The code for the Patient model is pretty simple:
# app/models/patient.rb
class Patient < ActiveRecord::Base
end
Much of the functionality for this class is either inherited or created dynamically during runtime as the database is queried by the controller. We'll be adding validation and associations to this model shortly.
Another file generated is a database migration file. A migration file is simply a ruby program that describes how the database schema (or the data itself) is to be modified as the database evolves over time. The migration generated for us below creates a table named patients with the specified columns in the database.
# db/migrate/*_create_patients.rb
class CreatePatients < ActiveRecord::Migration
def self.up
create_table :patients do |t|
t.string :name
t.text :address
t.date :dob
t.string :mcp_number
t.timestamps
end
end
def self.down
drop_table :patients
end
end
To actually create the schema in the database, we need to apply the
migration by running a task to migrate the database to its new state.
In rails tasks are run using a utility program called rake,
with the name of the task provided as a command line argument. (A list
of all tasks can be generated with $ rake --tasks.) To
migrate a database, we specify the task named the db:migrate.
$ rake db:migrate (in .../emr) == CreatePatients: migrating ================================================= -- create_table(:patients) -> 0.0060s == CreatePatients: migrated (0.0061s) ========================================
By default, the sqlite3 database is used, however other databases are supported as well including MySQL, DB2 and Oracle, to name a few.
With everything now in places, we can visit the URL
http://localhost:3000/patients/ and create, edit, delete,
show or list all the patients in the database. We can change the
routing so that the URL http://localhost:3000/
will automatically visit the list of patients:
# config/routes.rb
Emr::Application.routes.draw do
...
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
# root :to => "welcome#index"
root :to => "patients#index"
...
end
For this to work, we must also remove the index.html
file from the public directory:
$ rm public/index.html
As you are adding patients, note the sequence of events:
new
action of the PatientsController is called.
@patient member variable (no database access is done).
new.html.erb form is then generated. Because
this form uses the _form.html.erb partial, this
partial will also be rendered.
create action of the PatientsController
is called.
@patient)
initialized with the data supplied by the physician in the form.
This form data is available in the params hash.
A similar sequence of events happens when you modify a patient,
except that the edit/update actions
are called instead of the new/create actions.
Also, during the call to edit the model is
consulted (@patient = Patient.find(params[:id]))
to find the patient in the database whose id matches that
selected by the physician in the list. The _form.html.erb
partial employed by the edit.html.erb view uses
this object to pre-populates form with the patient's details.
Unfortunately, our application has problems. For example, we can create patients without actually filling in any of the fields. We can create invalid/duplicate MCP numbers. Fortunately, models provides an easy way to validate data before actually storing it in the database.
# app/models/patients.rb
class Patient < ActiveRecord::Base
validates :name, :address, :dob, :mcp_number, :presence => true
validates :mcp_number, :uniqueness => true,
:format => {
:with => %r{\A\d{12}\z},
:message => 'must have exactly 12 digits'
}
end
Now, whenever we leave fields blank or try to enter invalid data, we are returned to the form which highlights the problems that were encountered. (Note that the dates are not validated, so an invalid date like April 31 will be stored as May 1. Oddly, Rails 3 does not appear to provide a way to validate dates out of the box.)
To make the application feel a little more "real-world", we can populate it with some
random data. This is known as seeding the database and can be done
by modifying the db/seeds.rb file. For example, the script
below will add 100 random patients to the database with address, birth dates and
MCP numbers.
# db/seeds.rb
Patient.delete_all
FIRST_NAMES = %w(Alice Bob Charlie Dave Eva Mallory Peggy Victor Trent Walter Arthur)
LAST_NAMES = %w(Smith Jones Brown Williams Miller Wilson Moore Davis)
STREET_NAMES = %w(Pine Elm Oak Main)
STREET_TYPES = %w(Blvd. Cresent Road Street Avenue)
STDOUT.sync = true # Prevent output from being buffered.
print "Seeding database"
100.times do
patient = Patient.create(:name => "%s %s" % [FIRST_NAMES.sample, LAST_NAMES.sample],
:address => "%d %s %s" %
[rand(1000)+1, STREET_NAMES.sample, STREET_TYPES.sample],
:dob => Date.today - rand(40000),
:mcp_number => (rand(900000000000) + 100000000000).to_s)
print "."
end
puts "done"
We can then run this script as a rake task to seed the database:
$ rake db:seed (in .../emr) Seeding database....................................................................................................done
Each patient has a collection of progress notes associated with it. The views for these progress notes will be handled by the patient views, so we'll just generate a model and a controller for the progress notes.
First we create the model. Observe that each note references a patient (in SQL context, this is the foreign key) and as content field that contains text.
$ rails generate model ProgressNote patient:references contents:text
create app/controllers/progress_notes_controller.rb
invoke erb
create app/views/progress_notes
invoke test_unit
create test/functional/progress_notes_controller_test.rb
invoke helper
create app/helpers/progress_notes_helper.rb
invoke test_unit
create test/unit/helpers/progress_notes_helper_test.rb
We re-migrate to introduce the progress note table into the database.
$ rake db:migrate (in .../emr) == CreateProgressNotes: migrating ============================================ -- create_table(:progress_notes) -> 0.0040s == CreateProgressNotes: migrated (0.0041s) ===================================
The ProgressNote model generated above is as follows.
The belongs_to :patient creates the association between
the note and the patient to whom it belongs.
# app/models/progress_note.rb
class ProgressNote < ActiveRecord::Base
belongs_to :patient
end
We must also create an association in the opposition direction, informing
the Patient model that it may have zero or more progress notes.
# app/models/patients.rb
class Patient < ActiveRecord::Base
validates :name, :address, :dob, :mcp_number, :presence => true
validates :mcp_number, :uniqueness => true,
:format => {
:with => %r{\A\d{12}\z},
:message => 'must have exactly 12 digits'
}
has_many :progress_notes
end
A progress note is known as a nested resource. We inform the router of this relationship, as follows:
# config/routes.rb
Emr::Application.routes.draw do
resources :patients
resources :patients do
resources :progress_notes
end
...
end
Now, if patient is a Patient object we can
access its associated progress notes by using the syntax
patient.progress_notes. We can create a new progress note
for a patient using the syntax
patient.progress_notes.create(...). We'll see examples
of these later on.
Next, we create the controller class for the ProgressNotes:
$ rails generate controller ProgressNotes
The generated class is very simple with no methods. We'll manaully
add a create method. This method will be called when
the physician clicks on a Create Progress Note button in
a view that shows a patient that we'll see later. The create
action queries the database for the patient given the patient id,
then creates a progress note for that patient initialized with
the contents of the progress note's text field. After the note
has been created, we redirect to the view that shows the patient
and all of his or here progress notes.
# app/controllers/progress_notes_controller.rb
class ProgressNotesController < ApplicationController
def create
@patient = Patient.find(params[:patient_id])
@patient.progress_notes.create(params[:progress_note])
redirect_to patient_path(@patient)
end
end
In order to show the progress notes in the patient view, we modify
the app/views/patients/show.html.erb template. If there
are no progress notes, we'll display some text indicating this. Otherwise,
we'll iterate over all the patients progress notes displaying the
date the note was created as well as the contents of the note.
The block of code following the conditional display of the progress
note is a form that allows the physician to create a new progress note.
Because a progress note is a nested resource, the form_for
syntax in this case takes a list of objects that illustrate this
nesting. When the user clicks the submit button created by the form,
the create method of the ProgressNoteController
will be invoked.
<!-- app/views/patients/show.html.erb -->
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @patient.name %>
</p>
<p>
<b>Address:</b>
<%= @patient.address %>
</p>
<p>
<b>Dob:</b>
<%= @patient.dob %>
</p>
<p>
<b>Mcp number:</b>
<%= @patient.mcp_number %>
</p>
<% if @patient.progress_notes.empty? %>
<strong>No Progress Notes Available</strong>
<% else %>
<h2>Notes</h2>
<% @patient.progress_notes.each do |note| %>
<p>
<b>Date:</b>
<%= note.created_at.localtime %><br/>
<b>Note:</b>
<%= note.contents %>
</p>
<% end %>
<% end %>
<h2>Add a progress note:</h2>
<%= form_for([@patient, @patient.progress_notes.build]) do |f| %>
<div class="field">
<%= f.label :contents %><br />
<%= f.text_area :contents %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<%= link_to 'Edit', edit_patient_path(@patient) %> |
<%= link_to 'Back', patients_path %>
Finally, we can modify the view that shows a listing
of all the patients (app/views/patients/index.html.erb).
The modification will add an extra column that shows how many progress
notes the physician created for each patient.
<!-- app/view/patients/index.html.erb -->
<h1>Listing patients</h1>
<table>
<tr>
<th>Name</th>
<th>Address</th>
<th>Dob</th>
<th>Mcp number</th>
<th>Notes</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @patients.each do |patient| %>
<tr>
<td><%= patient.name %></td>
<td><%= patient.address %></td>
<td><%= patient.dob %></td>
<td><%= patient.mcp_number %></td>
<td align="center"><%= patient.progress_notes.size %></td>
<td><%= link_to 'Show', patient %></td>
<td><%= link_to 'Edit', edit_patient_path(patient) %></td>
<td><%= link_to 'Destroy', patient, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Patient', new_patient_path %>
The db/seeds.rb can be modified to create random progress
notes for each of the patients. This creates progress notes by grabbing
random words from the /usr/share/dict/words file and
adding them to the progress note associated with a patient. Note again,
the use of the patient.progress_notes.create(...). syntax.
# db/seeds.rb
Patient.delete_all
FIRST_NAMES = %w(Alice Bob Charlie Dave Eva Mallory Peggy Victor Trent Walter Arthur)
LAST_NAMES = %w(Smith Jones Brown Williams Miller Wilson Moore Davis)
STREET_NAMES = %w(Pine Elm Oak Main)
STREET_TYPES = %w(Blvd. Cresent Road Street Avenue)
WORDS = IO.read('/usr/share/dict/words').split("\n")
STDOUT.sync = true # Prevent output from being buffered.
print "Seeding database"
100.times do
patient = Patient.create(:name => "%s %s" % [FIRST_NAMES.sample, LAST_NAMES.sample],
:address => "%d %s %s" %
[rand(1000)+1, STREET_NAMES.sample, STREET_TYPES.sample],
:dob => Date.today - rand(40000),
:mcp_number => (rand(900000000000) + 100000000000).to_s)
rand(10).times do
# Generate gibberish for the progress notes.
contents = (1..(rand(30) + 10)).map { WORDS.sample }.join(" ")
patient.progress_notes.create(:contents => contents)
end
print "."
end
puts "done"