Intro
I’ve used Play for the past couple years, mostly using version 2.3. For various reasons, I haven’t worked with 2.4 or above, so some of the things I’ll mention below may be dated.
Overall it’s a great framework with a lot of things included. I prefer opinionated frameworks, since they make getting started a lot more straight forward. For newer developers, they provide a happy path that they can follow to avoid frustration.
One of the problems with batteries-included frameworks is that if you veer off the happy path, sometimes it’s difficult to find your way without combing through the source code. I’ll describe some features and techniques I’ve used that address some things you may be interested in after having worked with the framework for awhile.
Fair warning: this post is disorganized, but I plan to update it as I think of new things to add.
Templates
One of my favorite features of Play is the Twirl templates library. The templates you write will be compiled into Scala code along with the benefits that offers (type-safety). Although coding in a compiled language is obviously a little more clunky and verbose than a dynamic language, refactoring is much easier thanks to the compiler’s checks.
Templates are typically put into the views
directory. The simplest
template is a blank file. For example, views/simple.scala.html
would
get compiled into a object defined as follows:
package views.html
import play.twirl.api._
import play.twirl.api.TemplateMagic._
import play.api.templates.PlayMagic._
import models._
import controllers._
import play.api.i18n._
import play.api.mvc._
import play.api.data._
import views.html._
/**/
object simple extends BaseScalaTemplate[
play.twirl.api.HtmlFormat.Appendable,Format[play.twirl.api.HtmlFormat.Appendable]](play.twirl.api.HtmlFormat)
with play.twirl.api.Template0[play.twirl.api.HtmlFormat.Appendable] {
def apply():play.twirl.api.HtmlFormat
.Appendable = {
_display_ {
Seq[Any]()}
}
def render(): play.twirl.api.HtmlFormat.Appendable
= apply()
def f:(() => play.twirl.api.HtmlFormat.Appendable)
= () => apply()
def ref: this.type = this
}
It’s fairly verbose for something that displays nothing, but I’ve never had to look at this generated content other than to see how it works.
We can change the views/simple.scala.html
file to this:
@()
A template which takes 0 arguments. The generated code is the same as
above. Adding a single argument such as an Int
will change the types slightly.
The new template is:
@(index: Int)
And the generated code, omitting some unchanged code, becomes:
object simple extends BaseScalaTemplate[play.twirl.api.HtmlFormat.Appendable,Format[play.twirl.api.HtmlFormat.Appendable]](play.twirl.api.HtmlFormat) with play.twirl.api.Template1[Int,play.twirl.api.HtmlFormat.Appendable] {
def apply/*1.2*/(index: Int):play.twirl.api.HtmlFormat.Appendable = {
_display_ {
Seq[Any]()}
}
def render(index:Int): play.twirl.api.HtmlFormat.Appendable = apply(index)
def f:((Int) => play.twirl.api.HtmlFormat.Appendable) = (index) => apply(index)
}
It is mostly the same accept that the Int
type has been added as a
function argument in various places, and as a type parameter in other
places.
Let’s add some content to the template and check out the result. Here’s the new template, display the index variable in a paragraph tag:
@(index: Int)
<p>@index</p>
And the new generated code is the same except for the apply
function
// ... unchanged code elided
def apply/*1.2*/(index: Int):play.twirl.api.HtmlFormat.Appendable = {
_display_ {
Seq[Any](format.raw/*1.14*/("""
"""),format.raw/*2.1*/("""<p>"""),_display_(/*2.5*/index),format.raw/*2.10*/("""</p>"""))}
}
format.raw
and _display_
are used to render raw HTML and escaped
content, respectively.
Supported code
The template engine is particular about some of the code that’s used. For example, if you want to use an if-else block, you have to write it like:
@if(condition) {
<p>true</p>
} else {
<p>false</p>
}
Rather than:
@if(condition) {
<p>true</p>
}
@else {
<p>false</p>
}
Imports are okay:
@import play.api.mvc
<p>test</p>
val
declarations are not:
@(index: Int)
@val test = 1
<p>@test</p>
Unless you combine it with an import into a single line:
@import play.api.mvc; val test = 1;
<p>@test</p>
This may be an oversight of the Twirl parser. It’s also possible to
accomplish something similar via a reusable block
.
@test = { 1 }
<p>@test</p>
or reusable code block
:
@test = @{ 1 }
<p>@test</p>
This block gets written as a def
in the generated object:
def apply/*1.2*/(index: Int):play.twirl.api.HtmlFormat.Appendable = {
_display_ {
def /*3.2*/test/*3.6*/ = 1;
Seq[Any](format.raw/*1.14*/("""
"""),format.raw/*3.15*/("""
"""),format.raw/*4.1*/("""<p>"""),_display_(/*4.5*/test),format.raw/*4.9*/("""</p>"""))}
}
Partials
Rails coined the term partials
for resuable templates that can be
included in other templates. I use an underscore prefix to denote
partial templates. Let’s say we want have the following models:
package models
case class Student(id: Int, name: String)
case class Course(
name: String,
students: Seq[Student])
And the following views views/_studentTable.scala.html
:
@(students: Seq[models.Student])
<ul>
@for(student <- students) {
<li>@student.name</li>
}
</ul>
and views/course.scala.html
:
@(course: models.Course)
<h1>@course.name</h1>
@_studentTable(course.students)
View Configuration
When we have many templates calling different partials, we may want to
configure the partials in different ways as determined by the
controller. I’ve found the most straightforward way of dealing with this
is to create some view configuration object which gets passed to
template or partial, and contains and variables or methods required by
the view. For example, if we wanted to override how the partial renders
students we could do the following. In views/Config.scala
:
package views
import models._
case class Config(
render: Student => String = _.name)
Then we change modify the views/_studentTable.scala.html
to add this
parameter:
@(students: Seq[models.Student],
config: views.Config)
<ul>
@for(student <- students) {
<li>@config.render(student)</li>
}
</ul>
Then we can have the students’ ids appear when listed in the course view:
@(course: models.Course)
<h1>@course.name</h1>
@_studentTable(
course.students,
views.Config(render = s => s.name + " " + s.id))
Forms
Although the documentation has a good explanation of how to use forms and form validation, it doesn’t have an opinion on how to organize things. I’ll go over my preferred way of organizing this.
Let’s say we want validation for creating a Course
instance. I create
an object CourseForm
in a forms
package containing two members.
object CourseForm {
import play.api.data._
import Forms._
val mapping = Forms.mapping(
"name" -> nonEmptyText,
"students" -> seq(StudentForm.mapping)
)(Course.apply)(Course.unapply)
val form = Form(mapping)
}
object StudentForm {
import play.api.data._
import Forms._
val mapping = Forms.mapping(
"id" -> number,
"name" -> nonEmptyText
)(Student.apply)(Student.unapply)
val form = Form(mapping)
}
First, there is a
mapping: Mapping[T]
which makes it easy to bind form fields to an object. Next, there is a
form:
Form[T]
which is used in form rendering and validation. CourseForm.mapping
makes uses of StudentForm.mapping
instance, which is defined similarly
allowing for easier code reuse.
All of the docs regarding form submission show passing the mapping directly to the form, but separating things like this provides a lot of flexibility.
Form Validators
The validators that are built-in will get you most of the way you want,
but there are many cases for custom validators. One general validator I’ve
commonly had need of which is not part of the play.api.data.Forms
object is what I call choice
. choice
validates that the input is one
in a given list of choices. We can define choice
like this:
def choice[T](mapping: Mapping[T], choices: Set[T]): Mapping[T] =
mapping.verifying { t =>
choices.contains(t)
}
and use it like so:
import play.api.data._
val sizes = choice(
nonEmptyText,
Set("small, medium, large")))
val myForm = Form(
Forms.single("sizes" -> sizes))
Anorm
Here’s an example of using anorm to read a SQL table. First, the table:
CREATE TABLE students (
id int PRIMARY KEY,
name varchar(500)
);
We define a corresponding case class first:
case class Student( id: Int, name: Option[String] )
Then we use anorm to query for it:
import anorm._
def getStudents: Seq[Student] = {
import play.api.Play.current
DB.withConnection { implicit connection =>
SQL"select * from students".as(parser.*)
}
}
val parser: RowParser[Student] = {
import SqlParser._
int("id") ~
str("name").?
.map ( flatten )
.map ( Student.apply _ tupled )
}
While this works well for case classes, if we’re using Scala 2.10 and have more than the member-limit of fields we need to populate, what can we do?
Assuming there is some class with more than 22 members, we can define a parser as follows:
val parser: RowParser[Student] = {
import SqlParser._
val subParser1 = str("field1") ~
str("field2") ~
// ...
str("field22")
.map(flatten)
val subParser1 = str("field23") ~
str("field24") ~
// ...
str("field44")
.map(flatten)
// now combine the parsers
subParser1 ~ subParser1 map flatten map {
case (
(field1, ..., field22),
(field23, ..., field24)) =>
// create instance
}
}
Testing
The provided manual has great information on testing, but here are a few extra bits of information I’ve come to find useful.
Testing Controllers
Pre 2.4 there is no built-in DI, and although controller injection is supported, the default way to create a controller is to create a companion object. This makes it difficult to unit test. The simplest way to get around this, especially in a codebase that already exists, is to change the companion object into a trait, rename it appropriately, then create a new companion object that just extends from this trait. Say we have the following controller:
object MyController extends Controller {
def index = Action { Ok }
}
We change it to this instead:
trait MyControllerLike extends Controller {
def index = Action { Ok }
}
object MyController extends MyControllerLike
This allows us to easily unit test this controller by extending this trait in a test class:
class TestController extends MyControllerLike {
// override some method
}
// test the index method
val controller = new TestController
val result = controller.index(FakeRequest())
// confirm result is as expected
Test Logging
Sometimes we want to log during test runs. Assuming you are using the Play logger to do this, and create a play logger for each class by mixing in a trait like this:
trait LoggerSupport { self =>
implicit lazy val logger =
play.api.Logger(self.getClass.getName.replaceAll("\\$$", ""))
}
Then you can add a test/resources/logger.xml
file to
configure test logging.