首页 » MongoDB实战 » MongoDB实战全文在线阅读

《MongoDB实战》3.1 通过Ruby使用MongoDB

关灯直达底部

一般在想到驱动时,映入脑海的都是低级的位操作和迟钝的接口。感谢上帝,MongoDB的语言驱动和这一点儿都不沾边,API反而设计得很直观、很对语言的胃口,因此很多应用程序索性直接把MongoDB驱动作为与数据库通信的唯一接口。驱动API在不同语言之间保持着相当的一致性,这意味着,如果需要,开发者可以轻松地在语言之间进行切换。如果你是一位应用程序开发者,会发现在使用任何MongoDB驱动时都感觉良好且生产率很高,不用自己操心底层的实现细节。

本节将带你安装MongoDB Ruby驱动,连接数据库,了解如何执行基本的CRUD操作。这将为本章最后要构建的应用程序打下基础。

3.1.1 安装与连接

我们可以使用RubyGems安装MongoDB Ruby驱动,RubyGems是Ruby的包管理系统。

注意 如果还没在系统上安装Ruby,可以在找到详细的安装指南。你还需要Ruby的包管理器RubyGems,可以在http://docs.rubygems.org/read/chapter/3找到RubyGems的安装指南。

gem install mongo  

这条命令会安装mongobson1Gem。我们应该会看到如下输出(版本号可能会比下面的更高):

1. BSON会在下一节中做详细说明,它是一种受JSON启发的二进制格式,MongoDB用它来表示文档。bson Ruby Gem能将Ruby对象序列化为BSON,反之亦然。

Successfully installed bson-1.4.0Successfully installed mongo-1.4.02 gems installedInstalling ri documentation for bson-1.4.0...Installing ri documentation for mongo-1.4.0...Installing RDoc documentation for bson-1.4.0...Installing RDoc documentation for mongo-1.4.0...  

我们从连接MongoDB开始。首先确保mongod正在运行,接下来创建一个名为connect.rb的文件,键入以下代码:

require /'rubygems/'require /'mongo/'@con = Mongo::Connection.new@db = @con[/'tutorial/']@users = @db[/'users/']  

头两条require语句保证一定加载了驱动,接下来的三行实例化了一个连接,将tutorial数据库分配给了@db变量,在@users变量中保存了一个对users集合的引用。保存并运行文件:

$ruby connect.rb  

如果没有抛出异常,那么你已经成功地用Ruby连接到MongoDB了。虽然不够诱人,但连接是任何语言使用MongoDB的第一步。接下来,我们使用该连接插入文档。

3.1.2 用Ruby插入文档

所有MongoDB驱动在设计上都要求使用其语言中最自然的文档表述方式。在JavaScript中JSON对象是最明显的选择,因为JSON是一种文档数据结构;在Ruby中,散列数据结构最为合适。原生的Ruby散列和JSON对象只是稍有不同,最明显的是JSON用冒号来分隔键和值,而Ruby则使用=>2。

2. 在Ruby 1.9中,也可以将冒号作为键值分隔符,但为了保证向后兼容性,本书中仅使用=>。

如果你是一路跟着示例做下来的,那么继续向connect.rb文件添加代码。你也可以选择另一种不错的方式,即使用Ruby的交互式REPL——irb。你可以运行irb,载入connect.rb,这样立刻就能访问到其中实例化的连接、数据库和集合对象了。接着可以运行Ruby代码并接收实时反馈。下面就是一个例子:

$ irb -r connect.rbirb(main):001:0> id = @users.save({/"lastname/" => /"knuth/"})=> BSON::ObjectId(/'4c2cfea0238d3b915a000004/')irb(main):002:0> @users.find_one({/"_id/" => id})=> {/"_id/"=>BSON::ObjectId(/'4c2cfea0238d3b915a000004/'), /"lastname/"=>/"knuth/"}  

让我们为users集合构建一些文档。创建两个文档来表示用户smith和jones。每个文档都用Ruby散列来表示并被分配一个变量:

smith = {/"last_name/" => /"smith/", /"age/" => 30}jones = {/"last_name/" => /"jones/", /"age/" => 40}  

要保存文档,将它们传给集合的insert方法即可。每次调用insert都会返回一个唯一ID,应该将它保存在变量里以便日后获取数据:

smith_id = @users.insert(smith)jones_id = @users.insert(jones)  

可以通过一些简单的查询来验证文档是否成功保存。通常每个文档的对象ID都会被保存在_id键中。可以通过用户集合的find_one方法来进行查询:

@users.find_one({/"_id/" => smith_id})@users.find_one({/"_id/" => jones_id})  

如果你是在irb里运行代码的,查询的返回值会显示在提示符中。如果是运行Ruby文件,加上Ruby的p方法,把结果输出到屏幕上:

p @users.find_one({/"_id/" => smith_id})  

你已经成功地用Ruby插入了两个文档,现在再来仔细看看查询。

3.1.3 查询与游标

你刚使用了驱动的find_one方法来获取单条结果。能这么简单是因为find_one隐藏了一些MongoDB查询的细节。通过标准的find方法能对此有所了解,以下是两个可能的对数据集的查找操作:

