级别: 中级 John Chun, DB2 高级支持专家, IBM Alex Pitigoi, 咨询软件工程师, IBM Naomi Ngan, 高级软件工程师, Autonomy Christine Law (cwylaw@ca.ibm.com), DB2 高级支持专家, IBM
2007 年 12 月 03 日 Ruby 语言的出现并与强大的 Rails 框架结合,为 Web 解决方案的开发带来了巨大的机遇。随着 IBM_DB 适配器和驱动程序的引入,Rails 应用程序现在可以无缝地与 IBM 数据服务器进行交互。本文是 DB2 with Ruby on Rails 系列的第一篇文章。文中介绍了 Starter Toolkit for DB2 on Rails,谈到了安装 IBM_DB 驱动程序的多种方法以及使用 DB2 的 Rails 迁移。
简介
Ruby on Rails 发布于 2004 年,如今已快速成为 Web 应用程序开发中最流行的框架之一。这个开放源代码项目又名 Rails 或 RoR,它采用 Model-View-Controller (MVC) 架构和 Ruby 面向对象脚本编制语言,并遵循一些简单的原则,例如 “约定优于配置(convention over configuration)” 和 “不要重复自己(don't repeat yourself)”。因此,开发人员可以更快、更容易地构建应用程序,减少冗余的代码和配置文件,同时又能灵活地创建定制的扩展,以满足应用程序需求。通过对数据库持久性的支持,可以使用数据库服务器和内置的 WEBrick Web 服务器快速开发使用这种框架的 Web 应用程序。
IBM_DB 为 Rails 领域带来了什么?
随着 RubyForge 社区门户上开始提供 IBM_DB Rails 适配器和 Ruby 驱动程序,Ruby on Rails 框架经过官方测试,并且在所有 DB2 数据服务器上受支持。对于你们当中某些人来说这也许是个惊喜,社区还有一个免费版本的名为 DB2 Express-C 的 DB2 9 数据库可以使用。DB2 Express-C 数据服务器的开发、部署和分发是免费的,没有大小、时间或用户方面的限制,而且还包括了 Starter Toolkit for DB2 on Rails。这个包可以帮助开发人员在使用 DB2 的 RoR 环境中快速、轻松地设置和构建 Web 应用程序。
虽然在 Rails 框架中可以配置的数据存储有很多种,但 DB2 数据服务器可以带来一些独特的、突出的优点。由于 DB2 9 中引入了 pureXML™,使用 IBM 数据服务器的首要优点是,IBM_DB 适配器和驱动程序支持本地 XML 数据类型。
DB2 on Rails 入门
目前有两种方法可以设置使用 DB2 的 Rails 开发环境。如果您对 DB2 环境不熟悉,那么可以借助 Starter Toolkit for DB2,这是开始进行 DB2 on Rails 应用程序开发的最方便的方式。Starter Toolkit version 2.1 可以从 alphaWorks 下载,其中还有一个稍旧版本的 IBM_DB 适配器的安装程序。这个版本不支持 i5 和 zOS 平台上的 DB2,但是更新工作正在进行,很快就会有新版本可供下载。
在 IBM_DB 适配器和驱动程序 gem 及插件发布之后,如果要在一个已有的 DB2 环境中开发 Rails 应用程序,那么可以使用 “manual” 选项,这个选项使用起来同样也很轻松。请参阅 RubyForge rubyibm project
获得最新版本(从 4 月 30 日起,Production/Stable release 0.6.0 已经可用)。
手动逐步安装 DB2 Express-C 9
首先下载和配置 DB2 Express-C 数据服务器和 Rails 运行时环境。最新版本的 IBM_DB 适配器和驱动程序要求使用 DB2 9,FixPack 2 或 DB2 8,FixPack 15。为了下载和配置 DB2 Express-C 数据服务器和 Rails 运行时环境:
 | |
注 1:由于与 IBM_DB 驱动程序和适配器中的一些重要更改有关联的 CLI 修复包(与数值引用相关)的缘故,需要使用 DB2 Client 9 FP2。
注 2:大多数 DB2 9 上的 Rails 应用程序都要求至少 1024 的 APPLHEAPSZ。
db2 update db cfg for <database_name> using APPLHEAPSZ 1024
|
为了更新 db cfg,需要对 DB2 进行重启(db2stop 加 db2start 命令)。
只有手动安装 IBM_DB 适配器和驱动程序(方法 2)时才需要考虑这个问题,因为在最新的 Starter Toolkit for DB2 on Rails 中这个值是默认的。
|
|
将 IBM_DB 适配器和驱动程序安装为 Ruby gem,与 Rails plugin 比较
为了帮助理解 Ruby gem 安装与 Rails plugin 安装之间的区别,下面简要描述一下运行时环境。
RubyGems 是用于 Ruby 运行时环境中的库和应用程序的标准打包和安装框架。对于每个 bundle,在一个中央存储库中会发布并存储一个遵从打包格式的名为 gem 的文件,以允许同时部署同一个库或应用程序的多个不同的版本。与 Linux 发布打包管理和 bundle(.rpm、.deb)类似,通过 gem 终端用户实用程序也可以查询、安装、卸载和操作这些 gem。gem 实用程序可以无缝地查询远程 RubyForge 中央存储库,并且可以查找和安装任何已有的能使 Rails 开发人员更轻松的实用程序。安装了 IBM_DB gem 之后,马上便可以在 Ruby 运行时环境中通过以下命令从任何脚本(和应用程序)中访问其功能:
- require 'rubygems'
- gem 'ibm_db' (在 Rubygems 0.9 中,require_gem 已经不赞成使用,而被 gem 替代)
作为一个 ActiveRecord 适配器,在 Rails 框架中使用它之前,IBM_DB gem 需要由抽象适配器注册到它的 RAILS_CONNECTION_ADAPTERS (active_record.rb) 列表中。注册之后,会加载 IBM_DB gem 和它的依赖项(ibm_db Ruby driver、IBM Driver for ODBC 和 CLI)。这样便使得 Ruby 环境中的任何应用程序,包括 Rails,可以与 IBM 数据服务器交互。正如 IBM_DB gem README 文件(见本文 参考资料 小节)中描述的那样,每过几个简单的步骤,就要启用 Ruby 运行时,以访问 IBM 数据服务器。
 | |
注 3:在 Windows 环境中,当运行 “gem install ibm_db” 时,对于每种版本(mswin32 或 ruby)有两个选择。通过选择 “mswin32”,可以安装预先构建的用于 Windows 的二进制文件。
例子:
D:\>gem install ibm_db
Bulk updating Gem source index for: http://gems.rubyforge.org
Select which gem to install for your platform (i386-mswin32)
1. ibm_db 0.6.0 (mswin32)
2. ibm_db 0.6.0 (ruby)
3. ibm_db 0.4.6 (ruby)
4. ibm_db 0.4.6 (mswin32)
5. Skip this gem
6. Cancel installation
|
|
|
将 IBM_DB 适配器和驱动程序安装为 Ruby Gem
-
发出 gem 命令,安装 IBM_DB 适配器和驱动程序:
-
将 ‘ibm_db’ 注册到 Rails 框架中的连接适配器列表中。
将 ibm_db 手动添加到 gems\1.8\gems\activerecord-1.15.3\lib\active_record.rb:
RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite ... ibm_db ) |
Rails 插件为该框架自身提供了扩展机制。通过这种机制,Rails 可以在一个特定的 Rail 应用程序范围内扩展其功能。因此,当 Ruby 运行时环境中没有部署 IBM_DB gem 时,这提供了另一种访问 IBM 数据服务器的方法。虽然 Rails 插件没有提供特定于 Ruby gem 的版本管理,但是它们提供了一种有用的初始化机制,该机制允许 IBM_DB 插件在其初始化期间自己插入到 Rails 注册表中。因此,在应用程序中以插件形式安装 IBM_DB 之后,无需任何手动步骤,就可以让 Rails 框架加载它。正如 IBM_DB 插件 README 文件和 RubyForge 上的
rubyibm 项目文档 中描述的那样,只需注册插件源,并运行 Rails 应用程序安装脚本,就可以启用 Rails 应用程序,以便进行 IBM 数据服务器访问。虽然 RubyForge 可以启用到 Subversion (SVN) 库的 HTTP 访问(目前只支持 SVN 协议),在 Windows 上安装 Rails 插件时,仍然需要一个 SVN 客户机。在大多数 Linux® 和 UNIX® 发行版上,SVN 客户机是默认提供的,这使得 IBM_DB 插件安装具有很好的无缝性。
客户机环境
IBM_DB 适配器(ibm_db_adapter.rb)对 ibm_db 驱动程序存在直接依赖,它利用 IBM Driver for Open Database Connectivity (ODBC) and Call Level Interface (CLI) 连接到 IBM 数据服务器。
IBM CLI 是 IBM 数据服务器的可调用 SQL 接口,它遵从 ODBC。
对于 IBM_DB 适配器和驱动程序还有一些额外的注意事项。
-
IBM Driver for ODBC 和 CLI 的安装必需满足 IBM_DB 需求。
可以通过完全安装 DB2 数据库获得 IBM Driver for ODBC 和 CLI,也可以从 “IBM DB2 Driver for ODBC and CLI overview” 中单独获得这两个组件。
-
可以使用 CLI 关键词在任何 Ruby 应用程序之外修改驱动程序行为。
可以使用一些 CLI 关键词在 Rails 应用程序之外修改某些事务行为。例如,可以使用它们来设置当前模式或者修改一些事务元素,例如关闭自动提交行为。关于 CLI 关键词的细节可以参阅以下 DB2 Info Center 文档:
Version 8
Version 9
-
诊断信息的收集需要 CLI driver 跟踪实用程序。
由于通过 IBM_DB driver 的所有请求都是使用由
IBM Driver for ODBC 和 CLI 提供的 API 实现的,CLI 跟踪是识别使用 IBM_DB 适配器和驱动程序的应用程序中的问题的重要机制。
CLI 跟踪捕捉应用程序向 IBM driver for ODBC 和 CLI
发出的所有 API 调用(包括所有输入参数),并捕捉驱动程序返回给应用程序的所有值。它是设计用来捕捉应用程序如何与 IBM driver for ODBC 和 CLI 交互的一种接口跟踪,并提供关于驱动程序内部工作的信息。
Version 8
Version 9
数据库模式的变更
在一个多变的环境中,应用程序需要动态地做出调整,以解决新的需求和挑战。当应用程序开发人员更改他们的应用程序时,例如添加一个新的对象或类,就需要修改底层持久性,以确保数据库与应用程序同步。更改数据库模式的传统方法是生成新的 SQL 脚本。但是,通过 SQL 脚本难于按版本依次保存应用程序和数据库。而且,数据库开发人员很少构建 SQL 脚本来逆转数据库模式中做出的与应用程序更改相关联的更改。在大多数开发环境中,更改是通过删除所有数据库对象,并使用之前版本的 SQL(DDL)脚本重新创建它们来逆转的。
对于很多 Web 开发人员来说,在 Rails 上的主要发现是其内置的对通过迁移来变更模式的支持。虽然数据库开发人员肯定更倾向于通过 DDL 或数据操作语言(DML)使用 SQL,但大多数应用程序开发人员更乐意坚持使用他们的工具:Ruby 语言和它的库。这正是 Rails 通过迁移提供的东西:一个简单而有效的基础设施,它利用 ActiveRecord 抽象按版本依次创建和修改数据库对象,例如表和列。通过 Rails 迁移完成模式变更这一主要的数据库管理任务。Rails 框架简化了开发,但是相关的工具(rake 任务)在驱动数据库更改和使应用程序代码与表结构同步方面也非常有效。
使用 Rails 迁移进行 DB2 模式变更
Ruby on Rails 迁移可以解决前面描述的涉及数据库和数据结构更改的一些问题。现在,让我们看看 Rails 迁移如何为数据库模式变更提供帮助。
我们来考察一个 Rails 迁移的例子,该例子使用 IBM_DB 适配器,并尝试一些与 Rails 应用程序变更相关的数据库对象更改。
但是,首先需要确保像前面描述的那样安装和配置了 DB2 on Rails 开发环境。
我们的例子将尝试构建一个 Team Room,这是一个托管应用程序,它使注册的成员可以共享文本或二进制格式的各种文档,包括图像、可执行文件和任何其他媒体。另外还包括被共享的 XML 文档,因为通过 pureXML 数据类型很容易将它们本地存储在 DB2 9 中。这个例子还将发现利用 XML 文档内置层次结构的一些好方法。
-
首先,创建一个名为 “teamroom” 的 Rails 项目:
清单 1. 创建 Team Room Rails 项目
D:\rails>rails teamroom
create
create app/controllers
create app/helpers
create app/models
create app/views/layouts
create config/environments
create components
create db
<etc ......>
create log/server.log
create log/production.log
create log/development.log
create log/test.log
|
Rails 框架自动生成该项目的目录结构。从这里开始,我们假设是在 D:\rails\teamroom 目录中,此后提到的所有路径都是 Team Room
项目目录下的相对路径。
-
如果已经有一个 DB2 数据库,那么可以忽略这一步,直接进入步骤 3,开始配置数据库连接。
预计可能需要在本地将 XML 存储在 DB2 中,因此让我们使用 UTF-8 编码集创建 XMLDB 数据库。这里必须使用 UTF-8 编码集,以便在 DB2 表中定义 XML 列。
在 DB2 Command Line Processor 中,运行以下命令:
清单 2. 创建 XMLDB 数据库
db2 create db xmldb using codeset utf-8 territory us
|
-
现在编辑 D:\rails\teamroom\config\database.yml 文件,以建立到 DB2 9 XMLDB 数据库的连接。
清单 3. 编辑 database.yml 文件
# IBM DB2 Database configuration file
#
# Install the IBM DB2 driver and get assistance from:
# http://www.alphaworks.ibm.com/tech/db2onrails
development:
adapter: ibm_db
database: xmldb
username: user
password: secret
schema: teamroom
application: TeamRoom
account: devuser
workstation: devbox
# == remote TCP/IP connection (required when no local database catalog entry available)
# host: bigserver // fully qualified hostname or IP address
# port: 50000 // data server TCP/IP port number
|
下面的表中解释了 DB2 的每个连接属性:
表 1. 可用于 database.yml 的 DB2 连接属性
| 连接属性 | 描述 | 是否必需 |
|---|
| Adapter | Ruby 适配器名称,对于 DB2 为 ‘ibm_db’ | 是 |
|---|
| Database | Rails 项目所连接到的数据库 | 是 |
|---|
| Username | 用于连接到 DB2 数据库的用户 ID | 是 |
|---|
| Password | 指定的用户 ID 的密码 | 是 |
|---|
| Schema | 命名对象的集合。模式提供了在数据库中在逻辑上组织对象的方式。在这个例子中,我们将 Rails Team Room 项目的所有数据库对象组织在 ‘teamroom’
数据库模式之下。这样便允许多个 Rails 项目共享一个数据库 | 可以将默认模式设置为当前会话用户的授权 ID(见注 4) |
|---|
| Application | 当使用 DB2 Connect 时,用于标识客户机应用程序名称的一个字符串被发送至主机数据库服务器。在 DB2 Connect 上,发出 ‘db2 list applications’ 后将显示 ‘application’ 名称,而不是 Ruby 可执行文件。 | 可选 |
|---|
| Account | 当使用 DB2 Connect 时,用于标识客户机帐户的一个字符串被发送至主机数据库服务器 | 可选 |
|---|
| Workstation | 当使用 DB2 Connect 时,用于标识客户机工作站名称的一个字符串被发送至主机数据库服务器 | 可选 |
|---|
| Host | 数据库所在远程服务器的主机名 | 可选(见注 5) |
|---|
| Port | 该参数包含数据库服务器用于等待来自远程客户机的通信的 TCP/IP 端口的名称 | 可选(见注 5) |
|---|
 |
