§Play 2.1 migration guide
To migrate a Play 2.0.x application to Play 2.1.0 first update Play’s sbt-plugin
in the project/plugins.sbt
file:
addSbtPlugin("play" % "sbt-plugin" % "2.1.0")
Now update the project/Build.scala
file to use the new play.Project
class instead of the PlayProject
class:
First the import:
import play.Project._
Then the main
project creation:
val main = play.Project(appName, appVersion, appDependencies).settings(
Lastly, update your project/build.properties
file:
sbt.version=0.12.2
Then clean and re-compile your project using the play
command in the Play 2.1.0 distribution:
play clean
play ~run
If any compilation errors cropped up, this document will help you figure out what deprecations or incompatible changes may have caused the errors.
§Changes to the build file
Because Play 2.1 introduces further modularization, you now have to explicitly specify the dependencies your application needs. By default any play.Project
will only contain a dependency to the core Play library. You have to select the exact set of optional dependencies your application need. Here are the new modularized dependencies in Play 2.1:
jdbc
: The JDBC connection pool and the theplay.api.db
API.anorm
: The Anorm component.javaCore
: The core Java API.javaJdbc
: The Java database API.javaEbean
: The Ebean plugin for Java.javaJpa
: The JPA plugin for Java.filters
: A set of build-in filters for Play (such as the CSRF filter).
Here is a typical Build.scala
file for Play 2.1:
import sbt._
import Keys._
import play.Project._
object ApplicationBuild extends Build {
val appName = "app-name"
val appVersion = "1.0"
val appDependencies = Seq(
javaCore, javaJdbc, javaEbean
)
val main = play.Project(appName, appVersion, appDependencies).settings(
// Add your own project settings here
)
}
The mainLang
parameter for the project is not required anymore. The main language is determined based on the dependencies added to the project. If dependencies contains javaCore
then the language is set to JAVA
otherwise SCALA
.Notice the modularized dependencies in the appDependencies
section.
§play.mvc.Controller.form() renamed to play.data.Form.form()
Also related to modularization, the play.data
package and its dependencies were moved out from play core to javaCore
artifact. As a consequence of this, play.mvc.Controller#form
was moved to play.data.Form#form
§play.db.ebean.Model.Finder.join() renamed to fetch()
As part of the cleanup the Finder API join methods are replaced with fetch methods. They behave exactly same.
§Play’s Promise to become Scala’s Future
With the introduction of scala.concurrent.Future
in Scala 2.10 the scala ecosystem made a huge jump to unify the various Future and Promise libraries out there.
The fact that Play is now using scala.concurrent.Future
directly means that users can effortlessly combine futures/promises coming from both internal API-s or external libraries.
Java users will continue to use a Play’s wrapper around scala.concurrent.Future for now.
Consider the following snippet:
import play.api.libs.iteratee._
import play.api.libs.concurrent._
import akka.util.duration._
def stream = Action {
AsyncResult {
implicit val timeout = Timeout(5.seconds)
val akkaFuture = (ChatRoomActor.ref ? (Join()) ).mapTo[Enumerator[String]]
//convert to play promise before sending the response
akkaFuture.asPromise.map { chunks =>
Ok.stream(chunks &> Comet( callback = "parent.message"))
}
}
}
Using the new scala.concurrent.Future
this will become:
import play.api.libs.iteratee._
import play.api.libs.concurrent._
import play.api.libs.concurrent.Execution.Implicits._
import scala.concurrent.duration._
def stream = Action {
AsyncResult {
implicit val timeout = Timeout(5.seconds)
val scalaFuture = (ChatRoomActor.ref ? (Join()) ).mapTo[Enumerator[String]]
scalaFuture.map { chunks =>
Ok.stream(chunks &> Comet( callback = "parent.message"))
}
}
}
Notice the extra imports for:
- The new import for the execution context
play.api.libs.concurrent.Execution.Implicits
- The change for duration
scala.concurrent.duration
(instead of using the Akka API) - The
asPromise
method has been removed
Generally speaking, if you see error message “error: could not find implicit value for parameter executor”, you probably need to add:
import play.api.libs.concurrent.Execution.Implicits._
(Please see the Scala documentation about Execution context for more information)
And remember that:
- A Play
Promise
is now a ScalaFuture
- A Play
Redeemable
is now a ScalaPromise
§Changes to the Scala JSON API
Play 2.1 comes with a shiny new Scala JSON validator and path navigator. This new API however breaks compatibility with existing JSON parsers.
The play.api.libs.json.Reads
type signature has changed. Consider:
trait play.api.libs.json.Reads[A] {
self =>
def reads(jsValue: JsValue): A
}
In 2.1 this becomes:
trait play.api.libs.json.Reads[A] {
self =>
def reads(jsValue: JsValue): JsResult[A]
}
So, in Play 2.0 an implementation for a JSON serializer for the User
type was:
implicit object UserFormat extends Format[User] {
def writes(o: User): JsValue = JsObject(
List("id" -> JsNumber(o.id),
"name" -> JsString(o.name),
"favThings" -> JsArray(o.favThings.map(JsString(_)))
)
)
def reads(json: JsValue): User = User(
(json \ "id").as[Long],
(json \ "name").as[String],
(json \ "favThings").as[List[String]]
)
}
In Play 2.1 you will need to refactor it as:
implicit object UserFormat extends Format[User] {
def writes(o: User): JsValue = JsObject(
List("id" -> JsNumber(o.id),
"name" -> JsString(o.name),
"favThings" -> JsArray(o.favThings.map(JsString(_)))
)
)
def reads(json: JsValue): JsResult[User] = JsSuccess(User(
(json \ "id").as[Long],
(json \ "name").as[String],
(json \ "favThings").as[List[String]]
))
}
The API to generate JSON also evolved. Consider:
val jsonObject = Json.toJson(
Map(
"users" -> Seq(
toJson(
Map(
"name" -> toJson("Bob"),
"age" -> toJson(31),
"email" -> toJson("[email protected]")
)
),
toJson(
Map(
"name" -> toJson("Kiki"),
"age" -> toJson(25),
"email" -> JsNull
)
)
)
)
)
With Play 2.1 this becomes:
val jsonObject = Json.obj(
"users" -> Json.arr(
Json.obj(
"name" -> "Bob",
"age" -> 31,
"email" -> "[email protected]"
),
Json.obj(
"name" -> "Kiki",
"age" -> 25,
"email" -> JsNull
)
)
)
More information about these features can be found at the Json documentation.
§Changes to Cookie handling
Due to a change in JBoss Netty, cookies are made to be transient by setting their maxAge
to be null
or None
(depending of the API) instead of setting the maxAge
to -1. Any value equal to 0 or less for maxAge
will cause the cookie to be expired immediately.
The discardingCookies(String\*)
(Scala) and discardCookies(String...)
(Java) methods on SimpleResult
have been deprecated, since these methods are unable to handle cookies set on a particular path, domain or set to be secure. Please use the discardingCookies(DiscardingCookie*)
(Scala) and discardCookie
(Java) methods instead.
§RequireJS
In Play 2.0 the default behavior for Javascript was to use Google’s Closure CommonJS module support. In Play 2.1 this was changed to use RequireJS instead.
What this means in practice is that by default Play will only minify and combine files in stage, dist, start modes only. In dev mode Play will resolve dependencies client side.
If you wish to use this feature, you will need to add your modules to the settings block of your project/Build.scala
file:
requireJs := "main.js"
More information about this feature can be found on the RequireJS documentation page.