Professional Freelancer providing Web Design, Graphic Design and Web Development Services

Play Framework tutorial with Scala

I have been working with Play! Java web framework for a Google App Engine project. Play! is a very good java web framework making it comfortable and effective to create web applications in java, this framework was created with the web developer in mind not the Java programming language. I also have been reading and learning aboutScala programming language which is a very powerful and interesting programming language and platform more on that coming soon.

As i wanted to try Play Scala i created a simple URL shortener application which was useful to learn some Scala, Play! Scala concepts and paradigms.

On this example we will be using java.security for generation of the md5 of the full url this way we can detect if the url is already present in the database and if it is we just use that url, otherwise we save it and return the new shortUrl We will also usescala.Random and Integer.to_s(36) to generate a Base 36 short url.

The last lib worth mention is the Anorm, SQL data access. Anorm is a data access layer that let us “talk” with databases and as far as i understood, the philosophy of it is that the developer should “Take Control of SQL code”.

Nowadays developers work at very high levels of abstractions and this is good for an accelerated development process but it can come at a cost of efficiency. Therefor i quite sympathize with the way Anorm works. At it will make me remember some SQL practices which have been abstracted from me for a while.

I will walk you through what i did and if you have any remarks or tips to the code please let me know as i am open minded and willing to improve.

The code is available at github

Prerequisites

To get this going you should have scala and the Play! framework installed

You can download Scala on the Scala Language website and if you are on a Mac you can read my how to Install Scala on Mac.

For Play! you can head over to the official Play Framework installation guide as is as complete as it gets.

Generate the application with:

cd {your working directory}

play install scala

play new scalaPlayShortener --with scala

cd scalaPlayShortener && play dependencies

Database Migrations

