Play! Advent Calendar 2011 3日目です。
Play 2.0 のドキュメント にある
Using the Play 2.0 console を見ていたら "Launch the interactive console" なるものを発見。Scala の REPL を起動して書いたコードをインタラクティブに試すことができるらしい。例としてあげられている画像ではコンソールから View のテンプレートをよべている。ということは、これを使えば Rails のようにアプリケーションのコードとして書いた DAO を呼び出すことができる? ということで試してみました。
まずは適当な場所にアプリケーションを作ります。今回は Scala application を選びました。お手軽に試したいので DB は H2 でやってみます。conf/application.conf の DB 接続のコメントアウトされている設定をアンコメントします。DB にアクセスするコードは
ここ から拝借しました。そのままでは動かなかったので少し手を加えて以下のように。
package models
import java.sql.Date
import play.api.db._
import play.api.Play.current
import org.scalaquery.ql._
import org.scalaquery.ql.TypeMapper._
import org.scalaquery.ql.extended.{ExtendedTable => Table}
import org.scalaquery.ql.extended.H2Driver.Implicit._
import org.scalaquery.session._
object Tasks extends Table[(Long, String, Date, Boolean)]("TASKS") {
lazy val database = Database.forDataSource(DB.getDataSource())
def id = column[Long]("ID", O PrimaryKey, O AutoInc)
def name = column[String]("NAME", O NotNull)
def dueDate = column[Date]("DUE_DATE")
def done = column[Boolean]("DONE")
def * = id ~ name ~ dueDate ~ done
def findAll = database.withSession { implicit db:Session =>
(for(t <- this) yield t.id ~ t.name).list
}
}
db/evolutions/default というディレクトリを作って evolutions の DDL も用意します。
# --- !Ups
create table tasks (
id bigint not null primary key,
name varchar(255) not null,
due_date timestamp,
done boolean
);
create sequence task_seq start with 1000;
insert into tasks values (1, 'task 1', '2011-11-11', true);
insert into tasks values (2, 'task 2', '2011-12-03', false);
ScalaQuery を使っているので依存関係を設定します。Web 上のリポジトリにはまだ Scala 2.9.1 用の jar ファイルがなかったため github からソースを取得して publish-local したのちに project/Build.scala の該当箇所を以下のようにしました。
val appDependencies = Seq(
// Add your project dependencies here,
"org.scalaquery" %% "scalaquery" % "0.10.0-SNAPSHOT"
)
val main = PlayProject(appName, appVersion, appDependencies).settings(defaultScalaSettings:_*).settings(
// Add your own project settings here
resolvers += Resolver.file("Local Repository", file(Path.userHome.absolutePath + "/.ivy2/local"))(Resolver.ivyStylePatterns)
)
このあたりで Play のコンソールを立ち上げて確認してみます。update も compile もうまくいきました。次に今回のメインである Scala の REPL を立ち上げます。
[sandbox] $ console
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0).
Type in expressions to have them evaluated.
Type :help for more information.
scala>
起動したので早速 DB アクセスを試してみます。TAB 補完も効いていい感じです。
scala> models.Tasks.findAll
java.util.NoSuchElementException: None.get
at scala.None$.get(Option.scala:274)
at scala.None$.get(Option.scala:272)
at play.api.Play$.current(Play.scala:36)
at models.Tasks$.database(Tasks.scala:15)
at models.Tasks$.findAll(Tasks.scala:23)
--- snip ---
やっぱり最初からはうまくいかないですね。色々調べてみて
play.api.Play.current
あたりが怪しい。考えてみると play コマンドでコンソールを立ち上げて run や start などで Play アプリが起動します。ということはまだ Play は起動していないので、当然のことながら DB の接続を管理する DataSource もできていないはずです。ということで REPL から Play アプリの起動方法を探しました。
すると
play.api.Play
に
start
なるメソッド発見。これでいけそうです。
scala> import java.io.File
import java.io.File
scala> import play.api.Application
import play.api.Application
scala> import play.api.Play
import play.api.Play
scala> import play.core.ApplicationClassLoader
import play.core.ApplicationClassLoader
scala>
scala> val appBase = new File(".")
appBase: java.io.File = .
scala> val application = Application(appBase, new ApplicationClassLoader(Thread.currentThread.getContextClassLoader, Array()), None, Play.Mode.Dev)
application: play.api.Application = Application(.,play.core.ApplicationClassLoader@332946f7,None,Dev)
scala> Play.start(application)
[info] play - database [default] connected at jdbc:h2:mem:play
play.api.db.evolutions.InvalidDatabaseRevision: Database 'default' needs evolution! [An SQL script need to be run on your database.]
at play.api.db.evolutions.EvolutionsPlugin$$anonfun$onStart$1.apply(Evolutions.scala:387)
at play.api.db.evolutions.EvolutionsPlugin$$anonfun$onStart$1.apply(Evolutions.scala:383)
at scala.collection.immutable.Map$Map1.foreach(Map.scala:118)
at play.api.db.evolutions.EvolutionsPlugin.onStart(Evolutions.scala:383)
at play.api.Play$$anonfun$start$1.apply(Play.scala:58)
--- snip ---
ですよね evolutions してないとダメですよね。ということで今度は REPL から evolutions を起動する方法を探します。しばらく探していると……、ありました。
play.api.db.evolutions.OfflineEvolutions
ってやつが。
applyScript
メソッドでいけそうです。
scala> import play.api.db.evolutions.OfflineEvolutions
import play.api.db.evolutions.OfflineEvolutions
scala> OfflineEvolutions.applyScript(appBase, Thread.currentThread.getContextClassLoader, "default")
[warn] play - Applying evolution script for database 'default':
# --- Rev:1,Ups - 3022590
create table tasks (
id bigint not null primary key,
name varchar(255) not null,
due_date timestamp,
done boolean
);
create sequence task_seq start with 1000;
insert into tasks values (1, 'task 1', '2011-11-11', true);
insert into tasks values (2, 'task 2', '2011-12-03', false);
applyScript
メソッドの第 2 引数で渡している ”default" というのは application.conf で設定した
db.default.driver=org.h2.Driver
などの "db." の次の文字列を渡すみたいです。で、再チャレンジ。
scala> Play.start(application)
play.api.Configuration$$anon$2: Configuration error [Cannot connect to database at [jdbc:h2:mem:play]]
at play.api.Configuration.play$api$Configuration$$error(Configuration.scala:310)
at play.api.Configuration$$anonfun$reportError$1.apply(Configuration.scala:270)
--- snip ---
今度は DB につながらないと。調べてみたのですが原因が分からなかったので今回はインメモリじゃなくファイルでやることにしました。application.conf を以下のように書き換えました。
-db.default.url=jdbc:h2:mem:play
+db.default.url=jdbc:h2:h2db/play
再再チャレンジ。最初からやり直します。
scala> import java.io.File
import java.io.File
scala> import play.api.Application
import play.api.Application
scala> import play.api.Play
import play.api.Play
scala> import play.api.db.evolutions.OfflineEvolutions
import play.api.db.evolutions.OfflineEvolutions
scala> import play.core.ApplicationClassLoader
import play.core.ApplicationClassLoader
scala>
scala> val appBase = new File(".")
appBase: java.io.File = .
scala> OfflineEvolutions.applyScript(appBase, Thread.currentThread.getContextClassLoader, "default")
23:25:21.311 [run-main] DEBUG com.jolbox.bonecp.BoneCPDataSource - JDBC URL = jdbc:h2:h2db/play, Username = sa, partitions = 1, max (per partition) = 0, min (per partition) = 0, helper threads = 3, idle max age = 60 min, idle test period = 240 min
23:25:21.320 [run-main] WARN com.jolbox.bonecp.BoneCPConfig - Max Connections < 1. Setting to 20
23:25:22.209 [run-main] WARN play - Applying evolution script for database 'default':
# --- Rev:1,Ups - 3022590
create table tasks (
id bigint not null primary key,
name varchar(255) not null,
due_date timestamp,
done boolean
);
create sequence task_seq start with 1000;
insert into tasks values (1, 'task 1', '2011-11-11', true);
insert into tasks values (2, 'task 2', '2011-12-03', false);
scala> val application = Application(appBase, new ApplicationClassLoader(Thread.currentThread.getContextClassLoader, Array()), None, Play.Mode.Dev)
application: play.api.Application = Application(.,play.core.ApplicationClassLoader@18a8507c,None,Dev)
scala> Play.start(application)
[info] play - database [default] connected at jdbc:h2:h2db/play
[info] play - Application started
今度はうまくいきました。これでデータが取得できるか。
scala> models.Tasks.findAll
res2: List[(Long, String)] = List((1,task 1), (2,task 2))
とれました!! Play 1.x でやりたかったことが 2.0 でできるようになっていてますます 2.0 のリリースが楽しみになりました。
おそらく多くの人が望んでいる機能なのでベータがとれるころにはもっと簡単にできるようになっているかもしれません。
明日 (12/4) は
@Masahito さんです。