Starting from scala 2.10 version , Slick will be by defaut the database manager. In addition, Playframework also will be based on scala 2.10 since version 2.1.
In this tutorial, I will try to present an example using the last version of slick and scalatest based on scala 2.10.0-M7 and a developpment version of playframework
This post follow this famous tutorial !
Setup Slick in Play 2
If you don’t already have a Play 2 project, then create a new one after installing Play 2:
play new mySlickApp
Choose Scala as the language for the project.
The Slick library needs to be added to the Play project.
Edit the project/Build.scala file and update the dependencies:
val appDependencies = Seq(
"com.typesafe" % "slick_2.10.0-M7" % "0.11.1"
)
Then if you are using an Eclipse or IntelliJ Play can automatically create the project for you using either:
play idea or play eclipse
Note that we created the project files after updating the dependencies, so the projects would be configured with the required libraries. If the dependencies change in the future, just re-run the commands to create the projects.
You can now start the application from within your project’s root directory:
play ~run
Verify that the server is running by opening the following URL in your browser: http://localhost:9000
For local testing we will use an in-memory “h2” database. To setup Play to use that database, edit the conf/application.conf file and uncomment or add the following lines:
db.default.driver=org.h2.Driver db.default.url="jdbc:h2:mem:play"
The final setup step is to provide Squeryl a database connection, but we still want to use the standard Play configuration system to get the database connection information. This is easily done by adding a Global class that can hook into the startup phase of the Play application lifecycle. Create a new file named app/Global.scala containing:
import play.api.db.DB
import play.api.GlobalSettings
// Use H2Driver to connect to an H2 database
import scala.slick.driver.H2Driver.simple._
// Use the implicit threadLocalSession
import Database.threadLocalSession
import play.api.Application
import play.api.Play.current
object Global extends GlobalSettings {
override def onStart(app: Application) {
lazy val database = Database.forDataSource(DB.getDataSource())
}
}
On application startup the configuration parameter will be used to determine which driver to use to setup the database connection.
If you reload the http://localhost:9000 webpage in your browser, everything should still be working and you should see the following message in the Play STDOUT log:
[info] play - database [default] connected at jdbc:h2:mem:play
Create an Entity
Now lets create a simple entity object that will be used to persist data into the database. Create a new file named app/models/Bar.scala containing:
package models
import scala.slick.driver.H2Driver.simple._
case class Bar(id: Option[Int] = None, name: String)
object Bars extends Table[Bar]("bar") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
// This is the primary key column
def name = column[String]("name")
// Every table needs a * projection with the same type as the table's type parameter
def * = id.? ~ name <>(Bar, Bar.unapply _)
}
This is a very simple entity that will store a list of Bar objects. Each Bar has a name and an id property for the primary key. The case class in Scala is immutable and basically supercharges a class, adding a number of syntactic conveniences. It also allows it to be used for pattern matching, which can be quite handy when matching form values returned from the client. The Bars object is an instance of Table Bar that Slick will map into the database.
Begineer about slick, I will not use evolution to create database but let slick create it for me, let’s modify Global.scala as bellow :
import play.api.db.DB
import play.api.GlobalSettings
// Use H2Driver to connect to an H2 database
import scala.slick.driver.H2Driver.simple._
// Use the implicit threadLocalSession
import Database.threadLocalSession
import play.api.Application
import play.api.Play.current
import models.Bars
object Global extends GlobalSettings {
override def onStart(app: Application) {
lazy val database = Database.forDataSource(DB.getDataSource())
database .withSession {
Bars.ddl.create
}
}
}
reload the http://localhost:9000 webpage to create bar table on your local in-memory database. Test the Model
The testing support in Play 2 is very powerful and fits well with the Test Driven Development style. Play 2 with Scala uses specs2 as the default for testing but we prefer ScalaTest. Lets create a simple test for the Bar model object. Start by adding the ScalaTest dependency to the project and modifying the testOptions setting. Since scala 2.10.0-M6 scala-actor is an independent jar and must be add as dependencies. Update the project/Build.scala file to contain:
val appDependencies = Seq(
"org.scalatest" % "scalatest_2.10.0-M7" % "2.0.M4-2.10.0-M7-B1" % "test",
"org.scala-lang" % "scala-actors" % "2.10.0-M7" % "test",
"com.typesafe" % "slick_2.10.0-M7" % "0.11.1"
)
val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
testOptions in Test := Nil
// Add your own project settings here
)
Now create a new file named test/BarSpec.scala containing.
import models.{Bars, Bar}
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
// Use H2Driver to connect to an H2 database
import scala.slick.driver.H2Driver.simple._
// Use the implicit threadLocalSession
import Database.threadLocalSession
import play.api.test._
import play.api.test.Helpers._
class BarSpec extends FlatSpec with ShouldMatchers {
"A Bar" should "be creatable" in {
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
Bars.ddl.create
Bars.insert(Bar(None, "foo"))
val b = for(b <- Bars) yield b
b.first.id.get should equal(1)
}
}
}
This test uses a an in-memory database to run the test. The body of the test simply creates a new instance of Bar and tests that the id is equal to one. Different from Play 1, test are run from the command line using:
play test
If the tests worked, then you should see the following message in the Play STDOUT log:
[mySlickApp] $ test [info] BarSpec: [info] A Bar [info] - should be creatable [info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0 [success] Total time: 2 s, completed Oct 9, 2012 12:28:28 PM
If you’d like to have the tests run whenever the source changes then run:
play ~test
You can keep both the ~run and ~test commands running in the background. This allows you to quickly test the application from both programmatic unit / functional tests and from manual browser tests. Creating Bars From a Web Form
Now lets add a basic web UI for creating new Bar objects. Note that this code will not compile until this entire section is completed.
First update the app/controllers/Application.scala file to contain:
package controllers
import play.api._
import play.api.mvc._
import play.api.data.Form
import play.api.data._
import play.api.data.Forms._
// Use H2Driver to connect to an H2 database
import scala.slick.driver.H2Driver.simple._
// Use the implicit threadLocalSession
//import Database.threadLocalSession
import scala.slick.session.Session
import play.api.libs.json._
import models.{Bar, Bars}
import play.api.db._
import play.api.Play.current
// Use H2Driver to connect to an H2 database
import scala.slick.driver.H2Driver.simple._
// Use the implicit threadLocalSession
import Database.threadLocalSession
object Application extends Controller {
lazy val database = Database.forDataSource(DB.getDataSource())
val barForm = Form(
mapping(
"name" -> text
)
((name) => Bar(None, name))
((bar: Bar) => Some(bar.name))
)
def index = Action {
Ok(views.html.index(barForm))
}
def addBar = Action {
implicit request =>
barForm.bindFromRequest.value map {
bar =>
database withSession {
(Bars insert bar)
}
Redirect(routes.Application.index())
} getOrElse BadRequest
}
}
The barForm provides a mapping from a request parameter named name to the name property on the Bar case class (via it’s constructor). The index method has been updated to pass an instance of the barForm into the index template. We will update that template next. The addBar method binds the request parameters into an object named bar then in a session the bar is inserted into the database. Then the user is redirected back to the index page. If the request parameters could not be mapped to a Bar using the barForm then a BadRequest error is returned.
Now we need to update the app/views/index.scala.html template to contain:
@(form: play.api.data.Form[Bar])
@main("Welcome to Play 2.0") {
@helper.form(action = routes.Application.addBar) {
@helper.inputText(form("name"))
<input type="submit"/>
}
}
The template now takes a Form[Bar] parameter which is passed from the index method on the Application controller. Then in the body of the template a new HTML form is rendered using Play 2’s form helper. The form contains an HTML field for the name and a submit button. Notice that the action of the form points from the route to the Application controller’s addBar method.
If you look in the console window at this point you will see the error “value addBar is not a member of controllers.ReverseApplication”. This is because the route file is compiled and the view is checked for a valid route. But we haven’t created a route yet, so edit the conf/routes file and add a new line with the following:
POST /bars controllers.Application.addBar
This creates a HTTP route that maps POST requests for the /bars URL to the addBar method.
Now refresh http://localhost:9000 in your browser and you should see the very basic form for adding new Bar objects. If successful, after adding a new Bar the browser should just redirect back to the index page.
Now lets add a RESTful service to the application that will return all of the Bar objects as JSON-serialized data. Start by adding a new method to the app/controllers/Application.scala file:
def getBars = Action {
val json = database withSession {
val bars = for (b <- Bars) yield b.name.toString
Json.toJson(bars.list)
}
Ok(json).as(JSON)
}
The getBars method fetches the Bar objects from the database using Slick dsl and then creates a JSON representation of the list of Bar objects and returns the JSON data.
Now add a new route to the conf/routes file:
GET /bars controllers.Application.getBars
This maps GET requests for /bars to the getBars method.
Try this out in your browser by loading: http://localhost:9000/bars
You should see a list of the Bar objects you’ve created serialized as JSON.
The syntax of the query demonstrates the power of Slick type-safe query language and the power of Scala to create DSLs. The json val is then returned in an Ok (HTTP 200 status code response) with the content type set to application/json (the value of JSON).
References:
- Slick: http://slick.typesafe.com
- Playframework: http://www.playframework.org , https://github.com/playframework/Play20
- Scala: http://www.scala-lang.org/downloads
- Squeryl + Playframework + Scala: http://www.artima.com/articles/play2_scala_squeryl.html