赶在2009年SLR 停产之前买一辆

DB2 与 Ruby on Rails 入门

上一篇 / 下一篇  2007-12-05 11:38:37 / 个人分类:IBM DB2

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 运行时环境:

将 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 数据服务器。

将 IBM_DB 适配器和驱动程序安装为 Ruby Gem

  1. 发出gem命令,安装 IBM_DB 适配器和驱动程序:

    D:\>gem install ibm_db

  2. 将 ‘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 适配器和驱动程序还有一些额外的注意事项。

  1. IBM Driver for ODBC 和 CLI 的安装必需满足 IBM_DB 需求。

    可以通过完全安装 DB2 数据库获得 IBM Driver for ODBC 和 CLI,也可以从 “IBM DB2 Driver for ODBC and CLI overview” 中单独获得这两个组件。

  2. 可以使用 CLI 关键词在任何 Ruby 应用程序之外修改驱动程序行为。

    可以使用一些 CLI 关键词在 Rails 应用程序之外修改某些事务行为。例如,可以使用它们来设置当前模式或者修改一些事务元素,例如关闭自动提交行为。关于 CLI 关键词的细节可以参阅以下 DB2 Info Center 文档:

    Version 8

    Version 9

  3. 诊断信息的收集需要 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 文档内置层次结构的一些好方法。

  1. 首先,创建一个名为 “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 项目目录下的相对路径。

  2. 如果已经有一个 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

  3. 现在编辑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 连接属性
连接属性描述是否必需
AdapterRuby 适配器名称,对于 DB2 为 ‘ibm_db’
DatabaseRails 项目所连接到的数据库
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

Team Room 例子

现在可以开始执行实际的迁移步骤了。

步骤 1:创建 Documents 表

首先需要从一个存储区开始,使所有文档和媒体文件在这个托管应用程序的用户之间共享。因此,首先创建 DOCUMENTS 表,用于存储小组成员要共享的所有媒体文件。

我们定义以下列来描述共享文件的内容:




表 2. DOCUMENTS 表列和描述
列名数据类型描述
IDInteger主键
NameVARCHAR文档名称
SizeInteger文件大小
DataBLOB以二进制模式存储的文件,最大 2 M
Content_typeVARCHAR文档类型,包括 .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



在 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.



还需注意,在这个迁移期间,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 表中的列和描述
列名数据类型描述
IDInteger主键
NameVARCHAR文档的名称
SizeInteger文件大小
DataBLOB以二进制模式存储的文件,最大 2 M
Content_typeVARCHAR文档类型,包括:.doc、.ppt、.pdf、.sw、.exe、.a、.so、.gif、.jpeg、.mov、.avi、.mpeg、.bmp 等
Created_atTIMESTAMP文件被上传的时间(见注 9
Updated_atTIMESTAMP文档最近更新时间戳(见注 9
PlatformVARCHAR特定于文件平台的信息


然后生成第二个迁移,以便将这些属性添加到 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



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



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) ======================================



步骤 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



我们在 /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 :userbelongs_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 文件中。



步骤 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。


TAG: ibm db2

引用 删除 Guest   /   2008-05-20 01:21:37
1
我心飞扬的ZZ 引用 删除 izhonglin   /   2008-05-05 13:23:59
引用 删除 Guest   /   2008-04-26 20:52:19
这网页看的我头晕眼花的。
 

评分:0

我来说两句

显示全部

:loveliness: :handshake :victory: :funk: :time: :kiss: :call: :hug: :lol :'( :Q :L ;P :$ :P :o :@ :D :( :)

Open Toolbar