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:
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:
And the generated code, omitting some unchanged code, becomes:
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:
And the new generated code is the same except for the apply
function
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:
Rather than:
Imports are okay:
val
declarations are not:
Unless you combine it with an import into a single line:
This may be an oversight of the Twirl parser. It’s also possible to
accomplish something similar via a reusable block
.
or reusable code block
:
This block gets written as a def
in the generated object:
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:
And the following views views/_studentTable.scala.html
:
and views/course.scala.html
:
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
:
Then we change modify the views/_studentTable.scala.html
to add this
parameter:
Then we can have the students’ ids appear when listed in the course view:
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.
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:
and use it like so:
Anorm
Here’s an example of using anorm to read a SQL table. First, the table:
We define a corresponding case class first:
Then we use anorm to query for it:
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:
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:
We change it to this instead:
This allows us to easily unit test this controller by extending this trait in a test class:
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:
Then you can add a test/resources/logger.xml
file to
configure test logging.