XForms and Ruby on Rails at the doctor's office, Part 2: Implementing the patient information XForm

This is the second article in a four-part series about using XForms, IBM® DB2® pureXML™, and Ruby together to more easily create Web applications. In this series you will develop a hypothetical application for managing patient information at a doctor's office. You will get a taste of the individual strengths of each technology, but you will also see how to integrate them together. In Part 2 of the series, you will begin to implement the application.

Michael Galpin, Software architect, eBay

Michael Galpin has been developing Web applications since 1998 and holds a degree in mathematics from the California Institute of Technology. He is an architect at eBay in San Jose, CA.



04 June 2008 (First published 27 May 2008)

Also available in Russian

Introduction

In Part 1 of this four-part series, you began to design a Web application that would let patients enter information at the doctor's office. It discussed how you can use XForms, DB2 pureXML, and Ruby on Rails to create such an application, and you did some experimentation with using these technologies together. In this article, Part 2, you will begin to implement the application. You will design your first XForm and create a Ruby on Rails back-end to insert the data from the form into DB2. You will see how you can use these three technologies to leverage XML all the way across your application.


Prerequisites

Frequently used acronyms

  • API: application programming interface
  • CSS: Cascading Stylesheets
  • UI: User Interface
  • URL: Uniform Resource Locator
  • XML: Extensible Markup Language
  • XSD: XML Schema Definition

This article assumes familiarity with XML and with Web applications in general. Prior exposure to the three core technologies, XForms, DB2 pureXML, and Ruby on Rails, is helpful of course, but definitely not necessary. The article was written using the Mozilla XForms plugin version 0.8.0.3. This provides XForms runtime support in any Mozilla browser such as Firefox. Another very useful Mozilla plugin is XForms Buddy. This provides a debugger for XForms. Version 0.5.6 was used in this article. You will also need the DB2 database server from IBM. This article uses DB2 Express-C version 9.5. This is available for Windows®, Linux®, and UNIX® systems. Finally, you will need Ruby on Rails. This article uses Ruby 1.8.6 with Rails 1.2.5. This article also uses the Mongrel Web server in conjunction with Rails. This is available through Ruby Gems (just type gem install mongrel from the command line). See Resources for download links.


Patient information

Part 1 discussed how the technologies you are using, XForms, DB2 pureXML, and Ruby on Rails, allow you to use XML on the front and back ends of your application. One nice thing about this design is that it puts the XML data front-and-center. The design of your XML data model defines how you implement the XForms-based front end as well as how you retrieve data from DB2 using Ruby on Rails on the back end. So the logical place to start developing this application is to design the XML data model.

XML data model

The application will allow patients to enter information needed by the doctor and the staff in the office. You will need information such as the patient's name, insurance company, age, co-pay amount, and of course their symptoms. With all of that in mind, Listing 1 shows what a typical data model instance might look like.

Listing 1. Typical XML instance of patient information
<?xml version="1.0" encoding="UTF-8"?>
<Info>
     <FirstName>John</FirstName>
     <MiddleName>David</MiddleName>
     <LastName>Smith</LastName>
     <Age>33</Age>
     <Insurer>HealthCo</Insurer>
     <Id>555-69-1212</Id>
     <PolicyHolder>true</PolicyHolder>
     <Copay>10</Copay>
     <Symptoms>Cough, Fever</Symptoms>
</Info>

Most of these are simple strings, with a few exceptions. Middle name should be an optional field. Age is an integer, and must be non-negative. The same is true of co-pay. Policy holder is a Boolean flag. You will define the Id to follow a pattern of three digits, two digits, and four digits. With all of that in mind, Listing 2 shows an XML schema describing the patient data model.

Listing 2. Patient schema
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="http://developerworks.ibm.com/patient"
           xmlns="http://developerworks.ibm.com/patient"
           elementFormDefault="qualified"
           xmlns:this="http://developerworks.ibm.com/patient">

  <xs:simpleType name="policyId">
    <xs:restriction base="xs:string">
      <xs:pattern value="[0-9]{3}-[0-9]{2}-[0-9]{4}"/>
    </xs:restriction>
  </xs:simpleType>
  
  <xs:element name="Info">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="FirstName" type="xs:NMTOKENS"/>
        <xs:element name="MiddleName" type="xs:NMTOKEN" minOccurs="0"/>
        <xs:element name="LastName" type="xs:NMTOKENS"/>
        <xs:element name="Age" type="xs:nonNegativeInteger"/>
        <xs:element name="Insurer" type="xs:NMTOKENS"/>
        <xs:element name="Id" type="this:policyId" />
        <xs:element name="PolicyHolder" type="xs:boolean"/>
        <xs:element name="Copay" type="xs:nonNegativeInteger"/>
        <xs:element name="Symptoms" type="xs:string"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

