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

《MongoDB实战》3.3 构建简单的应用程序

关灯直达底部

我们将构建一个简单的应用程序,用来归档及显示微推文。我们可以把它想象成更大的应用程序中的一个组件,这个应用允许用户密切注意与其业务相关的搜索项。该示例将展示处理来自Twitter API之类数据源的JSON,以及将它转成MongoDB文档有多容易。如果使用关系型数据库,就不得不事先设计一个Schema,可能还会包含多张数据表,然后还要声明那些表。使用MongoDB的话,这些事情就都不需要了,但还能保留推文文档丰富的结构,并且可以高效地进行查询。

我们称该应用为TweetArchiver,它由两个组件组成:归档器和查看器,归档器会调用Twitter的搜索API保存相关推文,查看器用于在Web浏览器里浏览结果。

3.3.1 配置

该应用程序会用到三个Ruby库,可以这样进行安装:

gem install mongogem install twittergem 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 = 0end  

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)  endend  

在保存每个推文文档前,要做个小修改。为了简化日后的查询,将搜索项添加到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 TweetArchiverdef 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 = 0enddef update  puts /"Starting Twitter search for /'#{@tag}/'.../"  save_tweets_for(@tag)  print /"#{@tweets_found} tweets saved.nn/"endprivatedef 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 endend 

剩下的就是要编写一个脚本,为每个搜索项运行TweetArchiver代码。创建update.rb,包含以下代码:

require /'config/'require /'archiver/'TAGS.each do |tag|  archive = TweetArchiver.new(tag)  archive.updateend  

然后,运行该更新脚本:

ruby update.rb  

我们会看到一些状态消息,它们指明程序找到并保存了推文。可以打开MongoDB Shell,直接查询集合来验证脚本是否能正常运行:

> use twitter-archiveswitched to db twitter-archive> db.tweets.count30  

为了保证归档内容始终是最新的,可以使用一个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 developmentwith backup from Mongrel  

我们可以打开Web浏览器,访问http://localhost:4567,页面应该会和图3-3类似。单击屏幕上方的链接可以缩小结果范围,基于特定的标签显示结果。

应用程序就这样完成了,不可否认它比较简单,但它演示了MongoDB的易用性。我们不用事先定义Schema;能充分利用二级索引加速查询,避免重复插入;还能相对简单地和编程语言进行集成。

图3-3 Web浏览器中呈现的推文归档