注 4:总是显式指定模式
强烈建议显式地指定模式,以便在逻辑上组织数据库对象。否则,如果使用相同的授权 ID 连接到由多个 Rails 项目共享的同一个数据库,就可能无意中致使多个 Rails 项目写入到同一个 <AuthID>.schema_info 表中。这将导致不可预测的结果。Schema_info 表用于跟踪迁移版本。下面的 Team Room 例子对此作了进一步的解释。
注 5:只有当没有可用的 DB2 编目信息,且没有在用于 DB2 CLI 的 db2cli.ini 配置文件中注册数据源时,与远程 TCP/IP 连接相关的可选连接属性 host 和 port 才是必需的。在使用 IBM Driver for ODBC 和 CLI,而不是本地安装的完整的 DB2 Client 的情况下,就可能出现这种类型的设置。
|
|
Team Room 例子
现在可以开始执行实际的迁移步骤了。
步骤 1:创建 Documents 表
首先需要从一个存储区开始,使所有文档和媒体文件在这个托管应用程序的用户之间共享。因此,首先创建 DOCUMENTS 表,用于存储小组成员要共享的所有媒体文件。
我们定义以下列来描述共享文件的内容:
表 2. DOCUMENTS 表列和描述
| 列名 | 数据类型 | 描述 |
|---|
| ID | Integer | 主键 |
|---|
| Name | VARCHAR | 文档名称 |
|---|
| Size | Integer | 文件大小 |
|---|
| Data | BLOB | 以二进制模式存储的文件,最大 2 M |
|---|
| Content_type | VARCHAR | 文档类型,包括 .doc、.ppt、.pdf、.sw、.exe、.a、.so、
.gif、.jpeg、.mov、.avi、.mpeg、.bmp 等 |
|---|
a) 为了通过迁移的方式完成这个工作,执行 ruby script\generate migration create_docs_store,以生成表 DOCUMENTS 的迁移。Rails 生成迁移,并创建 db/migrate/001_create_docs_store.rb。在 UNIX 上,可以输入
ruby script/generate migration create_docs_store。(注意正斜杠 “/” 在 Windows 和 UNIX 上都适用。)
清单 4. 运行 ruby 脚本/生成迁移 create_docs_store
D:\rails\teamroom>ruby script/generate migration create_docs_store
create db/migrate
create db/migrate/001_create_docs_store.rb
|
注意,001_ create_docs_store.rb. 是在 db/migrate 中创建的,并且被编号为 ‘001’。从这一步开始,对于每个迁移步骤,db/migrate 中生成的文件依次递增编号。这表明 Rails 正在管理迁移的次序。
b) 像下面这样编辑 create_docs_store.rb:
清单 5. 编辑 create_docs_store.rb
class CreateDocsStore < ActiveRecord::Migration
def self.up
create_table :documents do |t|
t.column :name, :string, :null => false
t.column :size, :integer, :null => false
t.column :content_type, :string, :null => false
t.column :data, :binary, :limit => 2.megabytes
end
end
def self.down
drop_table :documents
end
end
|
 |
