Ratpack + JHaml + Morphia で MongoDB を使った Web アプリ開発

これまで以下のような構成で作成してきたサンプルと同様のものを Ratpack + JHaml + Morphia の構成で作成してみました。

使用した環境は以下の通りです。

サンプルのソースは http://github.com/fits/try_samples/tree/master/blog/20110730/

事前準備1(Ratpack のビルド)

https://github.com/bleedingwolf/Ratpack から Ratpack のソースを取得し、Gradle を使って以下のようにビルドします。

Ratpack のビルド
> gradle buildDistro

環境変数 PATH に作成された build/ratpack/bin へのパスを追加し、Windows 環境では以下のような bat ファイルを作成しておきます。

build\ratpack\bin\ratpack.bat
groovy %~d0%~p0ratpack %*

事前準備2(JHaml, Morphia の JAR ファイル用意)

JHaml と Morphia に必要な JAR ファイルを入手して、ユーザーディレクトリの .groovy/lib ディレクトリ等に配置しておきます。

  • JHaml
    • jhaml-0.1.3.jar
    • commons-lang-2.5.jar
    • guava-r06.jar
    • markdownj-0.3.0-1.0.2b4.jar
  • Morphia
    • morphia-1.00-SNAPSHOT.jar
    • mongo-2.6.3.jar (MongoDB Java Driver)

なお、JHaml の JAR ファイルは https://github.com/raymyers/JHaml から取得できるソースの dist と lib ディレクトリ内にあります。

モデルクラスの定義

モデルクラスは id:fits:20110521 で作成したものを Groovy 用に書き換えました。

ratpack_jhaml_morphia.groovy
・・・
import com.google.code.morphia.annotations.*
import org.bson.types.ObjectId
・・・
//Morphia モデルクラスの定義
@Entity(value = "users", noClassnameStored = true)
class User {
    @Id ObjectId id
    String name
}

class Comment {
    String content = ""
    Date createdDate = new Date()
    @Reference User user = null
}

@Entity(value = "books", noClassnameStored = true)
class Book {
    @Id ObjectId id = null
    String title
    String isbn
    @Embedded List<Comment> comments = []
}
・・・

Ratpack による Web サーバー処理

Ratpack による Web サーバー処理です。


現時点での Ratpack は JHaml をサポートしていなかったため、JHaml を処理する renderHaml を定義して対応しました。
JHaml でパースされた文字列を Groovy の SimpleTemplateEngine を使って処理すれば、適切にテンプレート処理されます。

さらに、JHaml では HTML エスケープを行う "&=" 等に対応していなかったため、String クラスに escape メソッドを追加して対応しました。


また、RatpackServlet ではデフォルト文字コード以外での出力処理を考慮していなかったため、convertOutputToByteArray を上書きして UTF-8 でレスポンスを返すように変更しました。

ratpack_jhaml_morphia.groovy
import groovy.text.SimpleTemplateEngine
import static org.apache.commons.lang.StringEscapeUtils.escapeHtml
import com.google.code.morphia.Morphia
import com.mongodb.Mongo
import com.cadrlife.jhaml.JHaml
・・・
import com.bleedingwolf.ratpack.RatpackServlet

//UTF-8 でのレスポンス出力への対応
RatpackServlet.metaClass.convertOutputToByteArray = {String output ->
    output.getBytes("UTF-8")
}

//HTMLエスケープ
String.metaClass.escape = {
    //StringEscapeUtils.escapeHtml を使ったエスケープ
    escapeHtml(delegate)
}
・・・
//JHaml を使ったテンプレート処理
def renderHaml = {template, params->
    def hamlText = new JHaml().parse(new File("templates/${template}").text)
    new SimpleTemplateEngine().createTemplate(hamlText).make(params).toString()
}

//MongoDB への接続設定
def db = new Morphia().createDatastore(new Mongo("localhost"), "book_review")

