级别: 中级 Michael Galpin (mike.sr@gmail.com), 软件架构师, Ludi Labs
2008 年 7 月 03 日
本系列文章 介绍如何结合使用 XForms、IBM® DB2® pureXML™ 和 Ruby 来简化 Web 应用程序的开发,分为四部分,这是第二部分。本系列文章中将开发一个虚构的应用程序来管理医生办公室中的患者信息。您将看到每种技术的强大功能,同时还将看到如何将其结合起来。
简介
本系列文章的第 1 部分设计了一个 Web 应用程序,让患者在医生办公室中输入信息。讨论了如何使用
XForms、DB2 pureXML 和 Ruby on Rails 创建这样的应用程序,并对这些技术的用法做了一些实验。本文是第 2 部分,我们开始实现该应用程序。我们将设计第一个 XForm,并建立从表单向 DB2 插入数据的
Ruby on Rails 后端。您将看到如何通过这三种技术在整个应用程序中利用 XML。
先决条件
 |
经常用到的缩写词
- API:应用程序编程接口
- CSS:级联样式表
- UI:用户界面
- URL:统一资源定位符
- XML:可扩展标记语言
- XSD: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 导航和提取数据。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 第 2 部分的样例代码 | part2_doctorsOffice.zip | 7KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
关于作者  | |  | Michael Galpin 从 1998 年开始开发 Web 应用程序,他从 California Institute of Technology 获得了数学学位。他是位于 San Jose, CA. 的 eBay 公司的架构师。 |
对本文的评价
|