注 6:默认情况下表名采用复数形式
默认情况下 Rails 以复数形式为表命名。运行上述迁移将在 DB2 中创建一个名为 DOCUMENTS 而不是 DOCUMENT 的表。
注 7:
rake db:migrate 运行所有要应用的迁移。在这种情况下,只有一个要应用的迁移。
|
|
在 Rails 应用程序中,模式的更改是通过迁移发生的。对数据库的每次更改都被进行版本控制,并在包含独立于供应商的语法的 Ruby 脚本中定义。而且,每个迁移脚本提供两个类方法:up 方法和 down
方法,这使得撤销更改与做出更改一样容易。对逻辑数据库模式做出更改所需的代码都放在 self.up 方法中。而撤销更改所需的代码都放在 self.down 方法中。
c) 通过 rake db:migrate 命令运行迁移:
清单 6. 运行第一个迁移,以创建 DOCUMENTS 表
D:\rails\teamroom>rake db:migrate
(in D:/rails/teamroom)
== CreateDocsStore: migrating =================================================
-- create_table(:documents)
-> 0.2010s
== CreateDocsStore: migrated (0.2010s) ========================================
|
d) 现在进入到 DB2 命令提示符下,发出 db2 list tables for schema teamroom,以确认 DOCUMENTS 表已创建:
清单 7. 列出被创建的 DB2 表
D:\>db2 list tables for schema teamroom
Table/View Schema Type Creation time
------------------------------- --------------- ----- --------------------------
DOCUMENTS TEAMROOM T 2007-04-21-21.00.18.131001
SCHEMA_INFO TEAMROOM T 2007-04-21-21.00.17.740001
2 record(s) selected.
|
注意,DOCUMENTS 表如预期般被创建,并且数据类型如下:
清单 8. 检查为 DOCUMENTS 表创建的列
D:\>db2 describe table teamroom.documents
Column Type Type
name schema name Length Scale Nulls
------------------------------ --------- ------------------ -------- ----- ------
ID SYSIBM INTEGER 4 0 No
NAME SYSIBM VARCHAR 255 0 No
SIZE SYSIBM INTEGER 4 0 No
DATA SYSIBM BLOB 2097152 0 Yes
CONTENT_TYPE SYSIBM VARCHAR 255 0 No
5 record(s) selected.
|
 |
