面向文档的数据库 CouchDB(4)

来源:developerWorks 中国 作者:成 富
  

由于 CouchApp 可以把目录结构转换到 CouchDB 的设计文档中,因此创建show方法的时候,只需要在 Web 应用的根目录下面创建一个shows目录,并在其中创建 JavaScript 文件即可。如该目录下的book-edit.js文件会被转换成名为book-edit的方法。创建文档的show方法需要返回 HTML 文档,有两种方法可以实现。

  • 直接在show方法构造 HTML 文档内容的字符串。这种方式比较直接,不过字符串拼接比较繁琐,而且容易出错,同时带来的维护成本也比较高。
  • 使用简单的模板技术来实现。 CouchApp 自带了一个基于 JavaScript 的简单模板实现,定义在vendor/couchapp/template.js中。该模板实现可以把模板中的<%= title %>这样的声明替换成传入的 JSON 对象中title属性的值。

本文的示例应用中使用的是模板来实现的,代码清单 10给出了book-edit.js文件的内容。


清单 10. 创建和更新图书的show方法
function(doc, req) {  
  // !json templates.book.edit 
  // !code vendor/couchapp/path.js 
  // !code vendor/couchapp/template.js 

  return template(templates.book.edit, { 
    doc : doc, 
    docid : toJSON((doc && doc._id) || null), 
    assets : assetPath(), 
    index : listPath('index','recent-posts',{descending:true,limit:8}) 
  }); 
 }

在代码清单 10中,!json和!code都是由 CouchApp 提供的宏声明,用来包含外部文件。!json用来包含设计文件中的 JSON 对象,后面接着的是 JSON 对象在设计文档中的路径。如!json templates.book.edit会把设计文档中templates字段的book字段的edit字段的内容包含到当前的show方法中,并作为变量templates.book.edit的值。!code用来包含一个 JavaScript 文件,后面接着的是 JavaScript 文件相对于根目录的路径。如!code vendor/couchapp/template.js会把template.js文件包含进来。template是包含在template.js文件中的一个方法,用来完成 HTML 模板内容的替换,它的第一个参数是 HTML 模板字符串,第二个参数是包含模板中<%= %>占位符实际值的 JSON 对象。变量templates.book.edit的值是templates目录下子目录book中edit.html文件的内容。该文件的主体内容如代码清单 11所示,完整代码见下载。assetPath和listPath是由 CouchApp 提供的帮助方法,用来生成所需的路径,可以在vendor/couchapp/path.js文件找到这些方法的定义。


清单 11. edit.html 文件的内容
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> 
  <head> ...... </head> 
  <body> 
     <div id="content"> 
        <form id="new-book" action="new.html" method="POST"> 
            <h1> 添加新的图书 </h1> 
            <fieldset> 
               <p> 
                  <label for="title"> 标题 </label> 
                  <input type="text" size="40" name="title" id="title"> 
               </p> 
               <p> 
                  <label for="author"> 作者 </label> 
                  <input type="text" size="20" name="author" id="author"> 
               </p> 
               ......   
            </fieldset> 
            <p> 
                 <input type="submit" value=" 保存 " id="save"> 
                 <span id="message" style="display:none"></span> 
            </p> 
        </form> 
     </div> 
  </body> 
  <script src="/_utils/script/json2.js"></script> 
  <script src="/_utils/script/jquery.js?1.2.6"></script> 
  <script src="/_utils/script/jquery.couch.js?0.8.0"></script> 
  <script src="/_utils/script/jquery.cookies.js"></script> 
  <script src="<%= assets %>/dianping.js"></script> 
  <script type="text/javascript"> 
     $(function() { 
        var dbname = document.location.href.split('/')[3]; 
        var dname = unescape(document.location.href).split('/')[5]; 
        var db = $.couch.db(dbname); 
        var localDoc = {}; 
        var bookFields = ["title", "author", "press", "publish_date", "price", 
		 "tags", "thumbnail", "summary"];  
        $("form#new-book").submit(function(e) { 
          e.preventDefault(); 
          $.dianping.bookFormToDoc("form#new-book", bookFields, localDoc); 
          if (localDoc.tags) { 
             localDoc.tags = localDoc.tags.split(","); 
             for(var idx in localDoc.tags) { 
                localDoc.tags[idx] = $.trim(localDoc.tags[idx]); 
             } 
          } 
          db.saveDoc(localDoc, { 
             success : function(resp) { 
               $("#message").text(" 保存成功! ").fadeIn(500).fadeOut(3000); 
             }, 
             error : function(status, error, reason) { 
               $("#message").text(" 保存失败,原因是:" + reason); 
             } 
          }); 
        }); 

        var docId = <%= docid %>; 
        if (docId) { 
           db.openDoc(docId, { 
             success : function(doc) { 
               $("h1").html(" 编辑图书信息 - " + doc.title); 
               localDoc = doc; 
               $.dianping.docToBookForm("form#new-book", doc, bookFields); 
             } 
           }); 
        } 
     }); 
  </script> 
 </html>

