资讯专栏INFORMATION COLUMN

[翻译]Play框架1.2.7版本教程(6) - 对添加标签的支持

bitkylin / 813人阅读

摘要:对添加标签的支持随着博客的文章越来越多,找到要找的文章越来越难。如果能够添加标签,那就能更好地分类文章了。要在模式下启动应用,输入添加新的到类确保它能工作。为了保持高效,我们需要向初始数据集添加标签测试数据。修改来添加一些标签数据。

对添加标签的支持

随着博客的文章越来越多,找到要找的文章越来越难。如果能够添加标签,那就能更好地分类文章了。

Tag模型

我们将给博客的模型们添加新成员。对Tag类的定义是非常简单的:

package models;
 
import java.util.*;
import javax.persistence.*;
 
import play.db.jpa.*;
 
@Entity
public class Tag extends Model implements Comparable {
 
    public String name;
 
    private Tag(String name) {
        this.name = name;
    }
 
    public String toString() {
        return name;
    }
 
    public int compareTo(Tag otherTag) {
        return name.compareTo(otherTag.name);
    }
}

因为有时想要在需要时才创建标签,我们写多一个findOrCreateByName(String name)工厂方法。把它加到Tag类:

public static Tag findOrCreateByName(String name) {
    Tag tag = Tag.find("byName", name).first();
    if(tag == null) {
        tag = new Tag(name);
    }
    return tag;
}
给文章打标签

现在是时候连接TagPost模型。让我们给Post类添加相应的关系:

…
@ManyToMany(cascade=CascadeType.PERSIST)
public Set tags;
 
public Post(User author, String title, String content) {
    this.comments = new ArrayList();
    this.tags = new TreeSet();
    this.author = author;
    this.title = title;
    this.content = content;
    this.postedAt = new Date();
}
…

注意我们这里使用TreeSet来保持一个有序的标签列表。(由于我们之前的compareTo实现,按的是字母顺序)

我们将保持该关系为单向关系。

我们将给Post类添加一系列辅助函数。第一个用于给Post打标签:

…
public Post tagItWith(String name) {
    tags.add(Tag.findOrCreateByName(name));
    return this;
}
…

下一个是根据指定标签查找所有文章:

…
public static List findTaggedWith(String tag) {
    return Post.find(
        "select distinct p from Post p join p.tags as t where t.name = ?", tag
    ).fetch();
}
…

是时候写一个新的测试用例了。要在test模式下启动应用,输入:

$ play test

添加新的@TestBasicTest类:

@Test
public void testTags() {
    // Create a new user and save it
    User bob = new User("bob@gmail.com", "secret", "Bob").save();
 
    // Create a new post
    Post bobPost = new Post(bob, "My first post", "Hello world").save();
    Post anotherBobPost = new Post(bob, "Hop", "Hello world").save();
 
    // Well
    assertEquals(0, Post.findTaggedWith("Red").size());
 
    // Tag it now
    bobPost.tagItWith("Red").tagItWith("Blue").save();
    anotherBobPost.tagItWith("Red").tagItWith("Green").save();
 
    // Check
    assertEquals(2, Post.findTaggedWith("Red").size());
    assertEquals(1, Post.findTaggedWith("Blue").size());
    assertEquals(1, Post.findTaggedWith("Green").size());
}

确保它能工作。

解决难题

如果你想要根据多个标签来查找相应的文章,该怎么办?这个问题比表面看上去要难。

我将直接给你所需的JPQL查询语句:

…
public static List findTaggedWith(String... tags) {
    return Post.find(
            "select distinct p from Post p join p.tags as t where t.name in (:tags) group by p.id, p.author, p.title, p.content,p.postedAt having count(t.id) = :size"
    ).bind("tags", tags).bind("size", tags.length).fetch();
}
…

代码的关键在于,我们需要使用一个having count语句来从连结产生的视图中过滤出拥有全部标签的文章。

注意这里我们不能用Post.find("...", tags, tags.count)语法,因为tags是一个变长参数

你可以在前一个测试中加入更多的检查来测试它:

…
assertEquals(1, Post.findTaggedWith("Red", "Blue").size());
assertEquals(1, Post.findTaggedWith("Red", "Green").size());
assertEquals(0, Post.findTaggedWith("Red", "Green", "Blue").size());
assertEquals(0, Post.findTaggedWith("Green", "Blue").size());
…
标签集合

我们最好有一个标签集合来看看有哪些标签。让我们给Tag类添加一个方法来生成标签集合:

public static List getCloud() {
    List result = Tag.find(
        "select new map(t.name as tag, count(p.id) as pound) from Post p join p.tags as t group by t.name order by t.name"
    ).fetch();
    return result;
}

这里,我们使用Hibernate的一个允许我们从JPA查询中返回自定义对象的特性。返回的结果是一个Map列表,其中每个Map包括标签名(键)和标签个数(值)。

在我们前面的测试中加多一个检查:

…
List cloud = Tag.getCloud();
assertEquals(
    "[{tag=Blue, pound=1}, {tag=Green, pound=1}, {tag=Red, pound=2}]",
    cloud.toString()
);
向博客界面添加标签

现在我们又多了一个浏览文章的方式了。为了保持高效,我们需要向初始数据集添加标签测试数据。

修改/yabe/conf/initial-data.yml来添加一些标签数据。比如:

…
Tag(play):
    name:           Play
 
Tag(architecture):
    name:           Architecture
 
Tag(test):
    name:           Test
 
Tag(mvc):
    name:           MVC
…