注 8:主键的自动创建
在 Rails 中,默认的主键列名是 id。
不必显式地定义主键 ID 列,因为默认情况下 Rails 会自动这么做。
|
|
还需注意,在这个迁移期间,Rails 还创建了一个名为 SCHEMA_INFO 的表。SCHEMA_INFO 是通过 rake db:migrate 命令创建的。
查询 SCHEMA_INFO 表时可以得到如下输出:
清单 9. 查询 SCHEMA_INFO 表
D:\>db2 select * from schema_info
VERSION
-----------
1
1 record(s) selected.
|
version 列中的值 ‘1’ 表明当前的迁移版本为 1(还记得之前曾运行过的 db/migrate/001_ create_docs_store.rb 吗)。
所以 rake db:migrate 实际上做以下事情:
- 创建 SCHEMA_INFO 表(如果该表不存在的话),并将值 ‘0’ 插入到 version 列中。
- 运行所有可应用的迁移。也就是说,运行编号大于当前迁移的所有迁移的
up 方法。第一个迁移从 version 值 ‘1’ 开始。
- 通过运行最新版本的迁移更新 SCHEMA_INFO 表。在上述例子中,version 被更新为 ‘1’,因为它是这个 Rails 项目的第一个迁移。
步骤 2:与文档相关的附加属性
在创建 DOCUMENTS 表之后,假设您决定存储关于每个文档的附加信息,例如操作系统平台、上传时间和最近更改时间。产生的 DOCUMENTS 表应该包含以下列(新添加的列用粗体标示)。
表 3. DOCUMENTS 表中的列和描述
| 列名 | 数据类型 | 描述 |
|---|
| ID | Integer | 主键 |
|---|
| Name | VARCHAR | 文档的名称 |
|---|
| Size | Integer | 文件大小 |
|---|
| Data | BLOB | 以二进制模式存储的文件,最大 2 M |
|---|
| Content_type | VARCHAR | 文档类型,包括:.doc、.ppt、.pdf、.sw、.exe、.a、.so、.gif、.jpeg、.mov、.avi、.mpeg、.bmp 等 |
|---|
|
Created_at
|
TIMESTAMP
|
文件被上传的时间(见注 9) |
|---|
|
Updated_at
|
TIMESTAMP
|
文档最近更新时间戳(见注 9) |
|---|
|
Platform
|
VARCHAR
|
特定于文件平台的信息
|
|---|
 |