With your XML data model defined, you can now create an XForms that allows a user to create an instance of this data model.

Patient XForms

Now you know what data that you want patients to enter into your application. This makes defining the XForms very straightforward. You simply create form elements that correspond to the elements in your data model. The type of form element is based on the data type of the data element. With that in mind, Listing 3 shows the XForms for the patient information.

Listing 3. Patient information XForms
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" 
      xmlns:xf="http://www.w3.org/2002/xforms">
     <head>
          <meta http-equiv="Content-Type" 
		      content="text/html; charset=ISO-8859-1" />
          <title>Patient Information</title>
          <xf:model>
                  <xf:instance>
                  <Info xmlns="">
                      <FirstName/>
                      <MiddleName/>
                      <LastName/>
                    <Age/>
                    <Insurer/>
                    <Id/>
                    <PolicyHolder/>
                    <Copay/>
                    <Symptoms/>                      
                  </Info>
              </xf:instance>
              <xf:submission action="http://localhost:3000/kiosk/create" 
                                 method="post" id="submit-info"/>              
          </xf:model>
     </head>
     <body>
          <p>
               <div id="firstName">
                    <xf:input ref="FirstName">
                         <xf:label>First Name:</xf:label>
                    </xf:input>
               </div>
               <div id="middleName">
                    <xf:input ref="MiddleName">
                         <xf:label>Middle Name:</xf:label>
                    </xf:input>
               </div>
               <div id="lastName">
                    <xf:input ref="LastName">
                         <xf:label>Last Name:</xf:label>
                    </xf:input>
               </div>
               <div id="age">
                    <xf:input ref="Age">
                         <xf:label>Age:</xf:label>
                    </xf:input>          
               </div>
               <div id="insurer">
                    <xf:input ref="Insurer">
                         <xf:label>Name of Insurance Provider:</xf:label>
                    </xf:input>               
               </div>
               <div id="idNumber">
                    <xf:input ref="Id">
                      <xf:label>Insurance ID Number(###-##-####):</xf:label>
                    </xf:input>
               </div>
               <div id="holder">
                    <xf:select1 ref="PolicyHolder">
                         <xf:label>Are you the policy holder?</xf:label>
                         <xf:item>
                              <xf:label>Yes</xf:label>
                              <xf:value>true</xf:value>
                         </xf:item>
                         <xf:item>
                              <xf:label>No</xf:label>
                              <xf:value>false</xf:value>
                         </xf:item>
                    </xf:select1>     
               </div>          
               <div id="copay">
                    <xf:input ref="Copay">
                         <xf:label>Co-pay :$</xf:label>
                    </xf:input>
               </div>
               <div id="symptoms">
                    <xf:textarea ref="Symptoms">
                         <xf:label>Please describe your symptoms:</xf:label>
                    </xf:textarea>          
               </div>     
               <div id="submit">
                    <xf:submit submission="submit-info">
                         <xf:label>Submit Information</xf:label>
                    </xf:submit>
               </div>
          </p>

          <a href="kiosk/list">Back to List</a>
     </body>
</html>

Here is a quick explanation of the XForms in Listing 3. You have one model and one instance. The instance is a blank version of the patient information data model defined previously. You also have a submission that is part of the model. The submission will take your model instance and post it to the URL specified in the submission action attribute. In the body of the form, you have a series of XForms elements. Each one is bound to the model instance using a ref XPath. Whatever values the user enters in data in the form will be bound to the model instance. Load the XForms in a browser and you should see something similar to Figure 1.

Figure 1. The patient XForms
The patient XForms

Not exactly the prettiest Web page, but it is a functional XForm. A few interesting things are worth mentioning:

.

  • First, the page is an XHTML page, not a usual HTML page. If you changed the page extension to .html or .htm, then it will not render properly. That is because XForms require XHTML and thus the Mozilla plugin will not activate on an HTML page.
  • Next, since it is XHTML, it still supports CSS. This is the easiest way to change the look of the page. You will use some CSS shortly as part of data validation.
  • Finally, notice that the URL was http://localhost:3000/patient.xhtml. In the example above, the page was served from a Ruby on Rails application using the Mongrel Web server. The page is a static page, so you can simply copy it to the public directory of your Rails application. You can use the default WEBrick Web server, but it requires additional configuration to serve XHTML pages. Mongrel on the other hand needs no extra configuration for XHTML pages.

As mentioned earlier, submitting the form will cause a POST of the model instance. However, before you start to process that post, you should perform some validation.

Validation

One of the most common tasks in data entry is validation of the input data. It is often preferable to do this on the client, to prevent the submission of invalid data to the back-end of a system. This usually involves writing JavaScript to extract data and then running a rule (maybe a regular expression) on it. Luckily XForms simplifies this quite a bit. XForms is all about XML and data validation has a well defined syntax in XML: XML schema. Add validation to the XForms you created with the previously defined schema in Listing 2, as shown in Listing 4.

Listing 4. XForms model with schema for validation
    <title>Patient Information</title>
    <style type="text/css">
    @namespace xf url("http://www.w3.org/2002/xforms");
       xf|input {
          display: table-row;
          line-height: 2em;
       }
       
       xf|label {
          display: table-cell;
          text-align: right;
          font-family: Ariel, Helvetica, sans-serif;
          font-weight: bold;
          font-size: small;
          padding-right: 5px;
          width: 250px;
       }
	   xf|*{
		  display: table-row;
	      line-height: 2em;
	   }
       #submitLabel{
		  display: table-row;
	   }
       *:required {
           background-color: yellow;
       }
       
       *:invalid  {
          background-color: yellow;
       }
    </style>
    <xf:model id="patientModel" schema="patient.xsd">

      <xf:instance xmlns="" id="patient">
        <p:Info>
          <FirstName></FirstName>
          <MiddleName></MiddleName>
          <LastName></LastName>
          <Age></Age>
          <Insurer></Insurer>
          <Id></Id>
          <PolicyHolder></PolicyHolder>
          <Copay></Copay>
          <Symptoms></Symptoms>
        </p:Info>
      </xf:instance>

      <xf:submission action="http://localhost:3000/kiosk/create"
                     method="post"
                     id="submit-info"/>
    </xf:model>

