play!ng with scala

39
ng with Siarzh Miadzvedzeu @siarzh 2. 0

Upload: siarzh-miadzvedzeu

Post on 10-May-2015

8.275 views

Category:

Technology


4 download

DESCRIPTION

Play framework 2.0 presentation for Scala developers.

TRANSCRIPT

Page 1: Play!ng with scala

ng with

Siarzh Miadzvedzeu @siarzh

2.0

Page 2: Play!ng with scala

is

“a web framework for a new era”

& “designed by web developers for web developers”

Guillaume Bort, creator

Page 3: Play!ng with scala

how it was0.x

Page 4: Play!ng with scala

how it was1.0

Page 5: Play!ng with scala

how it was1.2

Page 6: Play!ng with scala

how it was2.0

Page 7: Play!ng with scala

is

• full stack web framework for JVM• high-productive• asynch & reactive• stateless• HTTP-centric• typesafe• scalable• open source• part of Typesafe Stack 2.0

2.0

Page 8: Play!ng with scala

is fun and high-productive

• fast turnaround: change you code and hit reload! :)• browser error reporting• db evolutions• modular and extensible via plugins• minimal bootstrap• integrated test framework• easy cloud deployment (e.g. Heroku)

2.0

Page 9: Play!ng with scala

is asynch & reactive

• WebSockets• Comet• HTTP 1.1 chuncked responses• composable streams handling • based on event-driven, non-blocking Iteratee I/O

2.0

Page 10: Play!ng with scala

is HTTP-centric

• based on HTTP and stateless • doesn't fight HTTP or browser• clean & easy URL design (via routes)• designed to work with HTML5

2.0

“When a web framework starts an architecture fight with the web, the framework loses.”

Page 11: Play!ng with scala

is typesafe where it matters

• templates• routes• configs• javascript (via Goggle Closure)

• LESS stylesheets• CoffeeScript

2.0

all are compiled }+ browser error reporting

Page 12: Play!ng with scala

getting started2.0

3. create new project:

4. run the project:

export PATH=$PATH:/path/to/play20

1. download Play 2.0 binary package

2. add play to your PATH:

$ play new myFirstApp

$ cd myFirstApp $ play run

Page 13: Play!ng with scala

config2.0# This is the main configuration file for the application.# The application languages# ~~~~~application.langs="en" # Database configuration# ~~~~~# You can declare as many datasources as you want.# By convention, the default datasource is named `default`#db.default.driver=org.h2.Driverdb.default.url="jdbc:h2:mem:play"# db.default.user=sa# db.default.password= # Evolutions# ~~~~~# You can disable evolutions if needed# evolutionplugin=disabled...

single conf/application.conf:

Page 14: Play!ng with scala

IDE support2.0 $ eclipsify

$ idea

$ play netbeans

resolvers += {

  "remeniuk repo" at "http://remeniuk.github.com/maven" }

libraryDependencies += {  "org.netbeans" %% "sbt-netbeans-plugin" % "0.1.4"}

Eclipse:

IntelliJ IDEA:

Netbeans: add to plugins.sbt:

Page 15: Play!ng with scala

and SBT2.0 plugins.sbtBuild.scala