注 9:对于 Rails,选择的两个列名 created_at 和
updated_at 负有特殊语义。和其他 “magic column names”
(id、type、position、lock_version、parent_id>)一样,Rails 将使用这种名称作为惯例。在这种情况下,它将自动用一个行被创建或最近被更新的时间戳来进行更新。它只要求底层数据库列能接收 date、datetime 或 string 类型。完整的 Rails 惯例是,对于 date 列使用 _on 语法,而对于包含时间的列则使用 _at
语法。
|
|
然后生成第二个迁移,以便将这些属性添加到 DOCUMENTS 表中:
a) 运行 ruby script/generate migration add_docs_attributes。
这样会生成 db/migrate/002_add_docs_attributes.rb 文件。
清单 10. 创建第二个迁移,以便将列添加到 DOCUMENTS 表中
D:\rails\teamroom>ruby script/generate migration add_docs_attributes
exists db/migrate
create db/migrate/002_add_docs_attributes.rb
|
 | |
注 10:迁移的思想是,如果需要更改持久数据库中的内容,则可以生成一个新的迁移来执行更改。不需要为了做出更改而编辑原始的迁移。
|
|
b) 像下面这样编辑 002_add_docs_attributes.rb:
清单 11. 查询 SCHEMA_INFO 表
class AddDocsAttributes < ActiveRecord::Migration
def self.up
add_column :documents, :created_at, :timestamp
add_column :documents, :updated_at, :timestamp
add_column :documents, :platform, :string, :limit => 10
end
def self.down
remove_column :documents, :created_at
remove_column :documents, :updated_at
remove_column :documents, :platform
end
end
|
 |
注 11:可以使用 add_column 在 self.up 方法中添加属性。若要逆转更改,可以使用 remove_column。
|
|
c) 同样,发出 rake db:migrate 来执行第二个迁移。
清单 12. 运行第二个迁移,将附加列添加到 DOCUMENTS 表中
D:\rails\teamroom>rake db:migrate
(in D:/rails/teamroom)
== AddDocsAttributes: migrating ===============================================
-- add_column(:documents, :created_at, :timestamp)
-> 0.0500s
-- add_column(:documents, :updated_at, :timestamp)
-> 0.0100s
-- add_column(:documents, :platform, :string, {:limit=>10})
-> 0.0000s
== AddDocsAttributes: migrated (0.0600s) ======================================
|
 |
注 12:可以再次使用 db2 describe table teamroom.documents 来确认新的列是否被添加到 DOCUMENTS 表中。
注 13:
<model_name>_id
是对以复数形式命名为
<model_name>
的表的外键引用的默认名称。
在 Rails 中,诸如外键之类的关联是通过模型关系实现的。
该框架用于管理这种模型关系的重要惯例之一是表的主键列(它的
id
)的默认名称,而对表的外键引用的默认名称为
<model_name>_id
(表名是复数形式的
<model_name>
)。
Rails 迁移并不定义外键约束,理解这一点很重要。
表之间的关系是在开发人员用来自有某种关系的表中的健值填充列时设置的。建议另外还通过迁移在数据库中建立并强制实施那些约束,不过这不是 Rails 框架的需求。
|
|
步骤 3:管理用户和他们对资源的访问
组织或社区中的很多人都可能对这些文档感兴趣,所以需要有一种方式来管理那些用户和他们的访问。为此我们添加一个
USERS 表。另外还需要添加一个外键
'user_id' 到 DOCUMENTS 表中,以便知道哪个用户上传某个特定的文档。
下面是执行这些任务所需的步骤:
a) 运行 ruby script/generate migration create_users_table,这将创建 db/migrate/003_create_users_table.rb 文件
b) 像下面这样编辑 db/migrate/003_create_users_table.rb 文件:
清单 13. 编辑 003_create_users_table.rb
class CreateUsersTable < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column :usertype, :string, :limit => 5, :null => false
t.column :firstname, :string, :limit => 30
t.column :lastname, :string, :limit => 30
t.column :extension, :string, :limit => 4
end
add_column :documents, :user_id, :integer
end
def self.down
drop_table :users
remove_column :documents, :user_id
end
end
|
c) 运行 rake db:migrate 以创建 USERS 表:
清单 14. 通过迁移创建 USERS 表
D:\rails\teamroom>rake db:migrate
(in D:/rails/teamroom)
== CreateUsersTable: migrating ================================================
-- create_table(:users)
-> 0.1400s
-- add_column(:documents, :user_id, :integer)
-> 0.0000s
== CreateUsersTable: migrated (0.1400s) =======================================
|
d) 现在可以发出 ruby script/generate scaffold document,以便为 DOCUMENTS 表生成一个 scaffold。scaffolding 通过提供列出、显示、创建、更新和销毁类的对象的标准化动作,快速地使一个 Active Record 类上线。如下面的清单 15 所示,在 scaffold 生成期间,/app/controllers 和 /app/views 中创建了一个控制器和多个视图。
清单 15. 为文档创建 scaffold
D:\rails\teamroom>ruby script/generate scaffold document
exists app/controllers/
exists app/helpers/
create app/views/documents
create app/views/layouts/
create test/functional/
dependency model
create app/models/
exists test/unit/
exists test/fixtures/
create app/models/document.rb
create test/unit/document_test.rb
create test/fixtures/documents.yml
create app/views/documents/_form.rhtml
create app/views/documents/list.rhtml
create app/views/documents/show.rhtml
create app/views/documents/new.rhtml
create app/views/documents/edit.rhtml
create app/controllers/documents_controller.rb
create test/functional/documents_controller_test.rb
create app/helpers/documents_helper.rb
create app/views/layouts/documents.rhtml
create public/stylesheets/scaffold.css
|
至此,可以看看 scaffold 可以做哪些事情。在命令提示符下,输入 ruby script/server
以启动用于 Rails 的内置 WEBrick Web 服务器:
清单 16. 启动内置的 WEBrick Web 服务器
D:\rails\teamroom>ruby script/server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2007-04-26 16:54:57] INFO WEBrick 1.3.1
[2007-04-26 16:54:57] INFO ruby 1.8.5 (2006-12-25) [i386-mswin32]
[2007-04-26 16:54:57] INFO WEBrick::HTTPServer#start: pid=444 port=3000
|
注意端口号。默认端口号为 3000,但是如果在系统上端口 3000 已经被占用,那么端口号可能有所不同。
打开一个 Web 浏览器,进入 http://localhost:3000/,您将注意到一条欢迎登录消息。浏览至 http://localhost:3000/documents,您将看到:
图 1. 列出文档
e) 编辑生成的 /app/models/document.rb 文件,使之如下所示:
清单 17. 编辑 /app/models/document.rb
class Document < ActiveRecord::Base
belongs_to :user
end
|
belongs_to 表达了
DOCUMENTS 与 USERS 表之间一对一的外键关系。这种关联表明,每个文档只能与一个用户关联(即只能属于一个用户)。如果 DOCUMENTS 表有一个 user_id 外键列,则文档模型为
belongs_to :user。
 |
