Skip to content

Commit

Permalink
Fixture support for scalikejdbc-play-plugin.
Browse files Browse the repository at this point in the history
This pull request will add fixture feature to scalikejdbc-play-plugin.

 - Fixture scripts are Plain SQL like play-evolutions.
 - A fixture script have a 'Ups' part and a 'Downs' part like evolutions. 'Ups' are executed on starting and 'Downs' are on stopping.
 - The location for fixture scripts is config/db/fixtures/${dbname}
 - Need to specify fixture scripts in application.conf.

   To use in DEV mode
     db.${dbname}.fixtures.dev="insert_data.sql"
   To use in TEST mode
     db.${dbname}.fixtures.test="insert_data.sql"

   Multiple scripts are allowed too.
     db.${dbname}.fixtures.dev=[ "insert_data.sql", "insert_additional_data.sql" ]

   When specified multiple scripts, Ups part are executed in order. Downs are executed in reverse.
  • Loading branch information
tototoshi committed Mar 31, 2013
1 parent 33010c1 commit cb71bb9
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 75 deletions.
1 change: 1 addition & 0 deletions project/Build.scala
Expand Up @@ -232,6 +232,7 @@ object ScalikeJDBCProjects extends Build {
}
}
},
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "sequential", "true"),
publishTo <<= version { (v: String) => _publishTo(v) },
publishMavenStyle := true,
publishArtifact in Test := false,
Expand Down
43 changes: 43 additions & 0 deletions scalikejdbc-play-plugin/src/main/scala/scalikejdbc/Fixture.scala
@@ -0,0 +1,43 @@
/*
* Copyright 2013 Toshiyuki Takahashi
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package scalikejdbc

import java.io.File
import play.api.libs.Files

case class Fixture(file: File) {

private def script: String = Files.readFile(file)

private def isUpsMarker(s: String): Boolean = s.matches("""^#.*!Ups.*$""")

private def isDownsMarker(s: String): Boolean = s.matches("""^#.*!Downs.*$""")

def upScript: String =
script
.lines
.dropWhile { line => !isUpsMarker(line) }
.dropWhile { line => isUpsMarker(line) }
.takeWhile { line => !isDownsMarker(line) }
.mkString("\n")

def downScript: String =
script
.lines
.dropWhile { line => !isDownsMarker(line) }
.dropWhile { line => isDownsMarker(line) }
.mkString("\n")
}
@@ -0,0 +1,20 @@
/*
* Copyright 2013 Toshiyuki Takahashi
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package scalikejdbc

class FixtureNotFoundException(message: String)
extends Exception(message)

@@ -0,0 +1,89 @@
/*
* Copyright 2013 Toshiyuki Takahashi
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package scalikejdbc

import play.api._
import java.io.File
import scala.collection.JavaConverters._

trait FixtureSupport {

val fixturesRootPath: String = "db/fixtures"

private def fixtureConfigKey(dbName: String)(implicit app: Application): String =
if (Play.isDev) {
"db." + dbName + ".fixtures.dev"
} else if (Play.isTest) {
"db." + dbName + ".fixtures.test"
} else {
throw new UnsupportedOperationException("Fixture feature is only provided for dev mode and test mode.")
}

def fixtures(implicit app: Application): Map[String, Seq[Fixture]] = {
(for {
dbConfig <- app.configuration.getConfig("db").toList
subKey <- dbConfig.subKeys
} yield {
val dbName = subKey
val fixtureNames: Seq[String] = try {
app.configuration.getStringList(fixtureConfigKey(subKey))
.map(_.asScala)
.getOrElse(Nil)
} catch {
case e: PlayException => {
app.configuration.getString(fixtureConfigKey(subKey)).toSeq
}
}

val fixtureFiles = fixtureNames.map { fixtureName =>
val resourceName = List(fixturesRootPath, dbName, fixtureName).mkString("/")
app.resource(resourceName) match {
case Some(resource) => Fixture(new File(resource.getPath))
case None => throw new FixtureNotFoundException(
"Fixture not found (%s)".format(resourceName)
)
}
}

(dbName -> fixtureFiles)
}).toMap
}

def loadFixtures()(implicit app: Application): Unit = {
for {
(dbName, fs) <- fixtures
f <- fs
} {
execute(dbName, f.upScript)
}
}

def cleanFixtures()(implicit app: Application): Unit = {
for {
(dbName, fs) <- fixtures
f <- fs.reverse
} {
execute(dbName, f.downScript)
}
}

private def execute(dbName: String, script: String): Unit = {
NamedDB(Symbol(dbName)) localTx { implicit session =>
SQL(script).update.apply()
}
}

}
Expand Up @@ -20,7 +20,8 @@ import play.api._
/**
* The Play plugin to use ScalikeJDBC
*/
class PlayPlugin(app: Application) extends Plugin {
class PlayPlugin(implicit app: Application) extends Plugin
with FixtureSupport {

import PlayPlugin._

Expand Down Expand Up @@ -89,13 +90,21 @@ class PlayPlugin(app: Application) extends Plugin {

opt("closeAllOnStop", "enabled")(playConfig).foreach { enabled => closeAllOnStop = enabled.toBoolean }

if (Play.isTest || Play.isDev) {
loadFixtures()
}
}

override def onStop(): Unit = if (closeAllOnStop) {
ConnectionPool.closeAll()
registeredPoolNames.clear()
}
override def onStop(): Unit = {
if (Play.isTest || Play.isDev) {
cleanFixtures()
}

if (closeAllOnStop) {
ConnectionPool.closeAll()
registeredPoolNames.clear()
}
}
}

object PlayPlugin {
Expand Down
@@ -0,0 +1,69 @@
package scalikejdbc

import java.io.File
import play.api.test._
import play.api.test.Helpers._
import play.api.libs.Files
import org.specs2.mutable._
import org.specs2.specification.BeforeAfterExample

class FixtureSpec extends Specification with BeforeAfterExample {

def before = {
}

def after = {
}

def fixture = {
val script = """
|
|# --- !Ups
|drop table users if exists;
|create table users (
| email varchar(255) not null primary key,
| name varchar(255) not null,
| password varchar(255) not null
|);
|
|# --- !Downs
|drop table users if exists;
|
|""".stripMargin

val tmpfile = File.createTempFile("tmp", ".sql")
tmpfile.deleteOnExit()
val writer = new java.io.PrintWriter(tmpfile)
try {
writer.println(script)
} finally {
writer.close()
}
Fixture(tmpfile)
}

"Fixture" should {

"has #upScript" in {
val expected =
"""|drop table users if exists;
|create table users (
| email varchar(255) not null primary key,
| name varchar(255) not null,
| password varchar(255) not null
|);
|""".stripMargin
fixture.upScript must_== expected
}

"has #downScript" in {
val expected =
"""|drop table users if exists;
|
|""".stripMargin
fixture.downScript must_== expected
}

}

}
@@ -0,0 +1,47 @@
package scalikejdbc

import play.api.test._
import play.api.test.Helpers._
import org.specs2.mutable._
import org.specs2.specification.BeforeAfterExample
import play.api.Play.current
import scala.collection.JavaConverters._

class FixtureSupportSpec extends Specification with BeforeAfterExample {

def before = {
}

def after = {
}

val fixtureSupport = new FixtureSupport {}

def fakeApp = FakeApplication(
additionalConfiguration = Map(
"db.default.fixtures.test" -> List("users.sql", "project.sql").asJava,
"db.secondary.fixtures.test" -> "a.sql",
"db.default.driver" -> "org.h2.Driver",
"db.default.url" -> "jdbc:h2:mem:default;DB_CLOSE_DELAY=-1",
"db.default.user" -> "sa",
"db.default.password" -> "sa",
"db.secondary.driver" -> "org.h2.Driver",
"db.secondary.url" -> "jdbc:h2:mem:secondary;DB_CLOSE_DELAY=-1",
"db.secondary.user" -> "l",
"db.secondary.password" -> "g"
),
additionalPlugins = Seq("scalikejdbc.PlayPlugin")
)

"FixtureSupport" should {

"has #fixtures" in {
running(fakeApp) {
fixtureSupport.fixtures must have size 2
}
}

}

}

7 changes: 5 additions & 2 deletions scalikejdbc-play-plugin/test/zentasks/conf/application.conf
@@ -1,19 +1,22 @@
# Configuration

application.name=Zentasks

# Secret key
# ~~~~~
# The secret key is used to secure cryptographics functions.
# If you deploy your application to several instances be sure to use the same key!
application.secret="E27D^[_<Lpt0vjad]de;3/i;tx3gpRmG4Byof/3nahO/dIo9gbsMWut1w3xg[>9W"

# Database configuration
# ~~~~~
# ~~~~~
# You can declare as many datasources as you want.
# By convention, the default datasource is named `default`
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
db.secondary.driver=org.h2.Driver
db.secondary.url="jdbc:h2:mem:secondary"
db.secondary.fixtures.test=[ "users.sql", "project.sql", "project_member.sql", "task.sql" ]
#db.default.user=sa
#db.default.password=sa

Expand Down

0 comments on commit cb71bb9

Please sign in to comment.