在如今的企业环境中,越来越频繁地需要以 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
:
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
中:
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 查询表达式可以简化之:
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 数据库对象应有的样子:
最后,我们已经完成了任务。但是,如果现在要撤消所有更改,那么该怎么办呢?这很简单,只需运行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) ======================================== |