注 14:包含外键的表的模型总是有 belongs_to 声明。 |
|
我们在 /app/models/document.rb 中添加附加的代码,以便可以真正上传文档,并将文件存储在 DB2 9 数据库中。请参阅下载小节,以查看代码实现。在将很多文档添加到 Team Room 之后,在浏览器中可以看到如下所示的界面:
图 2. 列出文档
f) 类似地,发出 ruby script/generate scaffold user,以便为 Users 表生成一个 scaffold。然后编辑 /app/models/user.rb,使之如下所示:
清单 18. 编辑 /app/models/user.rb
class User < ActiveRecord::Base
has_many :document
end
|
现在,您已经详细理解了前面几个迁移步骤,接下来我们执行剩下的迁移,以创建所需的其他表和关联。
步骤 4:管理逐渐增长的共享文档集合
如果有一种方式来对社区所贡献的大量文档进行分类,以便添加基于内容主题的分类学,那么显然很有帮助。SUBJECTS 表将被创建,以帮助对文档进行分类。一个主题可以包含很多文档,但是每个文档只能属于一个主题。为了演示 SUBJECTS 与 DOCUMENTS 之间的这种一对多的关系,需要将一个 Subject ID 外键添加到 DOCUMENTS 中。
下面是执行这些任务所需的步骤:
a) 运行 ruby script/generate migration create_subjects_table,这将创建 db/migrate/004_create_subjects_table.rb 文件。
b) 像下面这样编辑 db/migrate/004_create_subjects_table.rb:
清单 19. 创建 SUBJECTS 表
class CreateSubjectsTable < ActiveRecord::Migration
def self.up
create_table :subjects do |t|
t.column :name, :string, :limit => 20
t.column :size, :integer
t.column :description, :text
t.column :tag, :string, :limit => 10
end
add_column :documents, :subject_id, :integer
end
def self.down
drop_table :subjects
remove_column :documents, :subject_id
end
end
|
c) 运行 rake db:migrate,以创建 SUBJECTS 表并添加 subject_id 外键。
d) 运行 ruby script/generate scaffold subject,为 SUBJECTS 表生成 scaffold。
e) 添加 has_many :document 关联到新生成的 /app/models/subject.rb 文件中。
清单 20. 添加关联到 subject 模型中
class Subject < ActiveRecord::Base
has_many :document
end
|
f) 再添加一个关联 belongs_to :subject 到步骤 3(d) 中生成的 /app/models/document.rb 文件中。
清单 21. 添加关联到文档模型中
class Document < ActiveRecord::Base
belongs_to :user
belongs_to :subject
<... code to assist with document uploading ...>
<... ...>
end
end
|
步骤 5:
管理用户通知需要
当一个新文档被上传到一个特定类别时,如果有一种方式来通知用户,那么会怎样呢?如果可以在一个 SUBSCRIPTIONS 表中收集那些对通知的请求,就很容易实现这一点。这里还需要考虑用户、主题和订阅之间的一些关系。
我们首先来描述用例场景:用户 Anthony 想在关于某些主题:编程、Linux 和钓鱼的新文档被上传时收到通知。他将浏览至一个显示目前已创建的所有主题的页面,然后勾选与上述主题相关的复选框。然后,每当关于这三个主题中任何一个主题的新文档被上传时,应用程序将生成一个通知消息,该消息通过电子邮件被发送到订阅了这些主题的用户。整个场景可以表达为以下一组关系:
- 每个用户最多只能有一项订阅,但订阅不是必需的(例如,one-to-none、one... 关系)。
- 需要将一个外键 user_id 添加到 SUBSCRIPTIONS 表中。
- 用户与订阅模型之间需要包括一个一对一关系。
- 每项用户订阅可以包括一些偏好的主题(例如一对多关系)。用户可以通过某种方式从所有可用的主题中进行选择。
- 需要将一个外键 subscription_id 添加到 SUBJECTS 表中。
- 订阅与主题模型之间需要包括一个一对多关系。
通过与前面类似的迁移步骤,可以达到这些数据库设计目标:
a) 运行 ruby script/generate migration create_subscriptions_table 将创建 db/migrate/005_create_subscriptions_table.rb 文件。
b) 编辑 db/migrate/005_create_subscriptions_table.rb:
清单 22. 创建 SUBSCRIPTIONS 表
class CreateSubscriptionsTable < ActiveRecord::Migration
def self.up
create_table :subscriptions do |t|
t.column :name, :string, :limit => 20
t.column :description, :text
t.column :user_id, :integer
end
add_column :subjects, :subscription_id, :integer
end
def self.down
drop_table :subscriptions
remove_column :subjects, :subscription_id
end
end
|
c) 运行 rake db:migrate 以创建 SUBSCRIPTIONS 表并将外键列添加到 SUBJECTS 中。
d) 运行 ruby script/generate scaffold subscription 为 SUBSCRIPTIONS 生成一个 scaffold。
e) 将 has_many :subject 关联添加到步骤 4 d 生成的 /app/models/subscription.rb 文件中。
f) 将相应的关联 belongs_to :subscription 添加到新生成的 /app/models/subject.rb 文件中。
g) 将 has_one :subscription 关联添加到步骤 3 f 生成的 /app/models/user.rb 文件中。
h) 将 belongs_to :user 关联添加到新生成的 /app/models/subscription.rb 文件中。
 | |
