10.4 连接到外部数据库
Ruby可连接到各种数据库——从Oracle等大型系统到小型的MySQL,这要感谢很多人的开发工作。出于完整性考虑,这里也包括CSV。
这些包提供的功能将不断变化,务必参阅包含最新信息的在线参考手册,Ruby Application Archive是个不错的起点。
10.4.1 连接到SQLite
对于欣赏零配置软件的人来说,SQLite是个受欢迎的数据库。它是一个自包含的小型可执行文件,是用C语言编写的,能够在单个文件中处理整个数据库。虽然它通常用于小型数据库,但也能够处理几个TB(terabyte)的数据。
Ruby的SQLite绑定比较简单。SQLite::API类封装了C API,由于封装基本上是一对一的且面向对象程度不高,因此仅当绝对必要时才应直接使用该API。
在大多数情况下,SQLite::Database类就能满足要求,下面是一段简短的示例代码:
require 'sqlite'
db = SQLite::Database.new("library.db")
db.execute("select title,author from books") do |row|
p row
end
db.close
# Output:
# ["The Case for Mars", "Robert Zubrin"]
# ["Democracy in America", "Alexis de Tocqueville"]
# ...
如果没有指定代码块,execute将返回一个ResultSet实例(实际上是一个可迭代的游标)。
rs = db.execute("select title,author from books")
rs.each {|row| p row } # Same results as before
rs.close
如果返回一个ResultSet,将由客户端负责关闭它(如上面的示例代码所示)。如果需要遍历记录列表多次,可调用reset方法以重新开始(这项功能还处于试验阶段,可能发生变化)。另外,如果愿意,也可使用next和eof?执行生成器风格的迭代(外部迭代)。
rs = db.execute("select title,author from books")
while ! rs.eof?
rec = rs.next
p rec # Same results as before
end
rs.close
这个库的方法可能引发大量异常,所有这些异常都是SQLite::Exception的子类,因此很容易捕捉任何异常或所有异常。
有必要简单提一下的是,这个库能够同Ara Howard开发的ArrayFields库(这里没有介绍)进行互操作。它让数组元素可以通过名称和整数进行索引或访问。如果在sqlite库前请求(require)了arrayfields库,则可通过名称或数字来索引ResultSet对象(然而,也将其配置为返回散列)。
虽然sqlite库的功能相当完善,但它可能没有提供程序员所需的所有功能,因为SQLite本身还没完全实现SQL92标准。有关SQLite或其Ruby绑定的更详细信息,请在网上搜索。
10.4.2 连接到MySQL
MySQL接口是Ruby中最稳定、功能最齐全的数据库接口之一,它是个扩展,必须在Ruby和MySQL都已安装并正常运行后安装。
安装它后,使用该模块包含三个步骤:首先在脚本中加载该模块,然后连接数据库,最后处理表。连接时需要提供主机、用户名、密码和数据库等常见参数。
require 'mysql'
m = Mysql.new("localhost","ruby","secret","maillist")
r = m.query("SELECT * FROM people ORDER BY name")
r.each_hash do |f|
print "#{f['name']} - #{f['email']}"
end
# Output looks like:
# John Doe - jdoe@rubynewbie.com
# Fred Smith - smithf@rubyexpert.com
类方法Mysql.new和MysqlRes.each_hash及实例方法query都很有用。
正如README文件描述的,该模块由4个类(Mysql、MysqlRes、MysqlField和MysqlError)组成。这里将总结一些有用的方法,但读者可通过文档找到更详细的信息。
类方法Mysql.new接受多个字符串作为参数(它们的默认值都是nil),并返回一个连接对象。参数分别是host、user、passwd、db、port、sock和flag。new的别名包括real_connect和connect。
方法create_db、select_db和drop_db都接受一个数据库名作为参数,它们的用法如下所示。方法close关闭到服务器的连接。
m=Mysql.new("localhost","ruby","secret")
m.create_db("rtest") # Create a new database
m.select_db("rtest2") # Select a different database
m.drop_db("rtest") # Delete a database
m.close # Close the connection
在最新的版本中,create_db和drop_db已被摒弃,但通过如下方式定义它们可使其仍管用:
class Mysql
def create_db(db)
query("CREATE DATABASE #{db}")
end
def drop_db(db)
query("DROP DATABASE #{db}")
end
end
方法list_dbs以数组形式返回一个可用数据库名列表。
dbs = m.list_dbs # ["people","places","things"]
query接受一个字符串作为参数,并默认返回一个MysqlRes对象。它也可能返回一个Mysql对象,这取决于query_with_result是如何设置的。
发现错误时,可通过errno获取错误号;而error返回实际的错误消息。
begin
r=m.query("create table rtable
(
id int not null auto_increment,
name varchar(35) not null,
desc varchar(128) not null,
unique id(id)
)")
# exception happens...
rescue
puts m.error
# Prints: You have an error in your SQL syntax
# near 'desc varchar(128) not null ,
# unique id(id)
# )' at line 5"
puts m.errno
# Prints 1064
# ('desc' is reserved for descending order)
end
下面总结了MysqlRes的一些有用的实例方法:
· fetch_fields:以MysqlField对象数组的方式返回下一行。
· fetch_row:以字段值数组的方式返回下一行。
· fetch_hash(with_table=false):返回一个散列,其中包含下一行的字段名和值。
· num_rows:返回结果集中的行数。
· each:顺序返回字段值数组的迭代器。
· each_hash(with_table=false):顺序返回散列{fieldname => fieldvalue}的迭代器(使用x[‘field name’]可获取字段值)。
MysqlField也有一些实例方法:
· name:返回指定字段的名称。
· table:返回指定字段所属表的名称。
· length:返回字段的定义长度。
· max_length:返回结果集中最长字段的长度。
· hash:返回一个散列,其中包含name、table、def、type、length、max_length、flags和decimals的名称和值。
请以在线文档为准,更详细的信息,请访问MySQL网站(http://www.mysql.com)和Ruby Application Archive。
10.4.3 连接到PostgreSQL
RAA提供了一个扩展,可用于访问PostgreSQL(支持PostgreSQL 6.5/7.0)。
假如已安装和配置好PostgreSQL(并创建了一个名为testdb的表),则只需遵循在Ruby中使用其他数据库接口的步骤:加载模块,连接数据库,然后处理表。可能需要执行查询、获取选定的结果以及处理事务。
require 'postgres'
conn = PGconn.connect("",5432, "", "", "testdb")
conn.exec("create table rtest ( number integer default 0 );")
conn.exec("insert into rtest values ( 99 )")
res = conn.query("select * from rtest")
# res id [["99"]]
PGconn类包含connect方法,该方法接受典型的数据库连接参数,如主机、端口、数据库、用户名和登录密码,但它还通过第三和第四参数接受选项和tty参数。在上面的例子中,已经以特权用户的身份连接到了UNIX套接字,因此不需要用户名和密码,且主机、选项和tty都为空。端口必须为整数,其他参数都是字符串。它的一个别名是方法new。
接下来需要处理表,这需要采取某种方式执行查询,为此可使用实例方法PGconn#exec和PGconn#query。
exec方法将其字符串参数作为SQL查询请求发送给PostgreSQL,如果成功则返回一个PGresult实例,否则引发PGError异常。
query方法也将其字符串参数作为SQL查询请求发送给PostgreSQL。然而,它在成功时返回一个数组。返回的数组实际上是个元组数组。如果失败,它返回nil,而错误细节可以通过调用方法error获取。
有个特殊方法可用于将值插入到指定表中,这就是insert_table。虽然名为insert_table,但它的实际意思是“插入到表中”(insert into table)。该方法返回一个PGconn对象。
conn.insert_table("rtest",[[34]])
res = conn.query("select * from rtest")
# res is [["99"], ["34"]]
上述代码将一行值插入到rtest表中。在这个简单的例子中,表开始只有一列。注意,PGResult对象res表明,更新后的结果包含两个元组。PGresult的方法将稍后讨论。
在PGconn类中,其他可能很有用的方法包括:
· db:返回连接的数据库名。
· host:返回连接的服务器名。
· user:返回通过认证的用户名。
· error:返回有关连接的错误消息。
· finish、close:关闭后端连接。
· loimport(file):将文件导入到大型对象中。如果成功,返回PGlarge实例;如果失败,则引发PGError异常。
· loexport(oid, file):将大型对象oid保存到文件中。
· locreate([mode]):如果成功则返回PGlarge实例,如果失败将引发PGError异常。
· loopen(oid, [mode]):打开大型对象oid,如果成功则返回PGlarge实例。mode参数指定打开大型对象的模式,取值为INV_READ或INV_WRITE(如果省略,默认为INV_READ)。
· lounlink(oid):将Postgres大型对象oid解除链接(删除)。
注意,最后5个方法(loimport、loexport、locreate、loopen和lounlink)涉及PGlarge类的对象。PGlarge类有专用于访问和修改其对象的方法(这些对象是PGconn的实例方法loimport、locreate和loopen等创建的)。
下面是PGlarge的方法:
· open([mode]):打开一个大型对象,mode参数指定模式(与前面介绍的PGconn#loopen相同)。
· close:关闭大型对象(当成无用单元被收集时也将关闭)。
· read([length]):从大型对象中读取length个字节,如果没有指定length,则读取所有数据。
· write(str):将字符串写入大型对象,并返回写入的字节数。
· tell:返回指针的当前位置。
· seek(offset, whence):将指针移到offset指定的位置,参数whence的可能取值为SEEK_SET、SEEK_CUR或SEEK_END(0、1、2)。
· unlink:删除大型对象。
· oid:返回大型对象的oid。
· size:返回大型对象的大小。
· export(file):将大型对象oid保存到文件中。
我们最感兴趣的是PGresult的实例方法(如下面的列表所示),PGresult对象是由查询创建的(使用完这些对象后,使用PGresult#clear释放它们占用的内容)。
· result:返回数组中的查询结果元组。
· each:迭代器。
· []:存取器。
· fields:返回由查询结果中的字段组成的数组。
· num_tuples:返回查询结果中的元组数。
· fieldnum(name):返回命名字段的索引。
· type(index):返回与字段类型对应的整数。
· size(index):返回字段的大小,以字节为单位;–1表示字段的长度可变。
· getvalue(tup_num, field_num):返回参数指定的字段的值,其中tup_num相当于行号。
· getlength(tup_num, field_num):返回字段长度,单位为字节。
· cmdstatus:返回最后一条查询命令的状态字符串。
· clear:删除PGresult对象。
10.4.4 连接到LDAP
至少有三个用于Ruby的LDAP库。Takaaki Tateishi开发的Ruby/LDAP库是个相当“瘦”的封装类。如果读者是LDAP专家,则这个库足以满足需求;如果不是专家,可能觉得它有点复杂。下面是一个例子:
conn = LDAP::Conn.new("rsads02.foo.com")
conn.bind("CN=username,CN=Users,DC=foo,DC=com","password") do |bound|
bound.search("DC=foo,DC=com", LDAP::LDAP_SCOPE_SUBTREE,
"(&(name=*) (objectCategory=person))", ['name','ipPhone']) do |user|
puts "#{user['name']} #{user['ipPhone']}"
end
end
ActiveLDAP遵循了ActiveRecord的设计模式,下面是一个使用示例,摘自其主页:
require 'activeldap'
require 'examples/objects/user'
require 'password'
# Instantiate Ruby/ActiveLDAP connection, etc
ActiveLDAP::Base.connect(:password_block => Proc.new { Password.get('Password: ') },
:allow_anonymous => false)
# Load a user record (Class defined in the examples)
wad = User.new('wad')
# Print the common name
p wad.cn
# Change the common name
wad.cn = "Will"
# Save it back to LDAP
wad.write
还有一个更新的由Francis Cianfrocca开发的库,有些人更喜欢这个库。下面是其用法示例:
require 'net/ldap'
ldap = Net::LDAP.new :host => server_ip_address,
:port => 389,
:auth => {
:method => :simple,
:username => "cn=manager,dc=example,dc=com",
:password => "opensesame"
}
filter = Net::LDAP::Filter.eq( "cn", "George*" )
treebase = "dc=example,dc=com"
ldap.search( :base => treebase, :filter => filter ) do |entry|
puts "DN: #{entry.dn}"
entry.each do |attribute, values|
puts " #{attribute}:"
values.each do |value|
puts " --->#{value}"
end
end
end
p ldap.get_operation_result
这些库哪个更好见仁见智,建议读者自己研究它们以形成自己的观点。
10.4.5 连接到Oracle
Oracle是世界上功能最强大、最流行的数据库系统之一,自然有很多人想用Ruby连接该数据库。当前最好的库是Kubo Takehiro开发的OCI8。
虽然名为OCI8,它实际上也可用于Oracle 8以后的版本。然而,由于它还不是很成熟,可能不支持较新版本的有些新特性。
OCI8的API被划分为一个“瘦”封装类(严格遵守Oracle Call Interface API的低级API)和一个高级API。在大多数情况下,程序员只需使用高级API,将来可能不再提供有关低级API的文档。
OCI8模块包含Cursor和Blob类。OCIException类是在数据库操作中可能引发的三种异常类——OCIError、OCIBreak和OCIInvalidHandle的超类。
使用OCI8.new连接到数据库,至少要传递用户名和密码,该方法将返回一个可用于执行查询的句柄。下面是一个例子:
require 'oci8'
session = OCI8.new('user', 'password')
query = "SELECT TO_CHAR(SYSDATE, 'YYYY/MM/DD') FROM DUAL"
cursor = session.exec(query)
result = cursor.fetch # Only one iteration in this case
cursor.close
session.logoff
上面的例子对一个游标进行操作,这里只对游标执行了一次fetch,然后关闭它。当然,也可检索多行:
query = 'select * from some_table'
cursor = session.exec(query)
while row = cursor.fetch
puts row.join(",")
end
cursor.close
# Or with a block:
nrows = session.exec(query) do |row|
puts row.join(",")
end
查询中绑定变量看起来“像”符号。要将绑定变量和实际值相关联,有多种方式。
session = OCI8.new("user","password")
query = "select * from people where name = :name"
# One way...
session.exec(query,'John Smith')
# Another...
cursor = session.parse(query)
cursor.exec('John Smith')
# And another...
cursor = session.parse(query)
cursor.bind_param(':name','John Smith') # bind by name
cursor.exec
# And another.
cursor = session.parse(query)
cursor.bind_param(1,'John Smith') # bind by position
cursor.exec
对于喜欢使用DBI的人来说,有一个DBI适配器,更详细的信息请参阅OCI8的文档。
10.4.6 使用DBI封装类
从理论上说,DBI使得能够以独立于数据库的方式访问数据库,即不管底层的数据库是Oracle、MySQL、PostgreSQL还是其他数据库,都可使用相同的代码来访问。通常只需要修改一行代码,即指定要使用哪种适配器的代码。虽然有时候DBI无法执行复杂的或数据库特定的操作,但对大多数日常工作来说,这是一个很方便的工具。
假设有一个Oracle数据库,并要使用OCI8自带的驱动程序或适配器来访问它,只需将足够的信息传递给connect方法,使其能够成功地连接到数据库,以后的所有操作都很简单。
require "dbi"
db = DBI.connect("dbi:OCI8:mydb", "user", "password")
query = "select * from people"
stmt = db.prepare(query)
stmt.execute
while row = stmt.fetch do
puts row.join(",")
end
stmt.finish
db.disconnect
在上面的例子中,可认为prepare方法编译或解析查询,然后查询被执行。fetch方法从结果集中检索一行,如果没有更多的行则返回nil(因此在上面的代码中使用了while循环)。可将finish方法视为关闭或释放连接的操作。
有关所有DBI特性的完整介绍,请参阅参考手册。有关完整的数据库驱动程序列表,请参阅RubyForge和RAA等。
10.4.7 对象关系映射器(Object-Relational Mapper,ORM)
传统的关系数据库擅长处理关系型数据,它以高效方式处理查询,而不需要预先知道即席查询的特征,但这种模型的面向对象程度不高。
这两种模型(RDBMS和OOP)都无处不在,但它们之间存在阻抗不匹配(impedance mismatch),很多人试图在这种差异之间搭建桥梁,实现这种目标的软件称为对象关系映射器(ORM)。
这种问题的解决方式很多,每种方式都有其优点和缺点。下面简要地介绍两种流行的ORM:ActiveRecord和Og(后者表示object graph(对象图))。
用于Ruby的ActiveRecord库是以Martin Fowler开发的设计模式ActiveRecord命名的。从本质上说,它将数据库表和类管理起来,这样无序使用SQL就能够直观地对数据进行操作。具体地说,它“封装数据库表或视图中的行,封装数据库访问,并添加有关这些数据的域逻辑”(参见Martin Fowler编著的《Patterns of Enterprise Application Architecture》,Addison Wesley,2003)。
通过继承ActiveRecord::Base,并对类进行定制,以描述每个表。和DBI一样,连接时必须提供足够的信息,以便能够识别并连接到数据库。下面这个简单的例子说明了其工作原理:
require 'active_record'
ActiveRecord::Base.establish_connection(:adapter => "oci8",
:username => "username",
:password => "password",
:database => "mydb",
:host => "myhost")
class SomeTable < ActiveRecord::Base
set_table_name "test_table"
set_primary_key "some_id"
end
SomeTable.find(:all).each do |rec|
# process rec as needed...
end
item = SomeTable.new
item.id = 1001
item.some_column = "test"
item.save
该API功能丰富而复杂,建议读者研究网上或书籍中的教程。这个库是Ruby on Rails不可分割的一部分,介绍Ruby on Rails的资料都将介绍到它。
Og不同于ActiveRecord的地方是,后者的面向数据库程度高,而前者的面向对象程度高。Og能够根据Ruby类的定义生成数据库架构(但不能反过来)。
Og的思维方式不同,其应用没有ActiveRecord广泛。但作者认为,它包含一些有趣的特性,是一个功能强大的ORM,尤其是在根据对象设计数据库时。
要定义要存储的类,可使用property方法,它与attr_accessor类似,但需要指定相关联的类型(类)。
class SomeClass
property :alpha, String
property :beta, String
property :gamma, String
end
其他数据类型包括Integer、Float、Time和Date等,属性可以是任何Ruby对象。
连接到数据库的方式与使用ActiveRecord或DBI时相同。
db = Og::Database.new(:destroy => false,
:name => 'mydb',
:store => :mysql,
:user => 'hal9000',
:password => 'chandra')
每个对象都有save方法,它实际执行将对象插入到数据库中的操作:
obj = SomeClass.new
obj.alpha = "Poole"
obj.beta = "Whitehead"
obj.gamma = "Kaminski"
obj.save
还有一些用传统数据库描述对象关系的方法:
class Dog
has_one :house
belongs_to :owner
has_many :fleas
end
这些方法及其他方法(如many_to_many和refers_to)可帮助创建复杂的对象—表关系。
Og很大,这里无法完整介绍,更详细的信息请参阅其他在线资源,如http://oxyliquit.de。







