Porting to Scala

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.

Models

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:

Models

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.

Models

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:

Models

As a lot of new things were introduced so fast, the best idea would be to review them one by one:

  1. We import the 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.
  2. The next import is the anorm package, which allows us to use a specific type such as Pk and especially the SQL case class.
  3. The 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.
  4. 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.
  5. The 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.
  6. This point is twofold; we see that 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.
  7. Still in the same transaction, we can now retrieve the underlying connection to the database to create the user in the database. For that, we can simply ask for this connection using the withConnection function that will execute its body in a classic JDBC connection.
  8. The 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.
  9. So it allows us to replace placeholders and set it directly to the underlying PreparedStatement.
  10. Add also to execute the query (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.

Note

For this example, we can simply copy the 1.sql file from the Java project as the same structure has been defined.

Parsing the DB result

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).

Parsing the DB result

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.

Note

get[String]("column") will create an SQL parser that parses the current row in the result set for a column named column and returns it as a String value.

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.

Note

The mapping is not usable as is when calling the database. At this stage, we must also tell, for instance, if we expect zero or one row (singleOpt), a single result (single) or several (*).

Speaking with the browser

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.

Note

However, we're lucky because Scala 2.10 has unleashed the power of macros. Given that Play! 2.1 is using this Scala version, some work has been done towards using macros to generate the formatting boilerplates. But this is only available for the JSON formatting use case, not yet for forms.

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:

Speaking with the browser

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.

Note

Actually, functions to be passed to a form definition are just conversions between the values from a request and an object from the database (for instance).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset