play2 + mongodb で チュートリアルの todo list 作成してみる
play2 + mongodb を使う
play2 で mongodb がSQLなデータベースと同じように使えるのか チュートリアルをもとに確認してみました。
題材としては チュートリアルにある TODO Listの DBを mongodb にしてみただけというものです。
play2 で mongodb 使う leon/play-salat.g8 という gitter のひな形があるので、
そちらを使ってひな形は さくっと作りました。
salat は CasbahのMongoDBObjectとscalaのケースクラスと相互変換してくれる、ORマッパーらしいです。
g8実行
% g8 leon/play-salat.g8 Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8 This template generates a Scala play 2.0 project with the salat plugin verbatim [*.html *.js *.png]: database_name [my_salat_app]: todolist database_host [127.0.0.1]: application_name [my_salat_app]: play2mongotodo play_version [2.0.2]: 2.0.3 salat_plugin_version [1.0.9]: Applied leon/play-salat.g8 in .
play のバージョン は 2.0.3 にしました。
play 実行
% play Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8 [info] Loading project definition from /Users/takuya/Dropbox/projects/play2mongotodo/project [info] Set current project to play2mongotodo (in build file:/Users/takuya/Dropbox/projects/play2mongotodo/) _ _ _ __ | | __ _ _ _| | | '_ \| |/ _' | || |_| | __/|_|\____|\__ (_) |_| |__/ play! 2.0.3, http://www.playframework.org > Type "help play" or "license" for more information. > Type "exit" or use Ctrl+D to leave this console. [warn] :::::::::::::::::::::::::::::::::::::::::::::: [warn] :: UNRESOLVED DEPENDENCIES :: [warn] :::::::::::::::::::::::::::::::::::::::::::::: [warn] :: net.liftweb#lift-json_2.9.1;2.5-SNAPSHOT: not found [warn] :::::::::::::::::::::::::::::::::::::::::::::: [error] {file:/Users/takuya/Dropbox/projects/play2mongotodo/}play2mongotodo/*:update: sbt.ResolveException: unresolved dependency: net.liftweb#lift-json_2.9.1;2.5-SNAPSHOT: not found
あれっ
net.liftweb#lift-json_2.9.1;2.5-SNAPSHOT: not found
と怒られました。
どうやら resolver の定義が必要なようですね。
project/Build.scala で resolver の設定を行います。
val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings( routesImport += "se.radley.plugin.salat.Binders._", templatesImport += "org.bson.types.ObjectId", resolvers += "Sonatype" at "https://oss.sonatype.org/content/repositories/snapshots" )
改めて play 実行
play2mongotodo] $ run [info] Updating {file:/Users/takuya/Dropbox/projects/play2mongotodo/}play2mongotodo... [info] Resolving org.hibernate.javax.persistence#hibernate-jpa-2.0-api;1.0.1.Fin [info] Done updating. --- (Running the application from SBT, auto-reloading is enabled) --- [info] play - Listening for HTTP on port 9000... (Server started, use Ctrl+D to stop and go back to the console...)
今度は無事起動しました。
無事起動出来ることが確認出来たので TODO LIST の作成に移りたいと思います。
前提として mongodb はすでにインストールされているものとします。
今回の leon/play-salat.g8 を元にして作った場合、
mongodb の接続定義は g8 で作成するときに指定しますが、
後から設定/変更する場合は
conf/application.conf ファイルの中にある
mongodb.default.db = "todolist"
# Optional values
#mongodb.default.host = "127.0.0.1"
#mongodb.default.port = 27017
#mongodb.default.user = "leon"
#mongodb.default.password = "123456"
この部分を変更します。
Todo list の作成
ここから先は Todo list の作成
ルーティングの定義
/conf/route
# Home page GET / controllers.Application.index # Tasks GET /tasks controllers.Application.tasks GET /view/:id controllers.Application.view(id: ObjectId) POST /tasks controllers.Application.newTask POST /tasks/:id/delete controllers.Application.deleteTask(id: ObjectId) # Map static resources from the /public folder to the /assets URL path GET /assets/*file controllers.Assets.at(path="/public", file)
ここは チュートリアルとほぼ変化ありません。
view に渡す引数のid は ObjectId 型を渡してます。
コントローラ
/app/controllers/Application.scala
package controllers import play.api._ import play.api.mvc._ import models.Task import se.radley.plugin.salat._ import com.mongodb.casbah.Imports._ import com.novus.salat._ import play.api.data._ import play.api.data.Forms._ object Application extends Controller { val taskForm = Form( "label" -> nonEmptyText ) def index = Action { Redirect(routes.Application.tasks) } def tasks() = Action { val tasks = Task.findAll Ok(views.html.list(tasks.toList,taskForm)) } def newTask = Action { implicit request => taskForm.bindFromRequest.fold( errors => BadRequest(views.html.list(Task.findAll.toList,errors)), label => { val task = Task(label=label) Task.save(task) Redirect(routes.Application.tasks) } ) } def deleteTask(id: ObjectId) = Action { Task.removeById(id) Redirect(routes.Application.tasks) } def view(id: ObjectId) = Action { Task.findOneById(id).map( task => Ok(views.html.task(task)) ).getOrElse(NotFound) } }
基本的に ほぼ同じですが、
一覧の取得に findAll を使っています。こちらは結果が Iterator で返ってくるので
Listに変換しています。
newTask の部分でも レコードの保存では save メソッドを実行して保存しております。
model
次は model の作成
/app/models/User.scala は削除し、
/app/models/Task.scala 作成しました。
/app/models/Task.scala
package models import play.api.Play.current import java.util.Date import com.novus.salat._ import com.novus.salat.annotations._ import com.novus.salat.dao._ import com.mongodb.casbah.Imports._ import se.radley.plugin.salat._ import salatcontext._ case class Task( id: ObjectId = new ObjectId, label: String, added: Date = new Date(), updated: Option[Date] = None, deleted: Option[Date] = None ) object Task extends ModelCompanion[Task, ObjectId] { val dao = new SalatDAO[Task, ObjectId](collection = mongoCollection("tasks")) {} }
こちらは チュートリアルよりも 関数定義が減ってます。
ModelCompanion を extend しているので mongodb にある操作系の関数はそのまま結構つかえそうです。
findAll や save は定義してなくても使えるのは extend しているからですね。
object Task extends ModelCompanion[Task, ObjectId] {
val dao = new SalatDAO[Task, ObjectId](collection = mongoCollection("tasks")) {}
}
この dao として定義している箇所で、mongodb のテーブルにあたる collection に接続する為の定義となります。この例ですと tasks collection にこのオブジェクトは接続することになります。
view
次は view です。
/app/views/index.scala.html
/app/views/welcome.scala.html
は削除しました。
/app/view/list.scala.html 作成
@(tasks: List[Task], taskForm: Form[String]) @import helper._ @main("Todo list") { <h2>@tasks.size task(s)</h2> <ul> @tasks.map { task => <li> <a href="@routes.Application.view(task.id)">@task.label</a> @form(routes.Application.deleteTask(task.id)) { <input type="submit" value="Delete"> } </li> } </ul> <h2>Add a new task</h2> @form(routes.Application.newTask) { @inputText(taskForm("label")) <input type="submit" value="Create"> } }
/app/view/task.scala.html 作成
@(task:Task) @main("Task Detail") { <h1>This is @task.label task</h1> <p>This was added on the @task.added.format("dd MMM yyyy")</p> }
ビューも チュートリアルと変わりません。
これだけで DB として mongodb を使うようにすることが出来ました。
実際違うところは Application.scala の一部のメソッドとmodelの定義ぐらいでした
これも salat のおかげです。
全体はGithubにて公開しております。