Skip to content

Commit

Permalink
Move evolution scripts to the conf/ directory.
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaumebort committed Jan 26, 2012
1 parent 3a0382e commit 021b6f2
Show file tree
Hide file tree
Showing 16 changed files with 170 additions and 2,118 deletions.
Expand Up @@ -68,7 +68,7 @@ object Helpers extends Status with HeaderNames {
/**
* Apply pending evolutions for the given DB.
*/
def evolutionFor(dbName: String) = play.api.db.evolutions.OfflineEvolutions.applyScript(new java.io.File("."), this.getClass.getClassLoader, dbName)
def evolutionFor(dbName: String) = play.api.db.evolutions.OfflineEvolutions.applyScript(this.getClass.getClassLoader, dbName)

/**
* Extracts the Content-Type of this Content value.
Expand Down
21 changes: 21 additions & 0 deletions framework/src/play/src/main/java/play/Application.java
Expand Up @@ -105,4 +105,25 @@ public Set<String> getTypesAnnotatedWith(String packageName, Class<? extends jav
).asJava();
}

/**
* Returns `true` if the application is `DEV` mode.
*/
public boolean isDev() {
return play.api.Play.isDev(application);
}

/**
* Returns `true` if the application is `PROD` mode.
*/
public boolean isProd() {
return play.api.Play.isProd(application);
}

/**
* Returns `true` if the application is `TEST` mode.
*/
public boolean isTest() {
return play.api.Play.isTest(application);
}

}
29 changes: 23 additions & 6 deletions framework/src/play/src/main/java/play/Play.java
Expand Up @@ -6,14 +6,31 @@
public class Play {

/**
* Returns the currently running application, or `null` if not defined.
* Returns the currently running application.
*/
public static Application application() {
play.api.Application app = play.api.Play.unsafeApplication();
if(app == null) {
return null;
}
return new Application(app);
return new Application(play.api.Play.current());
}

/**
* Returns `true` if the current application is `DEV` mode.
*/
public static boolean isDev() {
return play.api.Play.isDev(play.api.Play.current());
}

/**
* Returns `true` if the current application is `PROD` mode.
*/
public static boolean isProd() {
return play.api.Play.isProd(play.api.Play.current());
}

/**
* Returns `true` if the current application is `TEST` mode.
*/
public static boolean isTest() {
return play.api.Play.isTest(play.api.Play.current());
}

}
18 changes: 10 additions & 8 deletions framework/src/play/src/main/java/play/db/ebean/EbeanPlugin.java
Expand Up @@ -78,14 +78,16 @@ public void onStart() {
servers.put(key, EbeanServerFactory.create(config));

// DDL
boolean evolutionsEnabled = !"disabled".equals(application.configuration().getString("evolutions"));
if(evolutionsEnabled) {
String evolutionScript = generateEvolutionScript(servers.get(key), config);
if(evolutionScript != null) {
File evolutions = application.getFile("db/evolutions/" + key + "/1.sql");
if(!evolutions.exists() || Files.readFile(evolutions).startsWith("# --- Created by Ebean DDL")) {
Files.createDirectory(application.getFile("db/evolutions/" + key));
Files.writeFileIfChanged(evolutions, evolutionScript);
if(!application.isProd()) {
boolean evolutionsEnabled = !"disabled".equals(application.configuration().getString("evolutions"));
if(evolutionsEnabled) {
String evolutionScript = generateEvolutionScript(servers.get(key), config);
if(evolutionScript != null) {
File evolutions = application.getFile("conf/evolutions/" + key + "/1.sql");
if(!evolutions.exists() || Files.readFile(evolutions).startsWith("# --- Created by Ebean DDL")) {
Files.createDirectory(application.getFile("conf/evolutions/" + key));
Files.writeFileIfChanged(evolutions, evolutionScript);
}
}
}
}
Expand Down
Expand Up @@ -4,6 +4,7 @@ import java.io._
import java.sql.{ Date, Connection, SQLException }

import scalax.file._
import scalax.io.JavaConverters._

import play.core._

Expand Down Expand Up @@ -74,8 +75,8 @@ object Evolutions {
def updateEvolutionScript(db: String = "default", revision: Int = 1, comment: String = "Generated", ups: String, downs: String)(implicit application: Application) {
import play.api.libs._

val evolutions = application.getFile("db/evolutions/" + db + "/" + revision + ".sql");
Files.createDirectory(application.getFile("db/evolutions/" + db));
val evolutions = application.getFile("conf/evolutions/" + db + "/" + revision + ".sql");
Files.createDirectory(application.getFile("conf/evolutions/" + db));
Files.writeFileIfChanged(evolutions,
"""|# --- %s
|
Expand All @@ -90,10 +91,6 @@ object Evolutions {

// --

private def evolutionsDirectory(applicationPath: File, db: String): Option[File] = {
Option(new File(applicationPath, "db/evolutions/" + db)).filter(_.exists)
}

private def executeQuery(sql: String)(implicit c: Connection) = {
c.createStatement.executeQuery(sql)
}
Expand Down Expand Up @@ -279,8 +276,8 @@ object Evolutions {
* @param applicationPath the application path
* @param db the database name
*/
def evolutionScript(api: DBApi, applicationPath: File, db: String) = {
val application = applicationEvolutions(applicationPath, db)
def evolutionScript(api: DBApi, applicationClassloader: ClassLoader, db: String) = {
val application = applicationEvolutions(applicationClassloader, db)
val database = databaseEvolutions(api, db)

val (nonConflictingDowns, dRest) = database.span(e => !application.headOption.exists(e.revision <= _.revision))
Expand Down Expand Up @@ -332,54 +329,52 @@ object Evolutions {
/**
* Reads the evolutions from the application.
*
* @param applicationPath the application path
* @param db the database name
*/
def applicationEvolutions(applicationPath: File, db: String) = {
evolutionsDirectory(applicationPath, db).map { dir =>
def applicationEvolutions(applicationClassloader: ClassLoader, db: String) = {

val evolutionScript = """^([0-9]+)[.]sql$""".r
val upsMarker = """^#.*!Ups.*$""".r
val downsMarker = """^#.*!Downs.*$""".r
val upsMarker = """^#.*!Ups.*$""".r
val downsMarker = """^#.*!Downs.*$""".r

val UPS = "UPS"
val DOWNS = "DOWNS"
val UNKNOWN = "UNKNOWN"
val UPS = "UPS"
val DOWNS = "DOWNS"
val UNKNOWN = "UNKNOWN"

val mapUpsAndDowns: PartialFunction[String, String] = {
case upsMarker() => UPS
case downsMarker() => DOWNS
case _ => UNKNOWN
}
val mapUpsAndDowns: PartialFunction[String, String] = {
case upsMarker() => UPS
case downsMarker() => DOWNS
case _ => UNKNOWN
}

val isMarker: PartialFunction[String, Boolean] = {
case upsMarker() => true
case downsMarker() => true
case _ => false
val isMarker: PartialFunction[String, Boolean] = {
case upsMarker() => true
case downsMarker() => true
case _ => false
}

Collections.unfoldLeft(1) { revision =>
Option(applicationClassloader.getResourceAsStream("evolutions/" + db + "/" + revision + ".sql")).map { stream =>
(revision + 1, (revision, stream.asInput.slurpString))
}
}.sortBy(_._1).map {
case (revision, script) => {

val parsed = Collections.unfoldLeft(("", script.split('\n').toList.map(_.trim))) {
case (_, Nil) => None
case (context, lines) => {
val (some, next) = lines.span(l => !isMarker(l))
Some((next.headOption.map(c => (mapUpsAndDowns(c), next.tail)).getOrElse("" -> Nil),
context -> some.mkString("\n")))
}
}.reverse.drop(1).groupBy(i => i._1).mapValues { _.map(_._2).mkString("\n").trim }

Path(dir).children().toSeq.map(f => f.name -> f).collect {
case (evolutionScript(revision), script) => Integer.parseInt(revision) -> script.slurpString
}.toList.sortBy(_._1).map {
case (revision, script) => {

val parsed = Collections.unfoldLeft(("", script.split('\n').toList.map(_.trim))) {
case (_, Nil) => None
case (context, lines) => {
val (some, next) = lines.span(l => !isMarker(l))
Some((next.headOption.map(c => (mapUpsAndDowns(c), next.tail)).getOrElse("" -> Nil),
context -> some.mkString("\n")))
}
}.reverse.drop(1).groupBy(i => i._1).mapValues { _.map(_._2).mkString("\n").trim }

Evolution(
revision,
parsed.get(UPS).getOrElse(""),
parsed.get(DOWNS).getOrElse(""))
}
}.reverse
Evolution(
revision,
parsed.get(UPS).getOrElse(""),
parsed.get(DOWNS).getOrElse(""))
}
}.reverse

}.getOrElse(Nil)
}

}
Expand Down Expand Up @@ -410,10 +405,17 @@ class EvolutionsPlugin(app: Application) extends Plugin {

api.datasources.foreach {
case (db, (ds, _)) => {
val script = evolutionScript(api, app.path, db)
val script = evolutionScript(api, app.classloader, db)
if (!script.isEmpty) {
app.mode match {
case Mode.Test => Evolutions.applyScript(api, db, script)
case Mode.Prod if app.configuration.getBoolean("applyEvolutions." + db).filter(_ == true).isDefined => Evolutions.applyScript(api, db, script)
case Mode.Prod => {
Logger("play").warn("Your production database [" + db + "] needs evolutions! \n\n" + toHumanReadableScript(script))
Logger("play").warn("Run with -DapplyEvolutions." + db + "=true if you want to run them automatically (be careful)")

throw InvalidDatabaseRevision(db, toHumanReadableScript(script))
}
case _ => throw InvalidDatabaseRevision(db, toHumanReadableScript(script))
}
}
Expand All @@ -435,14 +437,14 @@ object OfflineEvolutions {
* @param classloader the classloader used to load the driver
* @param dbName the database name
*/
def applyScript(applicationPath: File, classloader: ClassLoader, dbName: String) {
def applyScript(classloader: ClassLoader, dbName: String) {

import play.api._

val api = DBApi(
Map(dbName -> DBApi.createDataSource(
Configuration.load().getConfig("db." + dbName).get, classloader)))
val script = Evolutions.evolutionScript(api, applicationPath, dbName)
val script = Evolutions.evolutionScript(api, classloader, dbName)

if (!Play.maybeApplication.exists(_.mode == Mode.Test)) {
Logger("play").warn("Applying evolution script for database '" + dbName + "':\n\n" + Evolutions.toHumanReadableScript(script))
Expand Down
Expand Up @@ -13,7 +13,7 @@ object Collections {
* {{{
* unfoldLeft(0) { state match
* case a if a > 100 => None
* case a => a + 1
* case a => (a + 1, a + 1)
* }
* }}}
*
Expand Down
Expand Up @@ -148,7 +148,7 @@ class ReloadableApplication(sbtLink: SBTLink) extends ApplicationProvider {
import play.api.db.evolutions._

Some {
OfflineEvolutions.applyScript(path, Play.current.classloader, db)
OfflineEvolutions.applyScript(Play.current.classloader, db)
sbtLink.forceReload()
Redirect(request.queryString.get("redirect").filterNot(_.isEmpty).map(_(0)).getOrElse("/"))
}
Expand Down
3 changes: 2 additions & 1 deletion framework/src/sbt-plugin/src/main/scala/PlayCommands.scala
Expand Up @@ -16,6 +16,7 @@ import PlayExceptions._
import PlayKeys._

import scala.annotation.tailrec
import scala.collection.JavaConverters._

trait PlayCommands extends PlayJvm {
this: PlayReloader =>
Expand Down Expand Up @@ -788,7 +789,7 @@ trait PlayCommands extends PlayJvm {

import java.lang.{ ProcessBuilder => JProcessBuilder }
val builder = new JProcessBuilder(Seq(
"java") ++ properties.map { case (key, value) => "-D" + key + "=" + value } ++ Seq("-Dhttp.port=" + port, "-cp", classpath, "play.core.server.NettyServer", extracted.currentProject.base.getCanonicalPath): _*)
"java") ++ (properties ++ System.getProperties.asScala).map { case (key, value) => "-D" + key + "=" + value } ++ Seq("-Dhttp.port=" + port, "-cp", classpath, "play.core.server.NettyServer", extracted.currentProject.base.getCanonicalPath): _*)

new Thread {
override def run {
Expand Down
59 changes: 59 additions & 0 deletions framework/test/integrationtest-scala/test/FormSpec.scala
@@ -0,0 +1,59 @@
package test

import org.specs2.mutable._

import play.api.test._
import play.api.test.Helpers._

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

class FormSpec extends Specification {

val userForm = Form(
tuple(
"email" -> text,
"address" -> optional(
single("city" -> nonEmptyText)
)
)
)

"the userForm" should {

"don't bind the address if missing" in {

val (email, address) = userForm.bind(
Map("email" -> "coco")
).get

email must equalTo("coco")
address must beNone

}

"don't bind the address if blank" in {

val (email, address) = userForm.bind(
Map("email" -> "coco", "address.city" -> "")
).get

email must equalTo("coco")
address must beNone

}

"bind the address" in {

val (email, address) = userForm.bind(
Map("email" -> "coco", "address.city" -> "Paris")
).get

email must equalTo("coco")
address must beSome.which(_ == "Paris")

}

}

}

0 comments on commit 021b6f2

Please sign in to comment.