讀古今文學網 > MongoDB實戰 > 3.3 構建簡單的應用程序 >

3.3 構建簡單的應用程序

我們將構建一個簡單的應用程序,用來歸檔及顯示微推文。我們可以把它想像成更大的應用程序中的一個組件,這個應用允許用戶密切注意與其業務相關的搜索項。該示例將展示處理來自Twitter API之類數據源的JSON,以及將它轉成MongoDB文檔有多容易。如果使用關係型數據庫,就不得不事先設計一個Schema,可能還會包含多張數據表,然後還要聲明那些表。使用MongoDB的話,這些事情就都不需要了,但還能保留推文文檔豐富的結構,並且可以高效地進行查詢。

我們稱該應用為TweetArchiver,它由兩個組件組成:歸檔器和查看器,歸檔器會調用Twitter的搜索API保存相關推文,查看器用於在Web瀏覽器裡瀏覽結果。

3.3.1 配置

該應用程序會用到三個Ruby庫,可以這樣進行安裝:

gem install mongo
gem install twitter
gem install sinatra
  

有個配置文件能在歸檔器和查看器腳本之間進行共享會很有用,創建一個名為config.rb的文件,初始化如下常量:

DATABASE_NAME = \"twitter-archive\"
COLLECTION_NAME = \"tweets\"
TAGS = [\"mongodb\", \"ruby\"]
  

首先指定了應用程序中使用的數據庫和集合的名字。然後定義了一個搜索項數組,我們會把它們發給Twitter API。

接下來是編寫歸檔器腳本。先從TweetArchiver類開始,用一個搜索項來進行實例化。然後調用TweetArchiver實例的update方法,這會發起一次Twitter API調用,將結果保存到MongoDB集合裡。

讓我們先從類的構造器下手:

def initialize(tag)
  connection = Mongo::Connection.new
  db = connection[DATABASE_NAME]
  @tweets = db[COLLECTION_NAME]

  @tweets.create_index([[\'id\', 1]], :unique => true)
  @tweets.create_index([[\'tags\', 1], [\'id\', -1]])

  @tag = tag
  @tweets_found = 0
end
  

initialize方法實例化了一個連接、一個數據庫對像和用來存儲推文的集合對象,其中還創建了兩個索引。每條推文都有一個id字段(與MongoDB的_id字段不同),代表推文的內部Twitter ID。我們為這個字段創建了一個唯一性索引,以避免同一條推文被插入兩次。

我們還在tagsid字段上創建了一個組合索引,tags升序,id降序。索引可以指定是升序還是降序,這主要在創建組合索引時比較重要,應該總是基於自己期待的查詢模式來選擇方向。因為我們希望查詢特定的標籤,並且按時間由近及遠顯示結果,所以tags升序、id降序的索引既能用來過濾結果,也能用來進行排序。如你所見,可以用1表示升序、-1表示降序,以此來指明索引方向。

3.3.2 收集數據

在MongoDB中可以插入數據而無需考慮其結構。因為不用事先知道會有哪些字段,Twitter可以隨意修改API的返回值,不會給應用程序帶來什麼不良後果。一般來說,如果使用RDBMS,對Twitter API(說得更廣泛點,對數據源)的任何改動都會要求進行數據庫Schema遷移。用了MongoDB,應用程序可能需要做些修改來適應新的數據Schema,但數據庫本身可以自動處理各種文檔風格的Schema。

Ruby的Twitter庫返回的是Ruby散列,因此可以直接將其傳遞給MongoDB集合對象。在TweetArchiver中,添加如下實例方法:

def save_tweets_for(term)
  Twitter::Search.new.containing(term).each do |tweet|
    @tweets_found += 1
    tweet_with_tag = tweet.to_hash.merge!({\"tags\" => [term]})
    @tweets.save(tweet_with_tag)
  end
end
  

在保存每個推文文檔前,要做個小修改。為了簡化日後的查詢,將搜索項添加到tags屬性中。然後將修改過的文檔傳遞給save方法。代碼清單3-1中是完整的歸檔器代碼。

代碼清單3-1 抓取推文並將其歸檔在MongoDB中的類

require \'rubygems\'
require \'mongo\'
require \'twitter\'

require File.join{File.dirname(__FILE),\'config\';

class TweetArchiver
  # Create a new instance of TweetArchiver
def initialize(tag)
  connection = Mongo::Connection.new
  db = connection[DATABASE_NAME]
  @tweets = db[COLLECTION_NAME]
  @tweets.create_index([[\'id\', 1]], :unique => true)
  @tweets.create_index([[\'tags\', 1], [\'id\', -1]])

  @tag = tag
  @tweets_found = 0
end

def update
  puts \"Starting Twitter search for \'#{@tag}\'...\"
  save_tweets_for(@tag)
  print \"#{@tweets_found} tweets saved.nn\"
end

private

def save_tweets_for(term)
  Twitter::Search.new(term).each do |tweet|
    @tweets_found += 1
    tweet_with_tag = tweet.to_hash.merge!({\"tags\" => [term]})
    @tweets.save(tweet_with_tag)
  end
 end
end
 

剩下的就是要編寫一個腳本,為每個搜索項運行TweetArchiver代碼。創建update.rb,包含以下代碼:

require \'config\'
require \'archiver\'

TAGS.each do |tag|
  archive = TweetArchiver.new(tag)
  archive.update
end
  

然後,運行該更新腳本:

ruby update.rb
  

我們會看到一些狀態消息,它們指明程序找到並保存了推文。可以打開MongoDB Shell,直接查詢集合來驗證腳本是否能正常運行:

> use twitter-archive
switched to db twitter-archive
> db.tweets.count
30
  

為了保證歸檔內容始終是最新的,可以使用一個cron任務,每隔幾分鐘就運行一次更新腳本。但那是管理的細節,這裡的重點是通過寥寥幾行代碼就能保存從Twitter查到的推文。1接下來的任務是顯示結果。

1. 還可以用更少的代碼來實現這一功能,這就留給讀者作為練習了。

3.3.3 查看歸檔

我們將使用Ruby的Sinatra Web框架構建一個簡單的應用,用來顯示結果。創建一個名為viewer.rb的文件,和其他腳本放在同一目錄裡。隨後,新建views子目錄,放入一個名為tweets.erb的文件。項目結構看起來應該像下面這樣:

- config.rb
- archiver.rb
- update.rb
- viewer.rb
- /views
- tweets.erb
  

現在編輯viewer.rb,加入以下代碼。

代碼清單3-2 一個簡單的Sinatra應用程序,用於顯示並搜索Tweet歸檔

前面幾行代碼加載了所需的庫,還有配置文件➊。接下來的配置塊中創建了一個到MongoDB的連接,並把指向tweets集合的引用保存在常量TWEETS裡➋。

應用程序中最重要的部分是get \'/\' do之後的代碼,這個塊裡的代碼處理了對應用程序根URL的請求。首先,構建查詢選擇器:如果提供了URL參數tags則創建一個查詢選擇器,將結果集限定在給定標籤裡➌;否則就創建一個空白的選擇器,查詢會返回集合中的全部文檔➍。然後發起查詢➎。現在你應該知道賦給@tweets變量的不是結果集,而是一個游標,我們將在視圖中對該游標進行迭代。

最後一行➏呈現了視圖文件tweets.erb,完整代碼如代碼清單3-3所示。

代碼清單3-3 用於顯示推文的內嵌Ruby的HTML

<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"
  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
<html lang=\'en\' xml:lang=\'en\' xmlns=\'http://www.w3.org/1999/xhtml\'>
<head>
   <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>  

<style>
   body {
     background-color: #DBD4C2;
     width: 1000px;
     margin: 50px auto;
   }  

 h2 {
     margin-top: 2em;
        }
  </style>  

</head>  

<body>

<h1>Tweet Archive 

<% TAGS.each do |tag| %>
   <a href=\"/?tag=<%= tag %>\"><%= tag %>
<% end %>

<% @tweets.each do |tweet| %>
  <h2><%= tweet[\'text\'] %>
  <p>
    <a href=\"http://twitter.com/<%= tweet[\'from_user\'] %>\">
      <%= tweet[\'from_user\'] %>
    </a>
    on <%= tweet[\'created_at\'] %>
  </p>

  <img src=\"<%= tweet[\'profile_image_url\'] %>\"  />
  <% end %>

  </body>
</html>
  

大部分代碼只是混入了ERB的HTML,2其中的重要部分在結尾附近,有兩個迭代器。第一個迭代器遍歷了標籤列表,顯示的鏈接能將結果集限定在指定的標籤上。@tweets.each開頭的是第二個迭代器,遍歷了每條推文,顯示推文的正文、創建日期和用戶頭像圖片。運行應用程序來查看結果:

2. ERB全稱是embedded Ruby。Sinatra應用通過一個ERB處理器來運行tweets.erb文件,並在應用程序上下文中運算<%和%>之間的Ruby代碼。

$ ruby viewer.rb
  

如果應用程序正常啟動,我們將看到標準的Sinatra啟動消息:

$ ruby viewer.rb
== Sinatra/1.0.0 has taken the stage on 4567 for development
with backup from Mongrel
  

我們可以打開Web瀏覽器,訪問http://localhost:4567,頁面應該會和圖3-3類似。單擊屏幕上方的鏈接可以縮小結果範圍,基於特定的標籤顯示結果。

應用程序就這樣完成了,不可否認它比較簡單,但它演示了MongoDB的易用性。我們不用事先定義Schema;能充分利用二級索引加速查詢,避免重複插入;還能相對簡單地和編程語言進行集成。

圖3-3 Web瀏覽器中呈現的推文歸檔