讀古今文學網 > MongoDB實戰 > 3.1 通過Ruby使用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.0
Successfully installed mongo-1.4.0
2 gems installed
Installing 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.rb
irb(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.new
db = 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的設計,以便能更有效地使用驅動。