Until now, in this chapter, we have talked about the Java API that Play! 2 provides to deal with data, nevertheless there is also a Scala version. We'll have a quick overview here by implementing the same workflow (validation, forms, persistence).
Actually, both APIs look the same, but in Scala, binding is very often our job, whereas in Java, it was the job of the reflection-based tools—Scala doesn't have many tools like that, but times are changing with its 2.1 release.
The first impact regarding this is that, in Scala, binding form instances is our responsibility. Then there is the communication with the database, where in Java, we had the Model
class helping us to deal with the Ebean ORM. On the other hand, the Scala database API takes a completely different direction called Anorm—which stands for Anorm is Not an Object Relational Mapper
—because it relies on SQL rather than automatic mapping.
To start with, we'll need to activate the database plugin; this is done in exactly the same way as in the Java version. So we're already prepared to step into the model classes definition.
As we'll need the same classes as in the Java version of this example, we'll create a User
class and an Address
class. For now, we'll tackle the User
class only, as the Address
class will be very similar. So, the following screenshot shows what the User
class looks like:
Pretty trivial. User
defines its structure (containing a special type for its email
field) and then it imports the DB API to create connections and transactions.
For the search and persistence tasks, we can see (in the next screenshot) the first usage of Anorm, which is a relational database access layer that supersedes JDBC by providing a better API—less verbose, binding back and forth with the domain model using Scala features such as pattern matching, for instance.
In this save
method, we can see that a choice will be made over creating or updating the data in the database. The following screenshot shows how create
can be defined:
As a lot of new things were introduced so fast, the best idea would be to review them one by one:
DB
API from Play! 2; it will provide us with the ability to wrap database accesses within a transaction or at least give us a connection to a data store.anorm
package, which allows us to use a specific type such as Pk
and especially the SQL
case class.Pk
type is helpful to define primary keys and is similar to Option
; having said that, it can either be NotAssigned
or Id(x)
—very helpful when generated primary keys are used.User
keeps a heavy reference to Address
, as it's not an Option
type; we can imagine that it will be eagerly fetched with the user.save
method on User
is able to determine if the user already exists (based on the email
value) and then asks whether to create it or update it. So, checks and persists are done in a single transaction—which are implemented as the body of the withTransaction
function that ensures a transaction is present from the start of its body until its end where a commit takes place.Pk
can be used as a classic Option
and then we call a function of its companion (a companion of a class can be used to create static functions) that is able to search on its potential value (for illustrative purposes only because email
will always be of the Id
type)—this search will be covered in the next section.withConnection
function that will execute its body in a classic JDBC connection.SQL
case class is providing us with a way to create our custom SQL queries—it's a kind of wrapper around string and JDBC prepared statements.PreparedStatement
.INSERT
in our case).By the way, we can see that Anorm is not an ORM (hence its name), so it cannot really see the structure of the data that it will have to handle. Because of this, it is not able to generate the DDL for us—that's a drawback of such a choice, but in favor of a lot of other advantages that are beyond the scope of this book. Thus we, as developers, are responsible for creating and maintaining the DDL ourselves.
With Anorm, we're able to retrieve data from a database in several ways. Now we'll see the parser one; nevertheless, it is worth checking its documentation for further help on this topic (http://localhost:9000/@documentation/ScalaAnorm
).
In this section, we'll see how we can retrieve data from the database as domain model instances by defining a mapping in the form of a SQL parser (manual ORM).
Both the User
and the Address
companions have been shown in the previous screenshot in order to have the picture at one glance.
The key points are the functions of SqlParser
, which are meant to build an SQL parser (of course), that's what is being done in the withAddress
and simple
values. Indeed, SqlParser
defines the structure to be taken over a database's result set. This enables us to combine at will, but also to define several for a single model depending on the amount of data retrieved or to create joins for external resources such as the user's address.
The syntax can help us build very complex stuff and has the advantage of being oversimple. For instance, in our two-level fetching for User
, we combined SQL parsers of type String
, Int
, or Boolean
with another one of type Address
, which is defined in the Address
companion.
The composition of such parsers is done using the ~
operator. Such compositions' results will then be mapped using pattern matching and retrieved as combined values to be used to create a user.
That was all about parsing (recall that it is only one of the several techniques that Anorm is able to apply on a result set). Hence we've now defined the mapping between the relational world and the domain model one.
Looking at the load
and all
functions, we'll see that it will be used in the as
method of SQL, which is simply executing the SQL and the mapping.
This last section will quickly cover how to deal with server-side forms.
As we said earlier, Scala doesn't have great introspection libraries in the current version, so Play! 2 overcame this problem by providing a very neat and intuitive API to define a mapping between the outside world and the server-side one.
This API is in the play.api.data
package and notice especially the Forms
object that defines plenty of mapping functions, satisfying almost all common use cases such as String
, Option
, Seq
, embedded structures, constraints (Email
, Min
, Max
), and so on.
In the Scala world, we create mappings by defining the structure we expect to come from or to be rendered to the outside world, and then we attach a constructor (apply
) and extractor (unapply
) to it. Let's see that in action:
Not much to say; a mapping is defined as a map between outside-world keys and server-side representation (globally, a type plus some constraints). Furthermore, all functions are self descriptive, thanks to a well designed API: a DSL.
Having defined the structure, we see that we end by passing two functions, which are the "so-called" apply
constructor and the unapply
extractor. Because we used case
classes, these functions are automatically created and maintained by the compiler itself.
And then the rest, like the form usage and so on, is 90 percent the same as in Java (because the syntaxes differ). So we can mimic the actions from there.