注 17:
细心的读者肯定会发现,目前的订阅设计并不完全实际或者可用:一个主题只能属于一项订阅,因此一旦 Anthony 在他的订阅页面选中了一个主题,那么 Bob 就不能再选择这个主题。显然,那应该是多对多的关系,不过现在这样做可以简化问题(例如,现在没有中间连接表)。这个问题在本系列的第 2 部分肯定会得到修复。
|
|
步骤 6:管理用户联系方式
将关于 Team Room 更新的消息通知给用户的一种方法是通过电子邮件。为此,需要添加每个用户的电子邮箱,因此通过另一个迁移将一个 email 列添加到 USERS 表中:
a) 运行 ruby script/generate migration addEmailToUser。这将生成 db/migrate/006_add_email_to_user.rb 文件。
b) 编辑 006_add_email_to_user.rb。
清单 23. 添加 email 信息到 USERS 表中
class AddEmailToUser < ActiveRecord::Migration
def self.up
add_column :users, :email, :string, :limit => 30
end
def self.down
remove_column :users, :email
end
end
|
c) 运行 rake db:migrate,将 email 列添加到 USERS 表中。
下面的图演示了步骤 1 到步骤 6 所执行的操作。
图 3. 不同模型之间的关联
步骤 7:将 XML 文档存储在 Team Room 中
在如今的企业环境中,越来越频繁地需要以 XML 格式存储文档,而这种上乘的结构化文档并不总是被大大有别于一些无定形的二进制对象来对待。在这个应用程序中,我们大胆尝试一些新鲜事物,以 DOCUMENTS 表中已经定义的 BLOB 之外的数据类型存储 XML 文档。
如果使用 DB2 9 数据服务器,并利用本地 XML 数据持久性,则可以按分层格式存储格式良好的 XML 文档。在 DB2 9 中,XML 列被声明为 XML 数据类型,这正是我们将在新的迁移中要使用的。
a) 运行 ruby script/generate migration add_xml_doc_column。
这将生成 db/migrate/007_add_xml_doc_column.rb。
b) 编辑 007_add_xml_doc_column.rb:
清单 24. 添加 XML 列到 DOCUMENTS 中
class AddXmlDocColumn < ActiveRecord::Migration
def self.up
add_column :documents, :xmldata, :xml
end
# Currently, a column that is part of a table containing an XML column
# cannot be dropped. To remove the column, the table must be dropped
# and recreated without the previous XML column.
def self.down
drop_table :documents
create_table :documents do |t|
t.column :name, :string, :null => false
t.column :size, :integer, :null => false
t.column :data, :binary, :limit => 2.megabytes
t.column :content_type, :string, :null => false
t.column :created_at, :timestamp
t.column :updated_at, :timestamp
t.column :platform, :string, :limit => 10
t.column :user_id, :integer
t.column :subject_id, :integer
end
end
end
|
c) 运行 rake db:migrate,将新的列添加到 DOCUMENTS 表中。
虽然 add_column 看上去有点熟悉,但是您可能会惊奇地发现需要删除整个表,然后重新创建表,而不是使用 remove_column。考虑到 XML 作为本地数据类型给表带来的重大变化,这应该不奇怪。在 XML 列移除被支持之前,用户可能感觉到这一点非常不方便,不过在本系列接下来的部分中,我们将给出另一个数据库模式设计,以及本地 XML 数据类型支持所带来的重要优点。但是首先,我们还是体验一下之前考虑的 “无定形” blob 数据。
d) 分析收集并格式化为 XML 文档的市场数据。
我们试着从 Team Room 中发布的一个文档中提取客户的邮政编码信息,以便确定收集到的市场数据中提供的所有多伦多地区的客户。
当浏览至 http://localhost:3000/ 时,使用文档 scaffold,并上传以示例代码提供的 teamroom/test/fixtures/CAN-Central.xml 文档。然后,将以下动作添加到 documents_controller.rb 中:
清单 25. 分析 XML 格式的市场数据
def zips
@id = params[:id]
@xmldata = Document.find_by_sql "select xmlquery(\
'<zipcodes>\
{for $i in $t/marketinfo/sales/customer/address\
where $i/city = \"Toronto\"\
return <zip>{$i/zip/text()}</zip>} \
</zipcodes>'\
passing c.xmldata as \"t\")\
from documents c where id = #{@id}"
p @xmldata[0]
redirect_to :action => 'list'
end
|
对于这个应用程序当前的阶段,这个复杂的 SQL 和嵌入式 XQuery 的结果只能打印在服务器控制台中,后面紧接之前上传的 CAN-Central.xml 文档的 zips 链接。这个结果将包含与 CAN-Central.xml 文档中存在的多伦多客户相关的邮政编码地区列表。乍看之下,嵌入到 SQL 语句中的 XQuery 可能有些复杂,但是在大多数情况下通过 XPath 查询表达式可以简化之:
清单 26. 从 XML 市场数据中发现多伦多市的邮政编码
Document.find_by_sql "select xmlquery('<zipcodes>\
{$t/marketinfo/sales/customer/address/zip[../city = \"Toronto\"]}\
</zipcodes>' passing c.xmldata as \"t\")\
from documents c where id = #{@id}"
|
在本系列接下来的部分中,我们将发现更好的方法来通过 SQL 嵌入式 XQuery 和 XPath 表达式简化 Rails 应用程序交互。暂时可以保证的是,您不再需要为了查看一个大型 XML 文档的一个小片段而检索其所有内容。这个工作可以在 DB2 本地 XML 数据存储中完成,同时还可以利用 DB2 引擎优化器。
图 4 显示了在步骤 7 结束时 Team Room 数据库对象应有的样子:
图 4. Team Room 数据库模式图
通过迁移回滚更改
最后,我们已经完成了任务。但是,如果现在要撤消所有更改,那么该怎么办呢?这很简单,只需运行 rake db:migrate VERSION=number
,其中 number
表示要回滚到的版本。例如,如果要销毁我们在 XMLDB 数据库中为 Rails Team Room 项目创建的表,并撤消到目前为止做出的所有更改,那么可以发出
rake db:migrate VERSION=0。Rails 按顺序逆转每个迁移步骤,首先回滚最近的迁移,最终将数据库回滚到版本 0。
清单 27. 通过迁移逆转所有更改
D:\rails\teamroom>rake db:migrate VERSION=0
(in D:/rails/teamroom)
== AddXmlDocColumn: reverting =================================================
-- drop_table(:documents)
-> 0.0150s
-- create_table(:documents)
-> 0.1880s
== AddXmlDocColumn: reverted (0.2030s) ========================================
== AddEmailToUser: reverting ==================================================
-- remove_column(:users, :email)
-> 0.1250s
== AddEmailToUser: reverted (0.1250s) =========================================
== CreateSubscriptionsTable: reverting ========================================
-- drop_table(:subscriptions)
-> 0.0000s
-- remove_column(:subjects, :subscription_id)
-> 0.1560s
== CreateSubscriptionsTable: reverted (0.1560s) ===============================
== CreateSubjectsTable: reverting =============================================
-- drop_table(:subjects)
-> 0.0000s
-- remove_column(:documents, :subject_id)
-> 0.1570s
== CreateSubjectsTable: reverted (0.1570s) ====================================
== CreateUsersTable: reverting ================================================
-- drop_table(:users)
-> 0.0000s
-- remove_column(:documents, :user_id)
-> 0.1400s
== CreateUsersTable: reverted (0.1400s) =======================================
== AddDocsAttributes: reverting ===============================================
-- remove_column(:documents, :created_at)
-> 0.1250s
-- remove_column(:documents, :updated_at)
-> 0.1870s
-- remove_column(:documents, :platform)
-> 0.1260s
== AddDocsAttributes: reverted (0.4380s) ======================================
== CreateDocsStore: reverting =================================================
-- drop_table(:documents)
-> 0.0000s
== CreateDocsStore: reverted (0.0000s) ======================================== |
结束语
我们介绍了如何使用 IBM_DB 适配器/驱动程序和 DB2 开始 Rails 应用程序开发。Starter Toolkit for DB2 on Rails
为那些不熟悉 DB2 和 Ruby on Rails 的人提供了最容易的方法,但是对于现有的 DB2 开发人员,IBM_DB 适配器和驱动程序 gem 或插件将提供同样容易的入门点。
我们还详细讨论了 Rails 迁移。
管理数据库模式的更改是件乏味的事情。Rails 迁移特性可以帮助应用程序开发人员管理这种模式演变,便于在应用程序代码与数据库对象之间同步更改。通过利用 Rails 迁移和在 Ruby 文件中定义数据库模式更改,可以以逻辑的方式对更改进行版本控制。而且,由于 Rails 的 ActiveRecord 是独立于供应商的,开发人员只需创建一个 Ruby 迁移文件,就可以在多个使用不同 ActiveRecord 适配器的数据库平台上处理相同的更改。
请继续关注 Ruby on Rails 系列的第 2 部分,在第 2 部分中我们将展示如何将 DB2 on Rails 和 XML 支持提高到新的水平,进一步介绍通过 Ruby on Rails 操作 XML。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Team room 样例代码 | teamroom1.zip | 10KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
作者简介  | |  | John Chun 是 DB2 Advanced Support 小组从事应用程序开发和工具领域的专家。他在 IBM DBT 多伦多实验室工作了 7 年,负责解决涉及多种不同语言,例如 Java、C、C++、Perl、REXX、C# 等的 DB2 应用程序的问题。John 从事过很多涉及 DB2 CLI 和 OLEDB 驱动程序以及 .NET data provider 的项目。John 是一名 DB2 认证的解决方案专家和认证的 Websphere 管理员。 |
 | |  | Alex Pitigoi 是 IBM 多伦多实验室的一名高级软件工程师。从 1998 年开始,他就从事 Information Management 中的各种软件开发项目,主要集中于 Web 技术和数据库管理。最近,他参加了 SQLModel 项目的开发,该项目现已被并入 Eclipse Data Tools Project,此外,他还参加了跨多个 IBM 数据服务器的数据库管理 Web Tools 的总体架构的开发。Alex 还从事过 DB2 Satellite Administration Center、IBM Express Runtime 方面的工作,并领导了为 DB2 发布的第一套 Web Tools。目前,他的工作重点是使 IBM 的数据服务器支持新的开放源代码技术(Ruby、Python、PHP)。 |
 | |  | Naomi Ngan 在 2000 年获得了加拿大多伦多大学计算机科学和统计学的荣誉学位。毕业时,她加入了
