블로그 이미지
App 개발에 대한 내용을 다룰 예정입니다. AppleSherbet

카테고리

분류 전체보기 (39)
한국프로야구 어플 (18)
세미나 (2)
Java Development (13)
Android App소개 (3)
기타 (2)
당근마켓 (1)
Total299,563
Today15
Yesterday32
Adding tagging support 

 요즘 회사 일이 바쁘다 보니 업데이트가 많이 늦어집니다. 하지만 끝까지 해야겠죠~ 
 이번에는 우리가 만든 블로그에 태그 기능을 넣을 차례다. 태그는 대부분 아시다시피 특정 블로깅 글에 태그를 달아주어 그 태그를 통해 블로그를 쉽게 필터링 할 수 있다.

The Tag model object 

 이제 태그 기능을 추가 하기 위해 Tag 모델을 하나 만들자. 
package models;
 
import java.util.*;
import javax.persistence.*;
 
import play.db.jpa.*;
 
@Entity
public class Tag extends Model implements Comparable<Tag> {
 
    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);
    }
}
Tag 모델에 Tag가 있을 경우 해당 태그를 없을경우 새로운 태그를 만드는 findOrCreateByName(String name) 메소드를 만들자. 
public static Tag findOrCreateByName(String name) {
    Tag tag = Tag.find("byName", name).first();
    if(tag == null) {
        tag = new Tag(name);
    }
    return tag;
}
Tagging posts 이제 Tag모델과 Post모델과 연결할 차례다. Post 클래스에 Tag와의 관례(Relation)을 설정해보자. 
…
@ManyToMany(cascade=CascadeType.PERSIST)
public Set<Tag> tags;
 
public Post(User author, String title, String content) {
    this.comments = new ArrayList<Comment>();
    this.tags = new TreeSet<Tag>();
    this.author = author;
    this.title = title;
    this.content = content;
    this.postedAt = new Date();
}
…
  
태그들을 저장하기 위해 자바 컬렉션 중 하나인 TreeSet을 사용하였다. TreeSet은 set에 저장시 특정 방법으로 미리 정렬을 하며 저장을 한다. 여기서는 알파벳순으로 정렬을 하며 저장을 하는데 이유는 바로 우리가 Tag 모델을 만들때 compareTo 메소드를 구현했기 때문이다. 
  TreeSet에서 정렬시 compareTo 메소드를 이용하기 때문에 Comparable 인터페이스를 구현 할 경우 우리가 원하는데로 정렬 방식을 바꿀 수 있다. 포스트와 태그는 Many-to-Many 관계가 적절하므로 @ManyToMany 어노테이션을 사용했다. 그리고 CascadeType.PERSIST를 이용해 하나의 Post가 저장(insert)될때 그 포스트에 들어있는 모든 Tag들도 함께 저장되도록 했다. 
 이제 태그 관리를 쉽게 하기 위한 헬퍼 메소드를 Post 모델 클래스에 만들자.  
…
public Post tagItWith(String name) {
    tags.add(Tag.findOrCreateByName(name));
    return this;
}
…
   
Tag모델에 추가한 findOrCreateByName 메소드를 이용해 Post에 태그를 추가해주는 헬퍼 메소드를 만들었다. 다음 메소드는 특정 태그에 해당하는 모든 Post들을 리턴하는 것이다. 역시 Post 모델에 추가해주자. 
…
public static List<Post> findTaggedWith(String tag) {
    return Post.find(
        "select distinct p from Post p join p.tags as t where t.name = ?", tag
    ).fetch();
}
…
 쿼리문에서 distinct 를 사용하는 이유는 중복된 Post에 대해서는 중복을 허용하지 않는다는 의미이다. 이제 지금까지의 작업에 대한 테스트 케이스를 추가할 차례이다. 아래 명령어를 이용해 서버를 재시작하자. 
$ play test
 
 BasicTest에 새로운 테스트 케이스를 추가해준다. 
@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());
}

 A little more difficult now
 