import sbt._import Keys._import PlayProject._ object ApplicationBuild extends Build {   val appName = "Your application"  val appVersion = "1.0"   val appDependencies = Seq(    // Add your project dependencies here,  )   val main = PlayProject(    appName, appVersion, appDependencies, mainLang = SCALA  ).settings(    // Add your own project settings here   ) }

addSbtPlugin("play" % "sbt-plugin" % "2.0")

Page 16: Play!ng with scala

routes2.0# Home page

GET / controllers.Application.homePage()GET /home controllers.Application.show(page = "home")

# Display a client.GET /clients/all controllers.Clients.list()GET /clients/:id controllers.Clients.show(id: Long)

# Pagination links, like /clients?page=3GET /clients controllers.Clients.list(page: Int ?= 1)

# With regexGET /orders/$id<[0-9]+> controllers.Orders.show(id: Long)

# 'name' is all the rest part of the url including '/' symbolsGET /files/*name controllers.Application.download(name)

Page 17: Play!ng with scala

reversed routing2.0# Hello actionGET /helloBob controllers.Application.helloBobGET /hello/:name controllers.Application.hello(name)

// Redirect to /hello/Bobdef helloBob = Action {    Redirect( routes.Application.hello("Bob") ) }

Page 18: Play!ng with scala

actions2.0Action(parse.text) { request =>   Ok("<h1>Got: " + request.body + "</h1>").as(HTML).withSession(                           session + ("saidHello" -> "yes")

).withHeaders(                             CACHE_CONTROL -> "max-age=3600",                              ETAG -> "xx"                          ).withCookies(                             Cookie("theme", "blue")                          )}

action: (play.api.mvc.Request => play.api.mvc.Result)

val notFound = NotFoundval pageNotFound = NotFound(<h1>Page not found</h1>)val badRequest = BadRequest(views.html.form(formWithErrors))val oops = InternalServerError("Oops")val anyStatus = Status(488)("Strange response type")

Page 19: Play!ng with scala

controllers2.0package controllersimport play.api.mvc._

object Application extends Controller {

   def index = Action {     Ok("It works!")   }

   def hello(name: String) = Action {     Ok("Hello " + name)   }

def goodbye(name: String) = TODO    }

Page 20: Play!ng with scala

templates2.0 @(title: String)(content: Html)

<!DOCTYPE html> <html>   <head>     <title>@title</title>   </head>   <body>     <section class="content">@content</section>   </body> </html>

@(name: String = “Guest”)

@main(title = "Home") {   <h1>Welcome @name! </h1> }

views/main.scala.html:

views/hello.scala.html:

…are just functions ;)

val html = views.html.Application.hello(name) then from Scala class:

Page 21: Play!ng with scala

database evolutions2.0# Add Post # --- !UpsCREATE TABLE Post (    id bigint(20) NOT NULL AUTO_INCREMENT,    title varchar(255) NOT NULL,    content text NOT NULL,    postedAt date NOT NULL,    author_id bigint(20) NOT NULL,    FOREIGN KEY (author_id) REFERENCES User(id),    PRIMARY KEY (id)); # --- !DownsDROP TABLE Post;

conf/evolutions/${x}.sql:

Page 22: Play!ng with scala

browser error reporting2.0

Page 23: Play!ng with scala

access SQL data via Anorm2.0

// using Parser API import anorm.SqlParser._

val count: Long = SQL("select count(*) from Country").as(scalar[Long].single)

val result:List[String~Int] = {   SQL("select * from Country") .as(get[String]("name")~get[Int]("population") map { case n~p => (n,p) } *) }

import anorm._

DB.withConnection { implicit c =>

  val selectCountries = SQL("Select * from Country")   // Transform the resulting Stream[Row] to a List[(String,String)]  val countries = selectCountries().map(row =>     row[String]("code") -> row[String]("name")  ).toList }

Page 24: Play!ng with scala

and Akka2.0

// schedule sending message 'tick' to testActor every 30 minutesAkka.system.scheduler.schedule(0 seconds, 30 minutes, testActor, "tick")

// schedule a single taskAkka.system.scheduler.scheduleOnce(10 seconds) {  file.delete()}

def index = Action { // using actors, coverting Akka Future to Play Promise  Async {

    (myActor ? "hello").mapTo[String].asPromise.map { response =>      Ok(response)     }   }}

def index = Action { // execute some task asynchronously  Async {

    Akka.future { longComputation() }.map { result =>      Ok("Got " + result)     }   }}

Page 25: Play!ng with scala

streaming response2.0

// Play has a helper for the above:

def index = Action {

  val file = new java.io.File("/tmp/fileToServe.pdf")  val fileContent: Enumerator[Array[Byte]] = Enumerator.fromFile(file)       SimpleResult(    header = ResponseHeader(200, Map(CONTENT_LENGTH -> file.length.toString)),

    body = fileContent  )}

def index = Action {  Ok.sendFile(new java.io.File("/tmp/fileToServe.pdf"))}

Page 26: Play!ng with scala

chunked results2.0

// Play has a helper for the ChunkedResult above:  

def index = Action {

  val data = getDataStream  val dataContent: Enumerator[Array[Byte]] = Enumerator.fromStream(data)    ChunkedResult(    header = ResponseHeader(200),

    chunks = dataContent  )}