//Top ページ処理
get("/") {
    def books = db.find(Book.class).order("title")
    def users = db.find(User.class).order("name")

    renderHaml "index.haml", ["books": books, "users": users]
}

//Book ページ処理
get("/books") {
    def books = db.find(Book.class).order("title")

    renderHaml "book.haml", ["books": books]
}

//Book 追加処理
post("/books") {
    db.save(new Book(title: params.title, isbn: params.isbn))

    response.sendRedirect("books")

    //NullPointerException 発生の回避策
    ""
}

//Comment 追加処理
post("/comments") {
    def book = db.get(Book.class, new ObjectId(params.book))
    def user = db.get(User.class, new ObjectId(params.user))

    book.comments.add(new Comment(content: params.content, user: user))
    db.save(book)

    response.sendRedirect(".")

    //NullPointerException 発生の回避策
    ""
}
・・・

JHaml によるページ定義

JHaml を使った Top ページの定義は以下の通りです。

templates/index.haml (Topページ定義)
!!! 5
%html
  %head
    %title Ratpack + JHaml + Morphia Sample
    %meta(charset="UTF-8")
  %body
    .menu Menu
    %ul
      %li
        %a(href="books")
          Books List
      %li
        %a(href="users")
          Users List

    .list Book Comments
    %form.post(action='comments' method='post')
      %select(name='user')
        - users.each
          %option(value='${it.id}')= it.name.escape()

      %select(name='book')
        - books.each
          %option(value='${it.id}')= it.title.escape()

      %input(name='content' type='text')
      %input(type='submit' value='Add')

    - books.each
      %ul
        %li= it.title.escape()
        %ul
          - it.comments.each
            %li
              ${it.content.escape()} : ${it.user.name.escape()}, ${it.createdDate.format("yyyy/MM/dd HH:mm:ss")}

Book ページは以下の通りです。

templates/book.haml(Bookページ定義)
!!! 5
%html
  %head
    %title Ratpack + JHaml + Morphia Sample
    %meta(charset="UTF-8")
  %body

    %a(href='.')
      back

    %form.post(action='books' method='post')
      Book Title:
      %input(name='title' type='text')
      ISBN:
      %input(name='isbn' type='text')
      %input(type='submit' value='Add')

    %ul
      - books.each
        %li
          ${it.title.escape()} - ${it.isbn.escape()}

実行

まず、MongoDB を実行しておきます。

MongoDB 実行例
> mongod -dbpath db

次に、Ratpackt による実行を行います。

Ratpack 実行例
> ratpack ratpack_jhaml_morphia.groovy
・・・
Starting Ratpack app with config:
[port:5000]
・・・
[   200] GET /
[   200] GET /books
[   302] POST /books
・・・

http://localhost:5000/ にアクセスすると、これまでのサンプルと同様の画面が表示されます。

表示された Top ページ例
<!DOCTYPE html>
<html>
  <head>
    <title>Ratpack + JHaml + Morphia Sample</title>
    <meta charset='UTF-8' />
  </head>
  <body>
    <div class='menu'>Menu</div>
    <ul>
      <li>
        <a href='books'>
          Books List
        </a>
      </li>
      <li>
        <a href='users'>
          Users List
        </a>
      </li>
    </ul>
    <div class='list'>Book Comments</div>
    <form action='comments' class='post' method='post'>
      <select name='user'>
          <option value='4e33d436379f236319731673'>test</option>
          <option value='4e33d43b379f236319731674'>テスター</option>
      </select>
      <select name='book'>
          <option value='4e33d448379f236319731675'>テストBook</option>
      </select>
      <input name='content' type='text' />
      <input type='submit' value='Add' />
    </form>
      <ul>
        <li>テストBook</li>
        <ul>
            <li>
              サンプルコメント : テスター, 2011/07/30 19:00:00
            </li>
        </ul>
      </ul>
  </body>
</html>