然后添加它们到文章的声明中:

…
Post(jeffPost):
    title:          The MVC application
    postedAt:       2009-06-06
    author:         jeff
    tags:
                    - play
                    - architecture
                    - mvc
    content:        >
                    A Play
…

在YAML文件的顶部添加Tags的定义,因为它们会被Post用到。

你需要重启应用来加载新的初始数据。注意Play甚至会告诉你出现在YAML文件中的问题:

然后修改#{display /}标签,在全文模式下展示标签集合。修改/yabe/app/views/tags/display.html

…
#{if _as != "full"}
    
         |  ${_post.comments.size() ?: "no"}
        comment${_post.comments.size().pluralize()}
        #{if _post.comments}
            , latest by ${_post.comments[0].author}
        #{/if}
    
#{/if}
#{elseif _post.tags}
    
#{/elseif}
…

标签页面

现在我们可以实现通过标签浏览文章了。在#{display /}标签,之前,我们留下一个空链接;现在终于可以用listTagged action补完它(修改yabe/app/views/tags/display.html):

…
- Tagged
#{list items:post.tags, as:"tag"}
    ${tag}${tag_isLast ? "" : ", "}
#{/list}
…

Application控制器创建action方法:

…
public static void listTagged(String tag) {
    List posts = Post.findTaggedWith(tag);
    render(tag, posts);
}
…

如常,我们创建一个特定的路由来保持URI语义化:

GET     /posts/{tag}                    Application.listTagged

我们遇到一个问题,因为这个路由跟之前的冲突了。这两个路由将匹配同一个URI:

GET     /posts/{id}                     Application.show
GET     /posts/{tag}                    Application.listTagged

不过,因为我们假设一个id是数值类型,而tag不是,我们可以简单粗暴地用正则表达式解决问题:

GET     /posts/{<[0-9]+>id}             Application.show
GET     /posts/{tag}                    Application.listTagged

最后,我们需要创建/yabe/app/views/Application/listTagged.html模板,用于新的listTagged action:

#{extends "main.html" /}
#{set title:"Posts tagged with " + tag /}
 
*{********* Title ********* }*
 
#{if posts.size() > 1}
   

There are ${posts.size()} posts tagged "${tag}"

#{/if} #{elseif posts}

There is 1 post tagged "${tag}"

#{/elseif} #{else}

No post tagged "${tag}"

#{/else} *{********* Posts list *********}*
#{list items:posts, as:"post"} #{display post:post, as:"teaser" /} #{/list}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64099.html

相关文章

  • [翻译]Play框架1.2.7版本教程(7) - 通过CRUD来实现一个基本管理面板

    摘要:通过来实现一个基本的管理面板目前,我们还没法使用博客的来写新的文章,或修改评论。提供了一个即开即用的模块,可以快速生成一个基本的管理面板。这是因为默认是以的输出来得到一个模型对象的表示。在本教程的最后一章,你会学到关于本地化信息的更多东西。 通过CRUD来实现一个基本的管理面板 目前,我们还没法使用博客的UI来写新的文章,或修改评论。Play提供了一个即开即用的CRUD模块,可以快速...

    骞讳护 评论0 收藏0
  • [翻译]Play框架1.2.7版本教程(12) - 国际化和本地化

    摘要:国际化和本地化完成了博客引擎后,我们来考虑额外的一件事应用的国际化和语言的本地化。国际化和本地化我们将分两步讨论,先是国际化,再是本地化。实际上,两者是同步进行的你在国际化的同时,往往也是在本地化。 国际化和本地化 完成了博客引擎后,我们来考虑额外的一件事:Web应用的国际化和语言的本地化。虽然我们可以一开始就做这件事,但是最好还是先完成该应用的单一语言版本,然后再添加其他语言的支持...

    hoohack 评论0 收藏0
  • [翻译]Play框架1.2.7版本教程(10) - 完成应用测试

    摘要:完成应用测试我们已经完成了我们想要创建的博客引擎。当然我们已经完成了测试所有模型层的功能。评估代码覆盖率当然我们还没有完成应用所需的所有测试用例。如你所见,我们远远没有完成对应用的全面测试。 完成应用测试 我们已经完成了我们想要创建的博客引擎。不过这个项目尚未完全结束。为了保证代码的质量,我们需要添加更多的测试。 当然我们已经完成了测试所有模型层的功能。所以博客引擎的核心功能已经被...

    _Dreams 评论0 收藏0
  • [翻译]Play框架1.2.7版本教程(1)

    摘要:确保你的文本编辑器已经做了相应的配置。第一个,会自动监测源代码的改变并在运行时自动重载。检查下面的一行是否出现在应用日志中使用版本控制系统来追踪变化当你开发一个项目时,最好使用版本控制系统来存储你的源代码。 Play是一个Java Web敏捷开发的框架http://www.playframework.com/documentation/1.2.7/home 之所以要翻译这个教程,是因...

    solocoder 评论0 收藏0
  • [翻译]Play框架1.2.7版本教程(4) - 浏览和提交评论

    摘要:浏览和提交评论博客主页现在已经完成,接下来要完成博客正文页面。整个页面将展示当前文章的所有评论,还包括一个用于提交新的评论的表单。刷新浏览器,检查这次是否使用了正确的。给模板添加表单在后面试下提交新的评论。 浏览和提交评论 博客主页现在已经完成,接下来要完成博客正文页面。整个页面将展示当前文章的所有评论,还包括一个用于提交新的评论的表单。 创建show action 要显示文章内...

    AWang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<