清单 13.word-count视图的运行结果
{ "rows":[ {"key":null,"value":439} ] } |
从代码清单 13中可以看到,视图的运行结果只有一行,value的值 439 是 Reduce 方法的最终运行结果,表示全部图书简介中共包含 439 个字符。默认情况下,Reduce 方法会把 Map 方法输出的记录变成一行。不过这里需要统计的是每个字符的出现次数,应该需要对字符进行分组来计数。通过在请求中添加参数group=true可以让 Reduce 方法按照 Map 方法输出的键进行分组,得到的部分运行结果如代码清单 14所示。
清单 14. 添加参数group=true之后word-count视图的运行结果
{ "rows":[ {"key":"\u4ea7","value":1}, {"key":"\u4eba","value":6}, {"key":"\u4ec0","value":1}, {"key":"\u4ee5","value":1}, {"key":"\u4eec","value":4}, {"key":"\u4f1a","value":1}, {"key":"\u4f24","value":1}, {"key":"\u4f46","value":1}, {"key":"\u97f3","value":2}, {"key":"\u9996","value":1} ] } |
在代码清单 14中,rows 数组中的每个元素表示一条记录,其中 key 是由 emit 方法输出的键,而 value 则是 emit 方法输出的值经过 Reduce 方法(如果有的话)得到的结果。由于指定了参数 group=true,相同的字符被分在一组并计数。
在获取视图运行结果的时候可以添加额外的参数,具体如表 4 所示。
表 4. 运行视图时的可选参数
参数 | 说明 |
---|---|
key | 限定结果中只包含键为该参数值的记录。 |
startkey | 限定结果中只包含键大于或等于该参数值的记录。 |
endkey | 限定结果中只包含键小于或等于该参数值的记录。 |
limit | 限定结果中包含的记录的数目。 |
descending | 指定结果中记录是否按照降序排列。 |
skip | 指定结果中需要跳过的记录数目。 |
group | 指定是否对键进行分组。 |
reduce | 指定reduce=false可以只返回 Map 方法的运行结果。 |
视图定义说明
视图定义是存放在设计文档中views字段中的,因此需要在 Web 应用根目录下新建一个 views 目录,该目录下的每个子目录都表示一个视图。每个子目录下至少需要有 map.js 文件提供 Map 方法,可以有 reduce.js 文件提供 Reduce 方法。下面通过几个具体的视图定义来解释视图的用法。
第一个例子是对应用中的标签(Tag)进行统计。每本图书都可以有多个用户自定义的标签,一个常见的需求是统计每个标签的使用次数,并生成标签云(Tag Cloud)方便用户浏览。该视图定义的 Map 和 Reduce 方法见代码清单 15。
清单 15. 标签统计的视图定义
//Map 方法 function(doc) { if(doc.tags && doc.tags.length) { for(var index in doc.tags) { emit(doc.tags[index], 1); } } } //Reduce 方法 function(key, values) { return sum(values); } |
在代码清单 15中,Map 方法首先判断文档是否包含标签,然后对于某个标签,输出标签作为键,计数值 1 作为值;而在 Reduce 方法中,将计数值累加。该视图定义与代码清单 12 中 word-count 视图定义类似。
第二个视图是根据标签来浏览图书,也就是说给定一个标签,列出包含该标签的图书。由于需要根据标签进行查询,因此把标签作为键,而对应的值则是图书文档。通过使用参数key=" 原创 "就可以查询包含标签“原创”的图书。该视图定义只包含 Map 方法,如代码清单 16 所示。
清单 16. 根据标签浏览图书的视图定义
//Map 方法 function(doc) { if(doc.type == 'book' && doc.tags && doc.tags.length) { for(var index in doc.tags) { emit(doc.tags[index], doc); } } } |
最后一个视图是用来查询每本图书对应的用户评论。该视图只有 Map 方法,其实现是对于用户评论,以其关联的图书文档 ID 和评论的创建时间作为键,输出文档的内容作为值。在使用该视图的时候需要添加参数 startkey=[docId] 和 endkey=[docId, {}]来限定只返回 ID 为 docId 的图书的用户评论。具体的视图定义如代码清单 17 所示。
清单 17. 查询图书评论的视图定义
function(doc) { if (doc.type == "comment") { emit([doc.book_id, doc.created_at], doc); } }; |
使用list方法呈现视图
与show方法对应,list方法用来把视图转换成非 JSON 格式。list方法保存在设计文档的lists字段中。代码清单 18 中给出了list方法在设计文档中的示例。
清单 18. 设计文档中的 lists 字段
{ "_id" : "_design/dianping", "views" { "book-by-tag" : "function(doc){...}" }, "lists" : { "browse-book-by-tag" : "function(head, row, req, row_info) { ... }" } |
代码清单 18 中的设计文档中定义了视图book-by-tag和 list 方法browse-book。通过 GET 请求访问/databasename/_design/dianping/_list/browse-book/book-by-tag可以获取用browse-book格式化视图book-by-tag的结果。由于视图的运行结果包含多行数据,list 方法需要迭代每行数据并分别进行格式化,因此对于一个视图的运行结果,list 方法会被多次调用。 list 方法的调用过程是迭代之前调用一次,对结果中的每行数据都调用一次,最后在迭代之后再调用一次。比如,假设结果中包含 10 条记录的话,list 方法会被调用1 + 10 + 1 = 12次。每个 list 方法都可以有四个参数:head、row、req和row_info。根据调用情况的不同,这四个参数的实际值也不同。具体如下面所示。
- 在迭代之前的调用中,head的值非空,包含与视图相关的信息,其中有两个字段:total_rows表示视图结果的总行数,offset表示当前结果中第一条记录在整个结果集中的起始位置,可以用来对视图结果进行分页。
- 在对每行数据的调用中,row和row_info的值非空:row的值为视图运行结果中的当前行,对应于代码清单 14中所示的rows数组中的一个元素。row_info包含与迭代状态相关的信息,包括row_number表示当前的行号,first_key表示结果中第一条记录的键,prev_key表示前一行的键。
- 在迭代之后的调用中,head和row的值均为空。在所有的调用中,req都包含了与此次请求相关的信息,其内容与 show 方法的第二个参数req相同,如表 2所示。
该 list 方法用来列出应用中的全部图书的概要信息。首先需要定义一个视图recent-books,该视图用来查询全部图书的概要信息,其定义如代码清单 19 所示。
清单 19.recent-books视图定义
function(doc) { if (doc.type == "book") { emit(null, { title : doc.title, author : doc.author, price : doc.price, publish_date : doc.publish_date press : doc.press }); } }; |
从代码清单 19 中可以看到,doc.type == "book"确定了只有图书才会出现在视图中,并且视图中的结果只包含图书的基本信息。在定义了视图之后,下面需要定义 list 方法。代码清单 20 中给出了 list 方法的定义。
清单 20. list 方法的定义
function(head, row, req, row_info) { // !json templates.index // !code vendor/couchapp/path.js // !code vendor/couchapp/date.js // !code vendor/couchapp/template.js if (head) { return template(templates.index.head, { assets : assetPath(), edit : showPath("book-edit"), index : listPath('index','recent-books',{limit:10}), total_books : head.total_rows }); } else if (row) { var book = row.value; return template(templates.index.row, { title : book.title, author : book.author, price : book.price, publish_date : book.publish_date, press : book.press, link : showPath("book-view", row.id), assets : assetPath() }); } else { return template(templates.index.tail, { assets : assetPath() }); } }; |