Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Play2 Up & Running

Play2 Up & Running

Presented at the Play Framework NYC meetup, this is a guided introduction to building a web application with Play 2.

The finished code is available here https://github.com/play-nyc/play-gilt

Mark Wunsch

April 17, 2013
Tweet

More Decks by Mark Wunsch

Other Decks in Programming

Transcript

  1. building blocks • `play new my-app` • `play run` •

    play.api.mvc.Controller • play.api.mvc.Action • play.api.libs.ws.WS • play.api.Configuration • scala.concurrent.Future • play.api.libs.json.Json • play.api.mvc.AsyncResult • play.api.templates.Html • controllers.Assets • play.api.test.Helpers • `play clean compile stage`
  2. mark:~/Projects $ play new my-app _ _ _ __ |

    | __ _ _ _| | | '_ \| |/ _' | || |_| | __/|_|\____|\__ (_) |_| |__/ play! 2.1.1 (using Java 1.6.0_43 and Scala 2.10.0), http://www.playframework.org The new application will be created in /Users/mwunsch/Projects/my-app What is the application name? [my-app] > my-app Which template do you want to use for this new application? 1 - Create a simple Scala application 2 - Create a simple Java application > 1 OK, application my-app is created. Have fun!
  3. mark:~/Projects/my-app $ play [info] Loading global plugins from /Users/mwunsch/.sbt/plugins [info]

    Loading project definition from /Users/mwunsch/Projects/my-app/project [info] Set current project to my-app (in build file:/Users/mwunsch/Projects/my-app/) _ _ _ __ | | __ _ _ _| | | '_ \| |/ _' | || |_| | __/|_|\____|\__ (_) |_| |__/ play! 2.1.1 (using Java 1.6.0_43 and Scala 2.10.0), http://www.playframework.org > Type "help play" or "license" for more information. > Type "exit" or use Ctrl+D to leave this console. [my-app] $
  4. [my-app] $ help play Welcome to Play 2.1.1! These commands

    are available: ----------------------------- classpath Display the project classpath. clean Clean all generated files. compile Compile the current application. console Launch the interactive Scala console (use :quit to exit). dependencies Display the dependencies summary. dist Construct standalone application package. exit Exit the console. h2-browser Launch the H2 Web browser. license Display licensing informations. package Package your application as a JAR. play-version Display the Play version. publish Publish your application in a remote repository. publish-local Publish your application in the local repository. reload Reload the current application build file. run <port> Run the current application in DEV mode. test Run Junit tests and/or Specs from the command line eclipse generate eclipse project file idea generate Intellij IDEA project file sh <command to run> execute a shell command start <port> Start the current application in another JVM in PROD mode. update Update application dependencies. Type `help` to get the standard sbt help.
  5. [my-app] $ run [info] Updating {file:/Users/mwunsch/Projects/my-app/}my-app... [info] Done updating. ---

    (Running the application from SBT, auto-reloading is enabled) --- [info] play - Listening for HTTP on /0:0:0:0:0:0:0:0%0:9000 (Server started, use Ctrl+D to stop and go back to the console...)
  6. mark:~/Projects/my-app $ tree. . !"" README !"" app # !""

    controllers # # $"" Application.scala # $"" views # !"" index.scala.html # $"" main.scala.html !"" conf # !"" application.conf # $"" routes !"" logs # $"" application.log !"" project # !"" Build.scala # !"" build.properties # !"" plugins.sbt !"" public # !"" images # # $"" favicon.png # !"" javascripts # # $"" jquery-1.9.0.min.js # $"" stylesheets # $"" main.css !"" target $"" test !"" ApplicationSpec.scala $"" IntegrationSpec.scala
  7. # Routes # This file defines all application routes (Higher

    priority routes first) # ~~~~ # Get all active sales GET / controllers.Application.index # Get active sales by store GET /:store controllers.Stores.activeSalesByKey(store) # Get a particular sale GET /:store/:sale controllers.Sales.show(sale, store) # Get a particular product GET /product/:id controllers.Products.show(id: Long) # Map static resources from the /public folder to the /assets URL path GET /assets/*file controllers.Assets.at(path="/public", file)
  8. val TODO = Action { NotImplemented[play.api.templates.Html]( views.html.defaultpages.todo() ) } trait

    Action[A] extends EssentialAction { def apply(request: Request[A]): Result }
  9. package test import org.specs2.mutable._ import play.api.test._ import play.api.test.Helpers._ class StoresSpec

    extends Specification { "Stores#activeSalesByKey" should { "be successful" in { val res = controllers.Stores.activeSalesByKey("women")(FakeRequest()) status(res) must equalTo(OK) } } }
  10. [my-app] $ test [info] StoresSpec [info] [info] Stores#activeSalesByKey should [error]

    x be successful [error] '501' is not equal to '200' (StoresSpec.scala:14) [info] [info] [info] Total for specification StoresSpec [info] Finished in 73 ms [info] 1 example, 1 failure, 0 error
  11. ... import scala.concurrent.Future import play.api.libs._ import play.api.libs.concurrent.Execution.Implicits._ object Stores extends

    Controller { def activeSalesByKey(store: String) = Action { val eventualResponse: Future[ws.Response] = { ws.WS.url(API_ROOT+"sales/"+store+"/active.json").get() } Async { eventualResponse map { response => Logger.info("API Response: %s %s".format(response.status, response.statusText)) Status(response.status) { response.body } } } } private val API_ROOT = "https://api.gilt.com/v1/" }
  12. ... object Stores extends Controller { def activeSalesByKey(store: String) =

    Action { val eventualResponse: Future[ws.Response] = { signedApiRequest("sales/"+store+"/active.json").get() } Async { eventualResponse map { response => Status(response.status) { response.body } } } } private def signedApiRequest(store: String) = { val apiKey = Play.maybeApplication flatMap { app => app.configuration.getString("gilt.apikey") } getOrElse "" ws.WS.url(API_ROOT+path).withQueryString("apikey" -> apiKey) } ... }
  13. # In conf/application.conf # Gilt API key, found in the

    $GILT_API_KEY env var. gilt.apikey=${?GILT_API_KEY}
  14. mark:~/Projects/my-app $ curl localhost:9000/women {"sales":[{"name":"Wardrobe Essentials: Everything You Need","sale":"https://api.gilt.com/v1/sales/women/wardrobe- essentials-413/detail.json","sale_key":"wardrobe-

    essentials-413","store":"women","sale_url":"http://www.gilt.com/ sale/women/wardrobe-essentials-413/ss? utm_medium=api&utm_campaign=play- tutorial&utm_source=salesapi","image_urls":{"744x281": [{"url":"http://cdn1.gilt.com/images/share/uploads/ 0000/0002/0068/200686017/orig.jpg ...
  15. class StoresSpec extends Specification { "Stores#activeSalesByKey" should { "be successful"

    in { running(FakeApplication()) { val res = controllers.Stores.activeSalesByKey("women")(FakeRequest()) status(res) must equalTo(OK) } } } }
  16. [my-app] $ test [info] StoresSpec [info] [info] Stores#activeSalesByKey should [info]

    + be successful [info] [info] [info] Total for specification StoresSpec [info] Finished in 324 ms [info] 1 example, 0 failure, 0 error
  17. building blocks ✓ `play new my-app` ✓ `play run` ✓

    play.api.mvc.Controller ✓ play.api.mvc.Action ✓ play.api.libs.ws.WS ✓ play.api.Configuration ✓ scala.concurrent.Future • play.api.libs.json.Json ✓ play.api.mvc.AsyncResult • play.api.templates.Html • controllers.Assets ✓ play.api.test.Helpers • `play clean compile stage`
  18. import play.api.libs.json._ object Stores extends Controller { def activeSalesByKey(store: String)

    = Action { ... Async { eventualResponse map { response => response.status match { case OK => Ok( unmarshalSales(response.json).toString ) case _ => Status(response.status) { response.body } } } } } private def unmarshalSales(json: JsValue): Seq(model.Sale) = { json.as[Seq[Sale]] } ... }
  19. [my-app] $ compile [info] Compiling 1 Scala source to /Users/mwunsch/Projects/play-

    gilt/target/scala-2.10/classes... [error] /Users/mwunsch/Projects/play-gilt/app/controllers/ Stores.scala:29: No Json deserializer found for type Seq[models.Sale]. Try to implement an implicit Reads or Format for this type. [error] json.as[Seq[models.Sale]] [error] ^ [error] one error found [error] (compile:compile) Compilation failed [error] Total time: 1 s, completed Apr 17, 2013 10:46:58 AM
  20. package models import play.api.libs.json._ case class Sale( name: String, key:

    String, giltUrl: String, apiUrl: String ) object Sale { implicit val readSale: Reads[Sale] = new Reads[Sale] { def reads(json: JsValue): JsResult[Sale] = { for { name <- (json \ "name").validate[String] key <- (json \ "sale_key").validate[String] url <- (json \ "sale_url").validate[String] api <- (json \ "sale").validate[String] } yield Sale(name, key, url, api) } } }
  21. ... import models.Sale object Stores extends Controller { ... private

    def unmarshalSales(json: JsValue): Seq(Sale) = { (json \ "sales").validate[Seq[Sale]].fold( err => Nil, success => success ) } ... }
  22. ... object Stores extends Controller { def activeSalesByKey(store: String) =

    Action { ... Async { eventualResponse map { response => response.status match { case OK => { val sales = unmarshalSales(response.json) Ok( views.html.store(sales, store) ) } case _ => Status(response.status) { response.body } } } } } ... }
  23. @(sales: Seq[Sale], store: String) @main(store) { <ul class="active-sales"> @for(sale <-

    sales) { <li class="sale" id="@sale.key"> <a href="@sale.giltUrl" class="url">@sale.name</a> </li> } </ul> }
  24. mark:~/Projects/my-app $ heroku create Creating whispering-sierra-2474... done, stack is cedar

    http://whispering-sierra-2474.herokuapp.com/ | [email protected]:whispering-sierra-2474.git Git remote heroku added mark:~/Projects/my-app $ git push heroku master Counting objects: 79, done. Delta compression using up to 8 threads. Compressing objects: 100% (70/70), done. Writing objects: 100% (79/79), 42.72 KiB, done. Total 79 (delta 22), reused 0 (delta 0) -----> Play 2.x - Scala app detected -----> Installing OpenJDK 1.6...done -----> Building app with sbt -----> Running: sbt clean compile stage
  25. building blocks ✓ `play new my-app` ✓ `play run` ✓

    play.api.mvc.Controller ✓ play.api.mvc.Action ✓ play.api.libs.ws.WS ✓ play.api.Configuration ✓ scala.concurrent.Future ✓ play.api.libs.json.Json ✓ play.api.mvc.AsyncResult ✓ play.api.templates.Html ✓ controllers.Assets ✓ play.api.test.Helpers ✓ `play clean compile stage`