Play has database migrations support know as (evolutions -> http://www.playframework.org/documentation/1.2.2/evolutions), so lets create our evolution.

cd {your working directory}/scalaPlayShorner && mkdir -p db/evolutions

Create a file called 1.sql and add the following SQL:

# Shortener schema
# --- !Ups

CREATE TABLE Shortener (
 id bigint(20) NOT NULL AUTO_INCREMENT,
 shortUrl varchar(12) NOT NULL,
 fullUrl varchar(4096) NOT NULL,
 fullUrlHash varchar(32) NOT NULL,
 PRIMARY KEY (id)
);

# --- !Downs

DROP TABLE Shortener;

The SQL after # — !Ups runs when you are applying the evolution and consequent SQL, on the other site # — !Downs is run when the migration is being reverted.

Routes

We will use 3 routes, first for the landing page with the form, second for the POST request when user submits the long URL and third the route that dispatches/redirect from the short to the long URL

Open the file in conf/routes and add this routes at the top, the file syntax is the same as in the Java version of the framework.

GET  / Application.index
POST / Application.shorten
GET  /{dispatchUrl} Application.dispatch

Scala Tests

Play Scala comes with scala test which is a great tool/DSL. Those who have used RSpec from Ruby land will see that they are very similar.

The scalatest syntax is very clear and does not let any doubt on what the code is doing, for example:

val url = "http://google.com"

Shortener.create(Shortener(url))

// Fetch Some DB Record
val shortenURL = Shortener.find( "fullUrl={fullUrl}")
 .on("fullUrl" -> url )
 .first()

// The DB Record should not be of type None
shortenURL should not be (None)

shortenURL.get.fullUrl should be (url)

Shortener.count().single() should be (1)

The Above code sample contains a should verb which is made available throughShouldMatchers trait. A must verb is also available through the MustMatchers

The full test case for this example application located at test ShortenerTests.scala:

import play._
import play.test._

import org.scalatest._
import org.scalatest.junit._
import org.scalatest.matchers._

import models._
import play.db.anorm._

class ShortenerTests extends UnitFlatSpec with ShouldMatchers with BeforeAndAfterEach {

 override def beforeEach() {
 Fixtures.deleteDatabase()
 } 

 it should "create and retrieve a Shorten Url" in {
 val url = "http://google.com"
 val url2 = "http://playgramework.org"
 Shortener.create(Shortener(url))

 val shortenURL = Shortener.find( "fullUrl={fullUrl}")
 .on("fullUrl" -> url )
 .first()

 shortenURL should not be (None)
 shortenURL.get.fullUrl should be (url)
 shortenURL.get.fullUrlHash should be ("c7b920f57e553df2bb68272f61570210")
 shortenURL.get.shortUrl should fullyMatch regex ("""[a-zA-z0-9]+""")
 Shortener.count().single() should be (1)

 // add url and increase count
 Shortener.create(Shortener(url2))
 Shortener.count().single() should be (2)

 // try to create an already existent url
 Shortener.create(Shortener(url2))

 //the count should be 2 since we are not saving the url just returning the existent one
 Shortener.count().single() should be (2)

 val shortUrl2 = Shortener.find( "fullUrl={fullUrl}")
 .on("fullUrl" -> url2 )
 .first()

 // the url2 properties
 shortUrl2 should not be (None)
 shortUrl2.get.fullUrl should be (url2)
 shortUrl2.get.fullUrlHash should be ("7faf91bbbde823d7c27ad7aaa17e196e")
 shortUrl2.get.shortUrl should fullyMatch regex ("""[a-zA-z0-9]+""")
 }
}

Model iteration

As mentioned above i access the database width Anorm. Anorm offers a Magic helper which allow us to match a case class to the database schema, however we are not required to match every table field if we don’t need to.

case class Shortener(
 id: Pk[Long],
 fullUrl: String,
 fullUrlHash:String,
 shortUrl:String = { Shortener.convertToBase36 }
)

The main Model interaction is done through an Object which extends the Magic helper. The magic helper provides some convenience methods about which you learn more here. As an example the find helper used on the findFirstByFullUrl method:

def findFirstByFullUrl(url:String) = {
 Shortener.find("fullUrl={fullUrl}")
 .on("fullUrl" -> url)
 .first()
}

Below you will find all of the shortener Model used in modes/shortener.scala .

package models

import java.security._
import java.lang.Integer
import scala.util.Random

import play.db.anorm._
import play.db.anorm.defaults._

case class Shortener(
 id: Pk[Long],
 fullUrl: String,
 fullUrlHash:String,
 shortUrl:String = { Shortener.convertToBase36 }
)

object Shortener extends Magic[Shortener] {

 def apply(fullUrl:String) = {
 val urlHash = Shortener.urlHash(fullUrl);
 Shortener.find("fullUrlHash = {urlHash}")
          .on("urlHash" -> urlHash)
          .first()
          .getOrElse {
            new Shortener(NotAssigned, fullUrl, Shortener.urlHash(fullUrl))
          }
 }

 def convertToBase36():String = {
   Integer.toString(new Random().nextInt(100000),36)
 }

 def findFirstByFullUrl(url:String) = {
   Shortener.find( "fullUrl={fullUrl}")
            .on("fullUrl" -> url )
            .first()
 }

 def findFirstByShortlUrl(url:String) = {
   Shortener.find( "shortUrl={shortUrl}")
            .on("shortUrl" -> url )
            .first()
 }

 def urlHash(url:String) = {
   val md = MessageDigest.getInstance("MD5");
   md.update( url.getBytes() );
   md.digest.map(0xFF & _).map { "%02x".format(_) }.foldLeft(""){_ + _}
 }
}

Controllers

In Play Scala the controllers are singletons object which extend play.mvc.Controller. In our case is the Application controller which has 3 methods in which 2 of them take arguments sent to them via the Routes configuration and via a form.

package controllers

import play._
import play.mvc._

import models.Shortener
import views.Application._

object Application extends Controller {

 def index = {
 // Call the views/Application/index.scala.html
 html.index()
 }

 // Get the POST.ed longUrl field from the submited form
 def shorten(longUrl:String) = {
 Shortener.create(Shortener(longUrl))
 val shortenURL = Shortener.findFirstByFullUrl(longUrl)

 // Call the view
 html.index(shortenURL)
 }

 // Get the dispatchUrl from the Routes Configuration
 def dispatch(dispatchUrl:String) = {
 val shortenURL = Shortener.findFirstByShortlUrl(dispatchUrl)
 shortenURL match {
 case None => NotFound
 case shortenURL:Option[Shortener] => Redirect(shortenURL.get.fullUrl)
 }
 }
}

In the controller above we retrieve the data form our Shortener Model and deal with that data, either by executing a Redirect, calling the NotFound and by display the template view.

For displaying the views we call html.index() and html.index(shortenURL), this call will retrieve the template in views/Applicaion/index.scala.html, so if we split the structure it will be views/{ControllerName}/{ActionCalled}.scala.html. Being ActionCalled .index()and .index(shortenURL). The reason for this is that the templates/views files are special Scala source files.

Now how does the template get the data we have passed as theshortenURLparameter? that bring us to the views.

Views

On the file located in views/Applicaion/index.scala.html i have created very basic template, but important thing is how we declare the values which will be made available for the rest of the template, so on the top of the file you can find:

@(
 short:Option[models.Shortener] = None,
 title:String = "Scala Url Shortener"
)

If look at it closer you will find a nameless function @(etc…) with default parameters. The short parameter will be bound to the formal parameter shortenURL. The @ is used to call Scala code. I am still getting to know the way of doing this style of templates.

There are a few more concepts to grasp with regards to the templating system as how to use @action to invoke controller methods and generate URLs.

Conclusion

I am still getting into Scala and the Play! Framework but for me this is obviously good stuff. I know that the above code can be improved and i will do so upon suggestions and future learnings.

There is one thing that kind bugged me which is the templates as they are different and maybe not so web designer friendly, but i will digg more into them and i know that scalate can be used as an alternative.

Overall this is nice stuff and i would like to know your feedback and tips.