實現患者信息XForm
簡介
本系列文章的第 1 部分設計了一個 Web 應用程序,讓患者在醫生辦公室中輸入信息。討論了如何使用 XForms、DB2 pureXML 和 Ruby on Rails 創建這樣的應用程序,並對這些技術的用法做了一些實驗。本文是第 2 部分,我們開始實現該應用程序。我們將設計第一個 XForm,並建立從表單向 DB2 插入數據的 Ruby on Rails 後端。您將看到如何通過這三種技術在整個應用程序中利用 XML。
先決條件
本文假設讀者對 XML 和 Web 應用程序有一定的了解。事先對這三種核心技術,XForms、DB2 pureXML 和 Ruby on Rails 有所涉獵當然很有幫助,不過絕對不是必需的。本文使用了 Mozilla XForms 0.8.0.3 版插件。它為所有 Mozilla 浏覽器,如 Firefox,提供了 XForms 運行時支持。另一種有用的 Mozilla 插件是 XForms Buddy,提供了 XForms 調試器。本文使用的是 0.5.6 版。還需要 IBM 的 DB2 數據庫服務器。本文使用 DB2 Express-C 9.5 版,支持 Windows®、Linux® 和 UNIX® 系統。此外還需要 Ruby on Rails,本文使用的是 Ruby 1.8.6 和 Rails 1.2.5。還用到了與 Rails 結合使用的 Mongrel Web 服務器。可通過 Ruby Gems 安裝(只要在命令行中輸入 gem install mongrel 即可)。
患者信息
第 1 部分討論了所用的 XForms、DB2 pureXML 以及 Ruby on Rails,它們支持在應用程序的前端和後端使用 XML。這種設計的一個好處是將 XML 數據放在了核心位置。XML 數據模型的設計決定了如何實現基於 XForms 的前端,以及如何使用後端的 Ruby on Rails 從 DB2 檢索數據。因此,開發應用程序應該首先從設計 XML 數據模型開始。
XML 數據模型
應用程序允許患者輸入醫生和其他醫務人員需要的信息。比如患者的姓名、保險公司、年齡、合作醫療支付額度等,當然還有他們的症狀。了解這些之後,清單 1 顯示了典型的數據模型實例。
清單 1. 典型的患者信息 XML 實例
<?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>
大部分是簡單的字符串,但也有少數例外。中間名應該是可選的字段。年齡是整數,並且必須是非負數。co-pay 同樣如此。保單持有者是一個 Boolean 標志。Id 應該定義成固定的范式:三個數字、兩個數字、四個數字。清單 2 顯示了描述患者數據模型的 XML 模式。
清單 2. 患者模式
<?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>
定義了 XML 數據模型之後,現在可以編寫允許用戶建立數據模型實例的 XForms 了。
患者 XForms
現在我們知道了需要患者輸入應用程序的數據。定義 XForms 就非常簡單了。只要建立和數據模型中元素相對應的表單元素就行了。每個表單元素的類型取決於數據元素的數據類型。患者信息的 XForms 如清單 3 所示。
清單 3. 患者信息 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>
下面對 清單 3 中的 XForms 簡單介紹一下。它有一個模型和一個實例。實例是前面定義的患者信息數據模型的空白版本。提交是模型的一部分。提交將把模型實例傳遞給提交動作屬性中指定的 URL。表單體包含一系列的 XForms 元素。每個元素都使用 ref XPath 綁定到模型實例。無論用戶在表單中輸入什麼數據,都將綁定到模型實例。在浏覽器中打開 XForms 將看到類似圖 1 所示的結果。
圖 1. 患者 XForms
雖然不夠完美,但確實是一個具備所需功能的 XForm。有幾點需要注意:
.
首先,這是一個 XHTML 頁面,而不是通常的 HTML 頁面。如果將頁面擴展名改為 .html 或者 .htm,就無法正確地呈現。這是因為 XForms 要求使用 XHTML,因此 Mozilla 插件對 HTML 頁面不起作用。
其次,由於是 XHTML,因此仍然支持 CSS。這是改變頁面外觀的最簡單方式。很快將使用 CSS 作為數據驗證的一部分。
最後,還要注意 URL http://localhost:3000/patient.xhtml。上例中的頁面由 Mongrel Web 服務器上的 Ruby on Rails 應用程序提供。該頁面是靜態的,因此只需要將其復制到 Rails 應用程序的 public 目錄。也可使用默認的 WEBrick Web 服務器,但是需要額外配置才能支持 XHTML 頁面。而 Mongrel 不需要額外的配置就能支持 XHTML 頁面。
如前所述,提交表單將觸發模型實例的 POST。但是在處理 post 之前需要進行某些驗證。
驗證
數據輸入最常見的任務之一就是檢查輸入的數據。通常最好在客戶機完成,以免把無效的數據提交到系統後端。一般來說,這需要編寫 JavaScript 提取數據然後執行某些規則(也許是正則表達式)。幸運的是 XForms 在這方面做了很多簡化。XForms 使用的是 XML,XML: XML schema 定義了完善的數據驗證語法。使用前面 清單 2 定義的模式為新建的 XForms 添加驗證,如清單 4 所示。
清單 4. 帶有驗證模式的 XForms 模型
<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>
請注意,模型引用了一個模式(patient.xsd)。XForms 會自動加載該模型並進行驗證。此外還增加了一些 CSS 顯示無效的數據。在浏覽器中打開的結果如圖 2 所示。
圖 2. 包含驗證和 CSS 的 XForms
用戶必須輸入必要的字段才能提交表單。如果輸入的數據有效,將自動改變顏色並將焦點轉移到新的表單字段。想一想,如果使用 JavaScript 需要編寫多少代碼。但利用 XSD 再加上一些 CSS 就輕松完成了。
XForms 提供了多種數據驗證方式。不一定要使用 XML 模式,但這確實是一種簡單的辦法,尤其是對於很容易用模式描述的數據。客戶端驗證就緒之後,可以在服務器端集中處理提交的數據了。
表單提交
您可能已經注意到為表單提交定義了一個 URL。如果熟悉 Ruby on Rails,對這個 URL 應該不陌生。約定優於配置,因此遵循 Rails 的約定,URL /patient/create 對應 patient 控制器的 create 動作。使用 Ruby 生成腳本可以很容易實現,如清單 5 所示。
清單 5. 生成患者 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
生成的東西有些不需要。可以刪除沒有用的文件。最重要的兩個是 patient 和 kiosk_controller 類。kiosk_controller 類,您可能已經猜到,是用於處理 XForms 請求的控制器。現在看看如何修改模型和控制器以便保存來自 XForms 的 XML。
Rails 模型和控制器
使用 Rails scaffolding 可以加快應用程序的開發。很多 Rails 應用程序中,不需要修改控制器,就可以直接使用 scaffolding 處理和數據庫的交互。通過利用 Rails ActiveRecord 類的對象-關系映射代碼,這是可以實現的,該類是所有模型的基類(包括 patient 類)。ActiveRecord 最初並非用於處理 XML,因此需要修改生成的類。首先看看清單 6 所示的 patient 類。
清單 6. 默認的 patient 類
class Patient < ActiveRecord::Base end
可以看到,默認情況下 Patient 類僅僅擴展了 ActiveRecord。ActiveRecord 根據映射的數據庫表的列名動態創建訪問器。它有一個構造函數接受名稱/值對作為參數。應用程序控制器利用它直接傳入表單數據。利用這一點我們來修改 patient 類,如清單 7 所示。
清單 7. 修改後的 patient 類
require 'rexml/document' class Patient < ActiveRecord::Base def information=(value) self[:information] = value.to_s end end
變動不大,但是很重要。我們希望在內部用字符串保存信息,這樣在 ActiveRecord 將信息插入數據庫的時候就能序列化為字符串。但是傳遞給模型的時候呢?由控制器來完成。清單 8 顯示了默認的控制器。
清單 8. 默認的 Kiosk 控制器
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
前面已經把 XForms 的動作 URL 定義為 /kiosk/create,因此將調用上面的 create 方法。Rails 認為提交的時候獲取的是一系列 HTML 表單元素而不是 XML 文檔。因此需要修改這個方法以便解析 XForms 發送的 XML 文檔。清單 9 顯示了修改後的 create 方法。
清單 9. 修改後的 create 方法
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
首先要注意,這裡使用 REXML 創建 XML 文檔。REXML 是 Ruby 提供的一個標准庫。XForms 通過 Info 參數傳遞的 XML 文檔作為一個對象來解析。只需要迭代遍歷再轉換回 XML 即可。這樣就可以讓 Rails 把文檔插入數據庫。現在可以測試一下。
測試應用程序
測試應用程序只需要使用一般的 ruby script/server start 命令啟動服務器。然後在浏覽器中打開 XForms,如 圖 2 所示。填寫表單並單擊 Submit Information。結果如圖 3 所示。
圖 3. 患者名單
這個 UI 是怎麼來的?這是 Rails scaffolding 提供的標准列表 UI。請注意,它使用一些非格式化的 XML 顯示 Info 字段。向右滾動可以看到查看、編輯和刪除患者的控件。查看和刪除都沒有問題!但是編輯不行,頁面下方的 New Patient 鏈接也不行。必須讓這些鏈接指向 XForms,我們需要從這裡輸入數據。這是本系列第 3 部分要解決的內容!
結束語
本文介紹了如何使用 XForms 創建數據輸入表單讓患者輸入自己的信息。XForms 負責將患者數據綁定到 XML 文檔,甚至能夠按照 XML 模式驗證數據。應用程序將 XML 文檔提交給 Ruby on Rails 控制器。Ruby 很容易地將輸入解析為 XML 並將其序列化為字符串。DB2 理解序列化的 XML 字符串,使用 pureXML 技術存儲它。甚至不需要進行額外的工作,Rails 就能夠顯示這些數據。接下來,可以使用 DB2 XML/SQL 語法查詢 XML 數據和檢索,使用 Ruby 導航和提取數據。
本文配套源碼