在代码清单 11中,edit.html的主体是一个 HTML 表单,用来输入图书的相关信息。如果在调用此show方法的时候传入了文档 ID 作为参数的话,会通过db.openDoc方法获取文档的内容,并填充表单。在表单提交的时候,首先把表单中用户输入的值变成 JSON 对象,再通过db.saveDoc方法保存文档。

修改文档结构

熟悉关系数据库的开发者可能都有过类似的经历,那就是要修改一个关系数据库的表结构是一件比较困难的事情,尤其当应用中已经有一定量的数据的时候。而 CouchDB 中保存的文档是没有结构的,因此当需要根据应用的需求做修改的时候,比关系数据库要简单。在本文的示例应用中,一开始并没有考虑为图书添加封面的缩略图。如果要增加这样的功能,只需要在创建文档的表单中添加一项,用来让用户输入缩略图的链接即可。之后再创建的文档就会自动添加该字段。

删除文档

删除文档只需要调用表 1中列出的$.couch.db(dbname).removeDoc(doc, options)方法即可。





视图

视图是 CouchDB 中用来查询和呈现文档的。完成视图的定义之后,视图的运行由专门的视图服务器来完成。 CouchDB 中默认的视图定义语言是 JavaScript 。 CouchDB 中的视图运行使用的是 MapReduce 编程模型(见参考资料)。每个视图的定义中至少需要提供 Map 方法,Reduce 方法是可选的。

视图的 Map 与 Reduce

Map 方法的参数只有一个,就是当前的文档对象。 Map 方法的实现需要根据文档对象的内容,确定是否要输出结果。如果需要输出的话,可以通过emit来完成。emit方法有两个参数,分别是key和value,分别表示输出结果的键和值。使用什么样的键和值应该根据视图的实际需要来确定。当希望对文档的某个字段进行排序和过滤操作的时候,应该把该字段作为键(key)或是键的一部分;value的值可以提供给 Reduce 方法使用,也可能会出现在最终的结果中。可以作为键的不仅是简单数据类型,也可以是任意的 JSON 对象。比如emit([doc.title, doc.price], doc)中,使用数组作为键。

通过 Map 方法输出的结果称为中间结果。中间结果可以通过 Reduce 方法来进一步做聚集操作。聚集操作是对结果中键(key)相同的数据集合来进行的。 Reduce 方法的输入不仅是 Map 方法输出的中间结果,也可以是上一次 Reduce 方法的结果,后面这种情况称为 rereduce 。 Reduce 方法的参数有三个:key、values和rereduce,分别表示键、值和是否是 rereduce 。由于 rereduce 情况的存在,Reduce 方法一般需要处理两种情况:

  • 传入的参数rereduce的值为false:这表明 Reduce 方法的输入是 Map 方法输出的中间结果。参数key的值是一个数组,对应于中间结果中的每条记录。该数组的每个元素都是一个包含两个元素的数组,第一个元素是在 Map 方法中通过emit输出的键(key),第二个元素是记录所在的文档 ID 。参数values的值是一个数组,对应于 Map 方法中通过emit输出的值(value)。
  • 传入的参数rereduce的值为true:这表明 Reduce 方法的输入是上次 Reduce 方法的输出。参数key的值为null。参数values的值是一个数组,对应于上次 Reduce 方法的输出结果。

 

下面通过一个实例来说明视图 Map 和 Reduce 的用法。该视图要解决的问题是对图书简介中出现的字符进行计数,这也是一个经典的 MapReduce 编程模型的实例。代码清单 12中给出了该视图的定义。


清单 12. 对图书简介中的字符计数的视图定义
//Map 方法
 function(doc) { 
  if(doc.type == 'book' && doc.summary) { 
    var words = Array.prototype.slice.apply(doc.summary); 
    for (var i = 0; i < words.length; i++) { 
      emit(words[i], 1); 
    } 
  } 
 } 
 //Reduce 方法
 function(key, values) { 
  return sum(values); 
 }

该视图定义的基本思路是对于每本图书的简介,把其中包含的每个字符都作为键输出,而对应的值是 1,表明是一次计数。在介绍视图 REST API的时候提过,只需要发送 HTTP GET 请求就可以获得视图的运行结果。代码清单 12中视图的名字是word-count,因此只需要发送 GET 请求到http://127.0.0.1:5984/dianping/_design/dianping/_view/word-count就可以获得如代码清单 13所示的运行结果。


时间:2009-08-07 09:10 来源:developerWorks 中国 作者:成 富 原文链接

好文,顶一下
(3)
100%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量