Notice that your model now references a schema (patient.xsd). XForms will load and validate against this schema automatically. Also notice you have added some CSS to show invalid data. If you bring this up in your browser it should look like Figure 2.

Figure 2. XForms with Validation and CSS.
XForms with Validation and CSS

The user must fill in the required fields in order to submit the form. The color changes automatically when a valid value is entered and the focus moves to a new form field. Think of the JavaScript you might normally write for this. Instead, you accomplished it with an XSD and some CSS.

XForms gives you many ways to do validation. You do not have to use an XML schema, but it does provide a simple way especially in the case of data that can be easily described through a schema. With client side validation in place, you can concentrate on processing the submitted data on the server.


Form submission

You might have noticed earlier that a URL for your form submission is already defined. If you are familiar with Ruby on Rails, then this URL should look pretty familiar. Convention over configuration, and following the Rails convention, the URL /patient/create corresponds to the create action on the patient controller. You can set this up easily using a Ruby generation script as in Listing 5.

Listing 5. Generating the patient scaffolding
>ruby script/generate scaffold patient kiosk
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/kiosk
      exists  app/views/layouts/
      exists  test/functional/
  dependency  model
      exists    app/models/
      exists    test/unit/
      exists    test/fixtures/
        skip    app/models/patient.rb
   identical    test/unit/patient_test.rb
   identical    test/fixtures/patients.yml
      create  app/views/kiosk/_form.rhtml
      create  app/views/kiosk/list.rhtml
      create  app/views/kiosk/show.rhtml
      create  app/views/kiosk/new.rhtml
      create  app/views/kiosk/edit.rhtml
      create  app/controllers/kiosk_controller.rb
      create  test/functional/kiosk_controller_test.rb
      create  app/helpers/kiosk_helper.rb
      create  app/views/layouts/kiosk.rhtml
   identical  public/stylesheets/scaffold.css

This generates a lot more than you need. You can delete unused files. The two important things it creates for you are the patient class and the kiosk_controller class. The kiosk_controller class is, as you might guess, the controller that will handle the request from your XForms. Now take a look at the model and controller and how you will modify them to save the XML from your XForms.

Rails model and controller