@users.find({/"last_name/" => /"smith/"})@users.find({/"age/" => {/"$gt/" => 20}}  

很明显,第一个查询找出了所有last_namesmith的用户文档,第二个查询匹配所有age大于20的文档。试着在irb中键入第二个查询:

irb(main):008:0> @users.find({/"age/" => {/"$gt/" => 30}})=> <#Mongo::Cursor:0x10109e118 ns=/"tutorial.users/"     @selector={/"age/" => /"$gt/" => 30}}>  

你将发现的第一件事会是find方法并不返回结果集,而是一个游标对象。游标出现在很多数据库系统中,出于对效率的考虑,迭代地批量返回查询结果集。假设users集合包含100万个匹配查询的文档。如果没有游标,就必须一次性返回全部这些文档。立刻返回这么大的结果意味着将所有数据复制到内存里,通过网络进行传输,然后反序列化到客户端。这本不应是个资源密集型操作,为了防止这种情况,查询实例化了一个游标,以一个可控的分块大小来获取结果集。当然,这对用户而言是透明的;在按需通过游标请求更多结果、连续调用MongoDB时,会填充驱动的游标缓冲。

下一节中会更详细地解释游标。回到例子上,现在获取到$gt查询的结果:

cursor = @users.find({/"age/" => {/"$gt/" => 20}})cursor.each do |doc|  puts doc[/"last_name/"]end  

这里用到了Ruby的each迭代器,它将每个结果都传递给一个代码块,本例中,稍后会将last_name属性输出到控制台。如果你不熟悉Ruby的迭代器,下面是一段更语言中立的等效代码:

cursor = @users.find({/"age/" => {/"$gt/" => 20}})while doc = cursor.next  puts doc[/"last_name/"]end  

这个例子里,我们连续调用游标的next方法,将值赋给本地变量doc,使用一个简单的while循环对游标进行迭代。

回想上一章里的Shell示例,再想想本节的游标,你会感到大吃一惊。Shell中使用游标的方式与驱动一样,不同之处在于调用find时Shell会自动迭代前20个游标结果。要获取剩下的结果,可以通过it命令继续手工迭代。

3.1.4 更新与删除

注意,上一章里的更新操作要求至少有两个参数:一个查询选择器和一个更新文档。下面是一个使用Ruby驱动的简单示例:

@users.update({/"last_name/" => /"smith/"}, {/"$set/" => {/"city/" => /"Chicago/"}})  

这个更新先查找last_namesmith的第一个用户,如果找到的话就将它的city值设置为Chicago,其中使用了$set操作符。

默认情况下,MongoDB只会更新单个文档。就算你有多个用户的姓是smith,也只会更新一个文档。要将更新应用到特定的smith上,需要向查询选择器添加更多的条件。但如果是想更新所有的smith文档,必须发起多项更新(multi-update)。为此,我们可以将:multi => true作为第三个参数传递给update方法:

@users.update({/"last_name/" => /"smith/"},  {/"$set/" => {/"city/" => /"New York/"}}, :multi => true)  

删除数据更加简单,使用remove方法就可以了。该方法接受一个可选的查询选择器,只删除那些匹配选择器的文档。如果没有提供选择器,就删除集合中的所有文档。此处,我们要删除age属性值大于等于40的所有用户文档:

@users.remove({/"age/" => {/"$gte/" => 40}})  

如果不带参数,remove方法会删除所有的文档:

@users.remove  

在上一章里我们说过remove实际上并不会删除集合,要删除集合及其索引,可以使用drop_collection方法:

connection = Mongo::Connection.newdb = connection[/'tutorial/']db.drop_collection(/'users/')  

3.1.5 数据库命令

在上一章里我们已经见到过数据库命令了,并看了两个stats命令。此处,我们将了解如何在驱动中运行命令,例子就是listDatabases命令,这是几个必须在admin数据库上运行的命令之一,在开启身份验证的时候还做了特殊处理。关于身份验证与admin数据库的详细内容,请阅读第10章。

首先,实例化一个Ruby数据库对象指向admin数据库。然后将命令的查询说明(query specification)传给command命令:

@admin_db = @con[/'admin/']@admin_db.command({/"listDatabases/" => 1}  

执行的响应是一个Ruby散列,罗列了所有存在的数据库和其在磁盘上的大小:

{    /"databases/" => [      {            /"name/" => /"tutorial/",            /"sizeOnDisk/" => 218103808,            /"empty/" => false      },      {            /"name/" => /"admin/",            /"sizeOnDisk/" => 1,            /"empty/" => true      },      {            /"name/" => /"local/",            /"sizeOnDisk/" => 1,            /"empty/" => true       }    ],    /"totalSize/" => 218103808,    /"ok/" => true}  

一旦习惯了使用Ruby散列来表示文档,几乎就可以无缝地从Shell API过渡过来。如果你还是对通过Ruby使用MongoDB感到不安,请不用担心,3.3节将带你进行更多的练习。但现在我们要稍作停顿,了解一下MongoDB驱动是如何工作的,这能让人更多地了解MongoDB的设计,以便能更有效地使用驱动。