eclipse+activatorでデバッグをWindows環境で行う

PlayFramework2.3から従来のplayコマンドではなくactivatorコマンドに変更となりました。それに伴いデバッグ方法も変わってしまったので、以下に書き留めておきます

環境

  • Windows8.1
  • eclipse 4.3
  • scala2.11
  • jdk1.7

設定

%UserProfile%\.activator\activatorconfig.txtにファイルを作成します。通常ですと、

c:\users\username\.activator\activatorconfig.txt

になるかと思います

このファイルに以下を記述します。addressはデバッグ用のポートなので開いているポートならばなんでも構いません。

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000

実行

プロジェクトのフォルダにコマンドラインからアクセスし、activatorで起動します。8000番ポートでデバッグ用にポートが開いているのがわかります

Z:\test_project>activator
Listening for transport dt_socket at address: 8000
[info] Loading project definition from Z:\workspace\qrapp_api\project
[info] Set current project to qrapp_api (in build file:/Z:/workspace/qrapp_api/)
[qrapp_api] $ run
--- (Running the application from SBT, auto-reloading is enabled) ---
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/utsubo/.ivy2/cache/ch.qos.logback/lo
gback-classic/jars/logback-classic-1.1.1.jar!/org/slf4j/impl/StaticLoggerBinder.
class]
SLF4J: Found binding in [jar:file:/C:/Users/utsubo/.ivy2/cache/org.slf4j/slf4j-n
op/jars/slf4j-nop-1.6.4.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorSta
ticBinder]
[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
(Server started, use Ctrl+D to stop and go back to the console...)

次にEclipseからプロジェクトを右クリックし、DebugConfigurationからRemoteJavaApplicationをダブルクリックします。ポート番号8000を確認しScalaDebugger(SocketAttach)を選択。起動します。ブラウザを開いてアプリケーションにアクセスすれば、ブレークポイントでデバッグ可能となります。

設定はこの辺りに詳しく書いています

http://stackoverflow.com/questions/19473941/cant-debug-with-typesafe-activator

PlayFramework+EclipseでViews.htmlエラー

いつも忘れるのでメモ

PlayFrameworkをEclipseで開発しているとこのエラーが必ず出ます

object Application extends Controller{
def index()=Action{implicit request=>
Ok(views.html.index())			 // <=この行が赤いラインでエラー表示される
}
}

これの解決方法ですがプロジェクトのプロパティからJavaBuildPathを選択し、LibrariesタブからAddClassFolderで targetーscala-2.11ーclass_managedを追加

F5を押してリフレッシュすれば治ります

これが赤いままだとUnitテストでClassNotFoundエラーが出ます

batikをPlayFrameworkで使う

batikとはSVGからPDFやPNG等へ変換するすごーく便利なJavaのライブラリです。これをPlayframeworkでWebアプリとして構築してみました

準備

batickで使用するJarファイルをダウンロードしてきます。

http://mvnrepository.com/artifact/batik

この辺りから落としてくれば十分です

batik-anim.jar

batik-svgpp.jar

batik-awt-util.jar

batik-swing.jar

batik-bridge.jar

batik-transcoder.jar

batik-codec.jar

batik-ttf2svg.jar

batik-css.jar

batik-util.jar

batik-dom.jar

batik-xml.jar

batik-ext-1.6-1.jar

batik.jar

batik-ext.jar

batik-extension.jar

batik-gui-util.jar

batik-gvt.jar

batik-parser.jar

batik-rasterizer.jar

batik-script.jar

batik-slideshow.jar

batik-squiggle.jar

batik-svg-dom.jar

batik-svggen.jar

pdf-transcoder.jar

js.jar

w3c.jar

気がついたらこれだけダウンロードしていました。全部いるのでしょうか?

今回はPDFとJPEGとPNGをパラメタで分けて出力します

PlayFramework+Redis+websocketでプッシュ配信(データ圧縮編)

Websocketに最近はまっています。

前回プッシュ配信をRedisのPub/Subを用いて実現した訳ですが、データ量が増えてくるとどうしても配信するデータを圧縮したくなります。

特にAWSなんかだと、従量課金となっていますので、データ転送量は少なければ少ないほど○

Websocket自体はまだデータの圧縮はサポートされていないとのことですので、自前でデータの圧縮伸長を実装してみました。

また、それだけではつまらないので、Reidsにチャンネルを作成し、ReidsのチャンネルごとにPub/Subする仕組みを入れています

図解するとちょっとわかりにくいですがこんな感じ

			user1							userActor			 redisActor	 sub		Redis
		|code1,code2| ---	|	 user1	 | ---	|	code1	| --- |
											 |					 |			|				 |
			user2						|					 | ---	|	code2	| --- |
		|code2,code3| ---	|	 user2	 | ---	|	code3	| --- |

User別に作成したActorと、ユーザが選択したチャンネル(code1,code2,code3)別に作成したActorを別々に作成し、チャンネルがReidsから更新された際には、購読しているUserのActorへ更新をかけるという感じです

こうすることにより、チャンネル数がユーザ数より圧倒的に少ない場合には、Reidsに対するコネクションも削減でき、効率が良くなります。

  • route
GET		 /board													 controllers.Application.board(code:Option[String])	 # 画面作成用
GET		 /board/data											controllers.Application.data(code) #WSでデータ取得用
GET		 /asset/javascripts/board.js		 #Javascript controllers.Application.boardJs(code:String)
  • Compress.scala

データ圧縮用に作成します。String型をZIP圧縮し、BASE64でエンコードします

package models

import java.util.zip._
import java.io._
import org.apache.commons.codec.binary._

object Compress {
	def encode(str:String):String={
		val out = new ByteArrayOutputStream()
		val defl = new DeflaterOutputStream(out, new Deflater(Deflater.BEST_COMPRESSION, true))
		defl.write(str.getBytes())
		defl.close()
		val compressed = Base64.encodeBase64(out.toByteArray())
		new String(compressed)		
	}
}
  • UserActor.scala
...
	
	def notifyAll(code:String,data:String){
		// 実際にWebSocketでブラウザに送るデータ
		val msg=JsObject(
				Seq(
						"code"->JsString(code),
						"data"->JsString(Compress.encode(data)) // JSONの一部データ部分のみ圧縮
..
  • Javascriptで解凍

JavaScriptでZIP解凍、Base64でコードするために↓からinfrate.js,base64.js,utf.jsなどをダウンロードしておきます。

http://www.onicos.com/staff/iz/amuse/javascript/expert/

@(code:String)(implicit r: RequestHeader)

$(function() {

		var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket
		var socket = new WS("@routes.Application.data(code).webSocketURL()")

		
		var receiveEvent = function(event) {
				var data = JSON.parse(event.data)

				var bs=base64decode(data.data) // Base64デコード
				var dec=zip_inflate(bs)				// ZIP解凍
				var p=JSON.parse(dec)
				$("#data").text(p.data);
			 
		}

		socket.onmessage = receiveEvent

})

サンプルの一式は以下においておきました

https://github.com/anagotan/play_websocket_compressed

PlayFramework+Redis+websocketでプッシュ配信を作成してみる

Websocketを使ったサンプルだとチャットが多いのですが、実際にはチャットのアプリを作ることはあまり有りません。

どちらかというとサーバ側から配信するというような用途が多いのではないでしょうか?

PlayframeworkのサンプルにはWebsocketを使ったサンプルが付属しています。

これを改造してサーバ側から配信できるように改造してみました

  • 環境
    • MacOS10.9
    • jdk1.7
    • playframework2.2
    • redis2.8.4
    • scala2.10
  • playframeworkのチャットサンプル

brewでインストールした場合にはこちらに入っているので、作業エリアにコピーしておきます

/usr/local/Cellar/play/2.2.1/libexec/samples/scala/websocket-chat/

  • unicast

一斉配信したい場合にはサンプルで採用されているbroadcastをそのまま使用してもいいのですが、今回は一対一通信で配信したいのでunicastを使用します。サンプルはこちらを参考にしました。

http://satoshi-m8a.github.io/blog/2013/05/18/scala-concurrent-unicast/

  • build.sbt

scalaからredisに接続するためにはscala-redisライブラリを使用します。そのためbuild.sbtに以下を記述します

import play.Project._

name := "websocket-chat"

version := "1.0"

libraryDependencies ++= Seq(
	cache,
	"net.debasishg" % "redisclient_2.10" % "2.11"
)

playScalaSettings
  • conf/logger.xml

ログ出力用に追加します


		
	
	
	
		 ${application.home}/logs/application.log
		 
			 %date - [%level] - from %logger in %thread %n%message%n%xException%n
		 
	 

	
		
			%coloredLevel %d{HH:mm:ss.SSS} [%thread] %logger{15} - %message%n%xException{5}
		
	
	
	
	

	
		
		
	
	

  • conf/application.conf

redisの接続先をconfに書いておきます


redis.uri="http://localhost:6379/"
  • models/ChatRoom.scala

モデルをscala-redisのテストコードを参考に修正します

package models

import akka.actor._
import scala.concurrent.duration._

import play.api._
import play.api.libs.json._
import play.api.libs.iteratee._
import play.api.libs.concurrent._

import akka.util.Timeout
import akka.pattern.ask

import play.api.Play.current
import play.api.libs.concurrent.Execution.Implicits._
import play.api.libs.iteratee.Concurrent.Channel

import com.redis._
import akka.actor.{ Actor, ActorSystem, Props }
case class PublishMessage(channel: String, message: String)
case class SubscribeMessage(channels: Array[String])
case class UnsubscribeMessage(channels: Array[String])
case object GoDown



object Robot {

	def apply(chatRoom: ActorRef) {

		// Create an Iteratee that logs all messages to the console.
		val loggerIteratee = Iteratee.foreach[JsValue](event => Logger("robot").info(event.toString))

		implicit val timeout = Timeout(1 second)
		// Make the robot join the room
		chatRoom ? (Join("Robot")) map {
			case Connected(robotChannel) =>
				// Apply this Enumerator on the logger.
				robotChannel |>> loggerIteratee
		}

		// Make the robot talk every 30 seconds
		Akka.system.scheduler.schedule(
			30 seconds,
			30 seconds,
			chatRoom,
			Talk("Robot", "I'm still alive")
		)
	}

}

object ChatRoom {

	implicit val timeout = Timeout(1 second)

	// username split ","	ex. "6758,9997"
	def join(username: String): scala.concurrent.Future[(Iteratee[JsValue, _], Enumerator[JsValue])] = {
		val roomActor = Akka.system.actorOf(Props[ChatRoom])
		
		roomActor ! SubscribeMessage(username.split(","))

		Robot(roomActor)

		(roomActor ? Join(username)).map {

			case Connected(enumerator) =>

				// Create an Iteratee to consume the feed
				val iteratee = Iteratee.foreach[JsValue] {
					event =>
						roomActor ! Talk(username, (event \ "text").as[String])
				}.mapDone {
					_ =>
						roomActor ! Quit(username)
				}

				(iteratee, enumerator)

			case CannotConnect(error) =>

				// Connection error

				// A finished Iteratee sending EOF
				val iteratee = Done[JsValue, Unit]((), Input.EOF)

				// Send an error and close the socket
				val enumerator = Enumerator[JsValue](JsObject(Seq("error" -> JsString(error)))).andThen(Enumerator.enumInput(Input.EOF))

				(iteratee, enumerator)

		}
	}
}

class ChatRoom extends Actor {
	println("starting subscription service ..")
	val system = ActorSystem("sub")
	val uri = new java.net.URI(Play.configuration.getString("redis.uri").get)
	val r = new RedisClient(uri.getHost,uri.getPort)
	val s = system.actorOf(Props(new Subscriber(r)))
	s ! Register(callback)
	
	def receive = {
		case SubscribeMessage(chs) => sub(chs)
		case UnsubscribeMessage(chs) => unsub(chs)
		case GoDown =>
			r.quit
			system.shutdown()
			system.awaitTermination()

		//case x => println("Got in Sub " + x)
		
		
		
		case Join(username) => {
			sender ! Connected(chatEnumerator)
		}

		case NotifyJoin(username) => {
			notifyAll("join", username, "has entered the room")
		}

		case Talk(username, text) => {
			notifyAll("talk", username, text)
		}

		case Quit(username) => {
			notifyAll("quit", username, "has left the room")
		}
	}

	def sub(channels: Array[String]) = {
		s ! Subscribe(channels.toArray)
	}

	def unsub(channels: Array[String]) = {
		s ! Unsubscribe(channels.toArray)
	}

	def callback(pubsub: PubSubMessage) = pubsub match {
		case E(exception) => println("Fatal error caused consumer dead. Please init new consumer reconnecting to master or connect to backup")
		case S(channel, no) => println("subscribed to " + channel + " and count = " + no)
		case U(channel, no) => println("unsubscribed from " + channel + " and count = " + no)
		case M(channel, msg) =>
			msg match {
				// exit will unsubscribe from all channels and stop subscription service
				case "exit" =>
					println("unsubscribe all ..")
					r.unsubscribe

				// message "+x" will subscribe to channel x
				case x if x startsWith "+" =>
					val s: Seq[Char] = x
					s match {
						case Seq('+', rest @ _*) => r.subscribe(rest.toString){ m => }
					}

				// message "-x" will unsubscribe from channel x
				case x if x startsWith "-" =>
					val s: Seq[Char] = x
					s match {
						case Seq('-', rest @ _*) => r.unsubscribe(rest.toString)
					}

				// other message receive
				case x =>
					println("received message on channel " + channel + " as : " + x)
					notifyAll("talk", channel, x)
			}
	}
	

	var chatChannel: Option[Channel[JsValue]] = None

	def onStart: Channel[JsValue] => Unit = {
		channel =>
			chatChannel = Some(channel)
			println("start")
			self ! NotifyJoin("you")
	}

	def onError: (String, Input[JsValue]) => Unit = {
		(message, input) =>
			println("onError " + message)
	}

	def onComplete = println("onComplete")

	val chatEnumerator = Concurrent.unicast[JsValue](onStart, onComplete, onError)
/*
	def receive = {

		case Join(username) => {
			sender ! Connected(chatEnumerator)
		}

		case NotifyJoin(username) => {
			notifyAll("join", username, "has entered the room")
		}

		case Talk(username, text) => {
			notifyAll("talk", username, text)
		}

		case Quit(username) => {
			notifyAll("quit", username, "has left the room")
		}

	}
	* 
	*/

	def notifyAll(kind: String, user: String, text: String) {
		val msg = JsObject(
			Seq(
				"kind" -> JsString(kind),
				"user" -> JsString(user),
				"message" -> JsString(text)
			)
		)
		chatChannel match {
			case Some(channel) => channel.push(msg)
			case _ => println("nothing")
		}
	}
}

case class Join(username: String)
case class Quit(username: String)
case class Talk(username: String, text: String)
case class NotifyJoin(username: String)
case class Connected(enumerator: Enumerator[JsValue])
case class CannotConnect(msg: String)

これで準備完了

  • 実行

play runで実行し、ユーザ名部分にカンマ区切りでキーを入力します。このキーはカンマ区切りで複数入力可能で、このキーがredisのキーとなります。

play run

画面が起動したら、a,b でログインしてみます

その後Reidsのコマンドで値を送ってみます

redis-cli publish a test

画面にa test が表示されます

PlayFramework2.1でセッションタイムアウトを作ってみた

PlayFrameworkのセッションはCookieになります。ブラウザを閉じるまで有効なので、従来のservletなどで実現してきたsessionの観念はありません。

これが意外と困ってしまうので(タイムアウトした際にログアウトさせるなど)Cookieに時間を持たせることによりにたようなものを実装してみました

routes

GET		 /													 controllers.Application.index
GET		 /login													controllers.Application.login
POST		/login													controllers.Application.submit

controllers.Application.scala

package controllers

import play.api._
import play.api.mvc._
import helper.Session
import helper.Login
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints._
	
case class User(id:String,password:String)
	
object Application extends Controller {
	val userForm = Form[User](
			mapping(
					"id" ->nonEmptyText,
					"password"->text(minLength=6))
			((id,password)=>User(id,password))((user:User)=>Some(user.id,user.password))
	)
	
	def index = Action {request=>{
			if(Session.check(request))Ok(views.html.index("LoginOK"))
			else Redirect(routes.Application.login)
		}	 
	}
	def login=Action{
		Ok(views.html.login(userForm))
	}
	def submit = Action { implicit request =>
		userForm.bindFromRequest.fold(
			errors => BadRequest(views.html.login(errors)),
			user => {
				if(Login.check(request,user.id,user.password))Redirect(routes.Application.index).withSession(Session.set(request))
				else BadRequest(views.html.login(userForm))
			}
		) 
	}
}

helper.Login.scala

本来ならばログインIDとパスワードとつきあわせてチェックするのですがとりあえず必ずOKになるように

package helper

import play.api._
import play.api.mvc._
object Login {

	def check(req:Request[AnyContent],id:String,password:String):Boolean={
		true // NO check
	}
}

helper.Session.scala

クッキーに保存した時刻と現在時刻を比較し、ログアウトの判定

package helper

import play.api._
import play.api.mvc._
object Session {
	val MAX_MILLISEC:Long=1000*60	// time out 
	val SESSION_KEY="play2"
	
	def check(req:Request[AnyContent]):Boolean={
		req.session.get(SESSION_KEY).map{
			user=>{ 
				val cookieDate=user.toLong
				val nowDate=new java.util.Date().getTime();
				if(nowDate-cookieDatenew java.util.Date().getTime().toString())
	}
// ログアウトボタンを押したらこれを呼ぶ、未実装
	def del(req:Request[AnyContent])={
		req.session - SESSION_KEY
	}
}

views.index.scala.html

@(message: String)
@message

views.login.scala.html

@(userForm:Form[User])

@import helper._

@helper.form(action=routes.Application.submit){
						@inputText(
								userForm("id"), 
								'_label -> "Username", 
								'_help -> "Please choose a valid username.",
								'_error -> userForm.globalError
						)
						
						
						@inputPassword(
								userForm("password"), 
								'_label -> "Password",
								'_help -> "A password must be at least 6 characters. "
						)
						



}

ログイン後1分放置するとログイン画面に戻ります

Slick+PlayFramework2.1でDBアクセスしjsonで出力

実際の業務では、サンプルなどでよく見かける、データの作成、更新、参照、削除という流れは実際ではあまりなく、既にあるデータを参照するだけとか祖いうのが多いかと思います。

前回までで、既存データベースに入っているデータを取得することができたので、JSON形式で出力してみます。

GSON

sjsonやPlay標準のJSONなど何種類かあるようですが、ちょっと試したところうまくいかなかったので、GSONを使用することにしました。

あらかじめGSONをダウンロードしておきeclipseのパスに追加しておきます。

http://code.google.com/p/google-gson/

mysql

use data
;
create table price(
code varchar(10) not null,
date datetime not null,
open int ,
high int,
low int,
close int
)
;
create unique index price1 on price(code,date)
;

application.conf

db.default.driver=com.mysql.jdbc.Driver
db.default.url="jdbc:mysql://localhost/data"
db.default.user=root
db.default.password=
slick.default="models.*"
evolutionplugin=disabled

routes

GET /data/price/:code	controllers.Data.OHLC(code:String)

controllers.Data.scala

package controllers
import play.api._
import play.api.mvc._
import models._
import com.google.gson._

object Data extends Controller {
	val gson:Gson=new GsonBuilder().setDateFormat("yyyy-MM-dd").create // 日付のフォーマットをしておく
	def OHLC(code:String)=Action {
		Ok(views.html.json(gson.toJson(Prices.select(code))))
	}
}

models.Price.scala

package models
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession

import play.api.db._
import play.api.Play._

import scala.slick.jdbc.{GetResult, StaticQuery => Q}
import Q.interpolation

case class Price(code:String,date:java.sql.Date,open:Int,high:Int,low:Int,close:Int) extends Ordered[Price]{
	def compare(that:Price):Int={
		date.getTime().compare(that.date.getTime())
	}
}

object Prices extends Table[Price]("price") {
	val DATE_TERM=180 // 180日分とる
	
	def code=column[String]("code",O.PrimaryKey)
	def date=column[java.sql.Date]("date",O.PrimaryKey)
	def open=column[Int]("open")
	def high=column[Int]("high")
	def low=column[Int]("low")
	def close=column[Int]("close")
	def * = code ~ date ~ open ~ high ~ low ~ close <> (Price,Price.unapply _)

	implicit val getPrice=GetResult(rs=>Price(rs<<,rs<<,rs<<,rs<<,rs<<,rs<<))
	
	def select(code:String)= connectDB{
		val list=sql"select * from price where code=$code order by date desc limit $DATE_TERM".as[PriceHistAdj].list.sorted
		// sort 逆順なので並べ替える、GSON用にArrayList化
		val ary=new java.util.ArrayList[Price]
		for(l<-list)ary.add(l)
		ary
	}
	def connectDB[Any](f: => Any): Any = {
		Database.forDataSource(DB.getDataSource("data")) withSession {
			f
		}
	}
}

views.json.scala.html

jsonp用

@(json:String)
callback(@json);

Slick+PlayFramework2.1でDBアクセス#2

既存のデータベースにアクセスしてみます。

DBのコネクションをPlayFrameworkから与えることでモデル側でコネクションを作成しません

MySQL

create table OHLC(
code varchar(10) not null,
date datetime not null,
open int null,
high int null,
low int null,
close int null,
unique key OHLC1(code,date) 
)

application.conf

db.default.driver=org.gjt.mm.mysql.Driver
db.default.url="jdbc:mysql://localhost/live?useOldAliasMetadataBehavior=true" 
db.default.user=root
db.default.password="" 
slick.default="models.*"
evolutionplugin=disabled

routes

GET /:code controllers.Application.price(code:String)

controllers.Application.scala

package controllers

import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import models._

object Application extends Controller {
	def price(code:String) = Action{
		Ok(views.html.index(Price.select(code)))
	}
}

models.Price.scala

package models
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession

import play.api.db._
import play.api.Play._

import scala.slick.jdbc.{GetResult, StaticQuery => Q}
import Q.interpolation


case class Price(code:String,date:java.sql.Date,open:Int,high:Int,low:Int,close:Int)

object Prices extends Table[Price]("OHLC"){
	def scode=column[String]("code",O.PrimaryKey)
	def date=column[java.sql.Date]("date",O.PrimaryKey)
	def open=column[Int]("open")
	def high=column[Int]("high")
	def low=column[Int]("low")
	def close=column[Int]("close")

	def * = code ~ date ~ open ~ high ~ low ~ close <> (Price,Price.unapply _)

	implicit val getPrice=GetResult(rs=>Price(rs.nextString,rs.nextDate,rs.nextInt,rs.nextInt,rs.nextInt,rs.nextInt))
	
	def price(code:String):List[Price]= connectDB{
		sql"select * from OHLC where code=$code".as[Price].list
	}
	
	def connectDB[Any](f: => Any): Any = {
		Database.forDataSource(DB.getDataSource("default")) withSession {
			f
		}
	}
}

views.index.scala.html

@(prices: List[Price])
				@prices.map { price =>
						@price.code,
						@price.date
						@price.open
}

Slick+PlayFramework2でデータベースアクセス

ScalaでPlayFrameworkを使う場合にDBアクセスにはanormとslickがあるようです。

slickはscalaQueryと呼ばれていたものが進化した模様。

scala2.9まではscalaQuery,それ以降はslickらしいです

http://d.hatena.ne.jp/tototoshi/20121204/1354615421

Build.scalaに必要なモジュールを書いてインストールするのが一般的らしいですが、うまくいかなかったので手動インストールで作成してみます。

slick

https://github.com/freekh/play-slick

からZIPでダウンロードし解凍

 $ cd slick-1.0.1
 $ sbt
 > compile
 > package
 $ find .|grep jar
	./slick-testkit/target/scala-2.10/slick-testkit_2.10-1.0.1.jar
	./target/root/scala-2.10/root_2.10-1.0.1.jar
	./target/scala-2.10/slick_2.10-1.0.1.jar

play-slick

プラグインなのでこちらも入れます

https://github.com/freekh/play-slick

 $ cd play-slick-master
 $ sbt
 > compile
 > package
 $ find .|grep jar
 ./target/scala-2.10/play-slick_2.10-0.3.3-SNAPSHOT.jar

プロジェクト作成

$ play new sample
			 _						_
 _ __ | | __ _ _	_| |
| '_ \| |/ _' | || |_|
|	__/|_|\____|\__ (_)
|_|						|__/

play! 2.1.2 (using Java 1.7.0_25 and Scala 2.10.0), http://www.playframework.org

The new application will be created in /Users/admin/Documents/workspace/play_sample/sample

What is the application name? [sample]
> sample

Which template do you want to use for this new application? 

	1						 - Create a simple Scala application
	2						 - Create a simple Java application

> 1
OK, application sample is created.

Have fun!

$ cd sample
$ play eclipse
$ mkdir lib
$ cp slick-1.0.1/target/scala-2.10/slick_2.10-1.0.1.jar .
$ cp play-slick-master/target/scala-2.10/play-slick_2.10-0.3.3-SNAPSHOT.jar .

Eclipse

  • eclipseの既存プロジェクトの読み込みで読み込む
  • プロジェクトのプロパティからslick_2.10-1.0.1.jar、play-slick_2.10-0.3.3-SNAPSHOT.jarのライブラリを追加

PlayFramework

http://takashima0411.hatenablog.com/entry/2012/11/28/231817

こちらの方のサンプルをそのままいただきました

Application.scala
package controllers

import play.api._
import play.api.mvc._

import play.api.data._
import play.api.data.Forms._

import models._

object Application extends Controller {

	val taskForm = Form(
		"label" -> nonEmptyText)

	def index = Action {
		Redirect(routes.Application.tasks)
	}

	def tasks = Action {
		Ok(views.html.index(Tasks.all(), taskForm))
	}

	def newTask = Action { implicit request =>
		taskForm.bindFromRequest.fold(
			errors => BadRequest(views.html.index(Tasks.all(), errors)),
			label => {
				Tasks.create(label)
				Redirect(routes.Application.tasks)
			})
	}

	def deleteTask(id: Long) = Action {
		Tasks.delete(id)
		Redirect(routes.Application.tasks)
	}

}
Task.scala
package models

import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession

case class Task(id: Long, label: String)

object Tasks extends Table[Task]("TASK") {

	def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
	def label = column[String]("LABEL", O.NotNull)
	def * = id ~ label <> (Task, Task.unapply _)
	def ins = label returning id

	def all(): List[Task] = connectDB {
		Query(Tasks).sortBy(_.id).list
	}

	def create(label: String) = connectDB {
		Tasks.ins.insert(label)
	}

	def delete(id: Long) = connectDB {
		Tasks.where(_.id === id).delete
	}

	def connectDB[Any](f: => Any): Any = {
		Database.forURL("jdbc:h2:mem:play", driver = "org.h2.Driver") withSession {
			f
		}
	}

}
Global.scala
import play.api._
import models._
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession

import models._
import java.sql.{Date,Time,Timestamp}

object Global extends GlobalSettings {

	override def onStart(app: Application) {
		Logger.info("Application has started")

		Database.forURL("jdbc:h2:mem:play", driver = "org.h2.Driver") withSession {

			Tasks.ddl.create

		}

	}

	override def onStop(app: Application) {
		Logger.info("Application shutdown...")
	}

}
index.scala.html

@(tasks: List[Task], taskForm: Form[String])

@import helper._


		
		

@tasks.size task(s)

    @tasks.map { task =>
  • @task.label @form(routes.Application.deleteTask(task.id)) { }
  • }

Add a new task

@form(routes.Application.newTask) { @inputText(taskForm("label")) }
routes
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET		 /													 controllers.Application.index
GET		 /index											controllers.Application.index
GET		 /tasks											controllers.Application.tasks
GET		 /newTask										controllers.Application.newTask
GET		 /deleteTask/:id								 controllers.Application.deleteTask(id:Long)

# Map static resources from the /public folder to the /assets URL path
GET		 /assets/*file							 controllers.Assets.at(path="/public", file)
application.conf
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"


slick.default="models.*"

# Evolutions
# ~~~~~
# You can disable evolutions if needed
evolutionplugin=disabled

これでとりあえず動きます

PlayFramework2.1で既存のMySQLにつないでみる

ちょっと悩んだのでメモ

PlayFrameworkで既存のMySQLにつなぐためには

テーブルはこれ

use sampled
;
create table sample(
id integer,
name varchar(32)
)
;
create unique index idx_sampleTable on sampleTable(id)
;

まずモデル

package models;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import play.db.ebean.model;

@Entity
@Table(name="sample")
public class Sample extends Model
{
	@Column(name="id")
	@Id
	public integer id;

	@Column(name="name")
	public String name;

	public String toString(){
		return id+","+name;
	}
}

application.conf

..
db.default.driver=com.mysql.jdbc.Driver
db.default.url="jdbc:mysql://dbserver/sampledb"
db.default.user=root
db.default.password=password

evolutionplugin=disabled


ebean.default="models.*"
..

これを使って、ViewとControllerにつなげれやればおっけー