당장 사용할 기능은 아니지만 만약 여러개의 태그들을 이용해서 포스팅을 찾고 싶을때는 어떻게 해야 할까? 약간 복잡할 수 있지만 JPQL 쿼리를 이용해 아래처럼 구현 할 수 있다.  
…
public static List<Post> 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();
}
…
(String... tags) 와 같은 vararg(넘기는 인자의 수가 유동적일때 사용)를 사용하여 tag 리스트를 인자로 넘겨 받고 있다.
 GROUP BY HAVING 절 등이 들어어 쿼리가 조금 복잡해 보인다.  having count (t.id) = :size 를 통해 정확히 모든 태그가 포함된 Post만을 검색한다. 이전에 만든 test 코드에 아래의 코드를 추가해 테스트가 가능하다. 
…
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());
…
   
The tag cloud Tag를 만들었으니 Tag 클라우드를 만들자. (클라우드 열풍이라서 이렇게 이름을 지은것 같다.;;) Tag 클라우드를 만들기 위한 메소드를 Tag 클래스에 추가한다.  
public static List<Map> getCloud() {
    List<Map> 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;
}
   
여기선 JPA 쿼리에서 객체를 리턴해 주는 하이버네이트 기술을 사용한다. Map의 List를 리턴해 주며 select new map (t.name as tag, count(p.id) as pound) 에서의 경우 tag와 pound 값이 key로 실제 값이 value 값으로 들어간다. 
한가지 테스트를 더 추가해보자.  
…
List<Map> cloud = Tag.getCloud();
assertEquals(
    "[{tag=Blue, pound=1}, {tag=Green, pound=1}, {tag=Red, pound=2}]",
    cloud.toString()
);
 
 Adding tags to the Blog UI 

 /yabe/conf/initial-data.yml 파일을 수정해 Post 에 태그를 추가하자.  
…
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
…
   
주의할 점은 Post 에 태그를 추가하기 전에 (파일상 위에) 태그들을 먼저 추가해 주어야 한다. 이제 새로운 initial-data 파일을 로드하기 위해 서버를 재시작한다. 만약 initial-data 값이 잘못될경우에도 Play에서는 에러를 표시해 준다.

  

잘 진행을 해왔다면 특별히 에러는 발생하지 않을 것이다. 이제 /yabe/app/views/tags/display.html 파일의 #{display /} tag를 수정해서 Post 에 태그 set을 보여주도록 수정하자. 

…
#{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}
…



 The new ‘tagged with’ page 이제 우리의 블로그 포스트를 태그를 기반으로 리스팅 할 수 있게 수정하자. #{display/ } 태그에 기존에 우리가 비워뒀던 링크를 태깅 기능을 수행 하도록 수정하자.  
…
- Tagged
#{list items:_post.tags, as:'tag'}
    ${tag}${tag_isLast ? '' : ', '}
#{/list}
…
   
코드를 보면 태그 이름을 인자로 받는 listTagged 액션 메소드를 수행한다. 이제 Application 컨트롤러에 listTagged 액션 메소드를 만들자.  
…
public static void listTagged(String tag) {
    List<Post> posts = Post.findTaggedWith(tag);
    render(tag, posts);
}
…

 구현은 간단하다 기존에 만들어놓은 헬퍼 메소드를 이용해 태그 이름으로 Post 리스트를 리턴해 랜더링 해주고 있다. 이제 깔금한 URL의 제공을 위해 route 파일을 수정하자.  
GET     /posts/{tag}                    Application.listTagged
 
여기에 약간의 문제가 있다. 아래의 두 route는 같은 URL 포맷이라 충돌을 일으키게 된다.  
GET     /posts/{id}                     Application.show
GET     /posts/{tag}                    Application.listTagged
 하지만 실제 Post 의 id 는 숫자이며 tag는 숫자가 아니므로 약간의 수정으로 충돌을 피할 수가 있다. 정규표현식을 이용해 첫번째 route 의 범위를 좁혀보자.  
GET     /posts/{<[0-9]+>id}             Application.show
GET     /posts/{tag}                    Application.listTagged
 
이렇게 할 경우 잘 동작하게 된다. 이제 마지막으로 /yabe/app/views/Application/listTagged.html 템플릿을 만들어서 랜더링이 가능하도록 하자.  
#{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}

 Play framework으로 블로그 개발하기 (6) - Adding tagging support - 끝


저작자 표시
신고
Posted by AppleSherbet

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

티스토리 툴바