Ok.stream(dataContent)

Page 27: Play!ng with scala

Comet2.0lazy val clock: Enumerator[String] = {

    val dateFormat = new SimpleDateFormat("HH mm ss")        Enumerator.fromCallback { () =>      Promise.timeout(Some(dateFormat.format(new Date)), 100 milliseconds)    }  }    def liveClock = Action {    Ok.stream(clock &> Comet(callback = "parent.clockChanged"))  }

and then in template:

<script type="text/javascript" charset="utf-8">   var clockChanged = function(time) { // do something }</script>    <iframe id="comet" src="@routes.Application.liveClock.unique"></iframe>

Page 28: Play!ng with scala

WebSockets2.0

def index = WebSocket.using[String] { request =>     // Log events to the console  val in = Iteratee.foreach[String](println).mapDone { _ =>    println("Disconnected")  }    // Send a single 'Hello!' message  val out = Enumerator("Hello!")    (in, out)}

just another action in controller:

Page 29: Play!ng with scala

caching API2.0 by default uses EHCache, configurable via plugins

Cache.set("item.key", connectedUser)

val user: User = Cache.getOrElse[User]("item.key") {  User.findById(connectedUser)}

// cache HTTP responsedef index = Cached("homePage",600) {  Action {    Ok("Hello world")  }}

Page 30: Play!ng with scala

i18n2.0

application.langs="en,en-US,fr"

home.title=File viewer

files.summary=The disk {1} contains {0} file(s).

conf/application.conf:

conf/messages.en:

val title = Messages("home.title") val titleFR = Messages("home.title")(Lang(“fr")) val summary = Messages("files.summary", d.files.length, d.name)

from Scala class:

from template: <h1>@Messages("home.title")</h1>

Page 31: Play!ng with scala

testing2.0 …using specs2 by default

"Computer model" should {

  "be retrieved by id" in {    running(FakeApplication(additionalConfiguration = inMemoryDatabase())) {        val Some(macintosh) = Computer.findById(21)

      macintosh.name must equalTo("Macintosh")      macintosh.introduced must beSome.which(dateIs(_, "1984-01-24"))       }  }}

Page 32: Play!ng with scala

testing templates2.0

"render index template" in {   val html = views.html.index("Coco")     contentType(html) must equalTo("text/html")   contentAsString(html) must contain("Hello Coco")}

Page 33: Play!ng with scala

testing controllers2.0

"respond to the index Action" in {   val result = controllers.Application.index("Bob")(FakeRequest())     status(result) must equalTo(OK)   contentType(result) must beSome("text/html")   charset(result) must beSome("utf-8")   contentAsString(result) must contain("Hello Bob")}

Page 34: Play!ng with scala

testing routes2.0

"respond to the index Action" in {   val Some(result) = routeAndCall(FakeRequest(GET, "/Bob"))     status(result) must equalTo(OK)   contentType(result) must beSome("text/html")   charset(result) must beSome("utf-8")   contentAsString(result) must contain("Hello Bob")}

Page 35: Play!ng with scala

testing server2.0

"run in a server" in {   running(TestServer(3333)) {       await( WS.url("http://localhost:3333").get ).status must equalTo(OK)     }}

Page 36: Play!ng with scala

testing with browser2.0

"run in a browser" in {

   running(TestServer(3333), HTMLUNIT) { browser =>         browser.goTo("http://localhost:3333")     browser.$("#title").getTexts().get(0) must equalTo("Hello Guest")         browser.$("a").click()         browser.url must equalTo("http://localhost:3333/Coco")     browser.$("#title").getTexts().get(0) must equalTo("Hello Coco")

   }}

…using Selenium WebDriver with FluentLenium

Page 37: Play!ng with scala

Demo2.0

Page 38: Play!ng with scala

Resources2.0http://www.playframework.org/https://github.com/playframework/Play20/wikihttp://www.parleys.com/#st=5&id=3143&sl=4http://www.parleys.com/#st=5&id=3144&sl=13http://www.parleys.com/#id=3081&st=5http://vimeo.com/41094673

Page 39: Play!ng with scala

Questions2.0 ?