IBM,负责解决应用程序开发环境中 IBM DB2 RDBMS 产品的缺陷和问题。在 IBM 呆了大约 4 年后,她进入位于加利福尼亚大学旧金山分校(UCSF)的 Ernest Gallo Clinic and Research Center,负责开发 Bioinformatics 软件。其工作包括设计和开发 Linux 和 Windows 平台上 XML 环境中的数据库对象、JSP、Java 独立应用程序和存储过程。目前,她是 Autonomy
Corporation 的高级软件工程师,从事 J2EE 企业软件的开发。她在 DB2 应用程序开发和工具开发方面有着深厚的知识,并且拥有 DB2、XML、WebSphere 等领域的多个 IBM 和 Sun 开发人员认证。 |
 | |  | Christine Law 是 IBM 多伦多实验室的一名高级 DB2 专家,也是 IBM 认证的专家。她在 IBM 多伦多实验室负责解决 DB2 应用程序的问题和缺陷。她拥有丰富的 Linux、UNIX 和 Windows 平台上的应用程序开发经验,熟悉不同的编程语言和脚本编制语言,尤其擅长 JDBC、SQLJ、存储过程和嵌入式 SQL。最近,她的兴趣包括诸如 AJAX 和 Ruby 之类的开放源代码技术。 |
对本文的评价
|