You used the Rails scaffolding to accelerate the development of your application. In many Rails applications, you can use the scaffolding to actually handle the interaction with the database without making any changes to the controller. That is possible by leveraging the object-relational mapping code in Rails's ActiveRecord class that is the base class for all models (including your patient class. ActiveRecord was not designed to handle XML, so you will need to modify the generated classes. To start, look at the patient class in Listing 6.

Listing 6. Default patient class
class Patient < ActiveRecord::Base
end

As you can see, by default the Patient class simply extends ActiveRecord. ActiveRecord dynamically creates accessors based on the column names of the database table that it is mapped to. It has a constructor that takes in a hash of name/value pairs. This is used by the application controller to simply pass in the form data directly. You can take advantage of this and modify the patient class in Listing 7.

Listing 7. Modified patient class
require 'rexml/document'

class Patient < ActiveRecord::Base
  def information=(value)
    self[:information] = value.to_s
  end    
end

You only made a slight change, but it is an important one. You want to keep the value of information as a string internally so that it will be serialized as a string when ActiveRecord inserts it into the database. But what is going to be passed in to your model? That is handled by the controller. Listing 8 shows the default controller.

Listing 8. Default Kiosk controller
class KioskController < ApplicationController
  def index
    list
    render :action => 'list'
  end

  # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
  verify :method => :post, :only => [ :destroy, :create, :update ],
         :redirect_to => { :action => :list }

  def list
    @patient_pages, @patients = paginate :patients, :per_page => 10
  end

  def show
    @patient = Patient.find(params[:id])
  end

  def new
    @patient = Patient.new
  end

  def create
    @patient = Patient.new(params[:patient])
    if @patient.save
      flash[:notice] = 'Patient was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

  def edit
    @patient = Patient.find(params[:id])
  end

  def update
    @patient = Patient.find(params[:id])
    if @patient.update_attributes(params[:patient])
      flash[:notice] = 'Patient was successfully updated.'
      redirect_to :action => 'show', :id => @patient
    else
      render :action => 'edit'
    end
  end

  def destroy
    Patient.find(params[:id]).destroy
    redirect_to :action => 'list'
  end
end

Earlier you defined the action URL for the XForms as /kiosk/create, thus the create method above is what will be called. Rails thinks that it is getting a series of HTML form elements on submission, not an XML document. So you will modify this method slightly to parse the XML document that is sent in by the XForms you wrote. Listing 9 shows the modified create method.

Listing 9. Modified create method
  def create
    doc = REXML::Document.new("<Info></Info>")
    params[:Info].each_pair do |key,value|
      if (key.index(':') == nil) #namespace attributes 
        el = REXML::Element.new key
        el.add_text value
        doc.root.add el
      else
        doc.root.add_attribute key,value
      end
    end
    @patient = Patient.new
    @patient.information = doc
    if @patient.save
      flash[:notice] = 'Patient was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

The first thing you might notice is that you are using REXML to create an XML document. REXML is a standard library included with Ruby. The XML document from XForms comes in as the Info parameter and is parsed as an object. You simply iterate over it and transform it back into XML. This is all you need to do to get Rails to insert your document into the database. Now you can test it out.


Testing the application

To test the application, you simply start up your server using the usual ruby script/server start command. Then point the browser to the XForms as in Figure 2. Fill out the form and click Submit Information. You should see the screen in Figure 3.

Figure 3. List of patients
List of patients

Where did this UI come from? It is the standard list UI provided by Rails scaffolding. Notice that it shows your Info field with a blob of unformatted XML. If you scroll to the right, you should see controls to view, edit, and delete the patient. The viewing and deleting both work as is! The editing does not, nor does the New Patient link at the bottom of the page. You must modify the links to use the XForms, since that is what you want to use to enter data. That's what Part 3 of this series will help you do!


Summary

You saw how to use XForms to create a data entry form for patients to enter their information. XForms takes care of binding patient data to an XML document and can even validate against an XML schema. Your application submits this XML document to a Ruby on Rails controller. Ruby makes it easy to parse this input as XML and to serialize that XML as a string. DB2 understands the serialized XML string and can store it natively using pureXML technology. Rails is even able to display this data with no special work on your part. From here, you can use DB2 XML/SQL syntax to query the XML data or retrieve it and use Ruby to navigate and extract data.


Download

DescriptionNameSize
Part 2 sample codepart2_doctorsOffice.zip7KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Information Management
ArticleID=309632
ArticleTitle=XForms and Ruby on Rails at the doctor's office, Part 2: Implementing the patient information XForm
publish-date=06042008