I am currently reading 'Play for Scala' by Peter Hilton. I have just got the the end of the first example Play app where you build a paperclip directory.
However upon compiling I get a compilation error telling me that the value 'flash' has not been found. Usually this is a simple mistake I have made but given that I am just following the guide in the book I can't identify a fix.
The error is in lines 52 and 53 in the 'NewProduct' function
Here is the code:
package controllers
import play.api.mvc.{Action, Controller}
import models.Product
import play.api.data.Form
import play.api.data.Forms.{mapping, longNumber, nonEmptyText}
import play.api.i18n.Messages
import play.api.mvc.Flash
object Products extends Controller {
private val productForm: Form[Product] = Form(
mapping(
"ean" -> longNumber.verifying(
"validation.ean.duplicate", Product.findByEan(_).isEmpty),
"name" -> nonEmptyText,
"description" -> nonEmptyText
)(Product.apply)(Product.unapply)
)
def list = Action {implicit request =>
val products = Product.findAll
Ok(views.html.products.list(products))
}
def show(ean: Long) = Action {implicit request =>
Product.findByEan(ean).map {product =>
Ok(views.html.products.details(product))
}.getOrElse(NotFound)
}
def save = Action { implicit request =>
val newProductForm = productForm.bindFromRequest()
newProductForm.fold(
hasErrors = {form =>
Redirect(routes.Products.newProduct()).
flashing(Flash(form.data) + ("error" -> Messages("validation.errors")))
},
success = {newProduct =>
Product.add(newProduct)
val message = Messages("products.new.success", newProduct.name)
Redirect(routes.Products.show(newProduct.ean)).
flashing("success" -> message)
}
)
}
def newProduct = Action { implicit request =>
val form = if(flash.get("error").isDefined)
productForm.bind(flash.data)
else
productForm
Ok(views.html.products.editProduct(form))
}
}
Example is working with Play < 2.3, you may want to check which version you are currently using. With Play > 2.3, request.flash must be used instead. In both case you can use request.flash (which is more explicit).
Related
I am new to Reactivemongo database(2.6), as I am trying to upload/insert json object(which is of key/value pairs, I am sending it to store in database after clicking of Submit button from my UI ) from my localsystem to store in Mongodb using Play Framework/Scala(tried with Play 2.2.3 and 2.3.8). I have tried:
import play.api.libs.json.{JsObject, JsValue, OWrites}
import play.api.mvc.{Action, Controller}
import play.modules.reactivemongo.MongoController
import play.modules.reactivemongo.json.collection._
import scala.concurrent.ExecutionContext.Implicits.global
object Application extends Controller with MongoController {
def jsinfocollection: JSONCollection = db.collection[JSONCollection]("mycollection")
implicit val jsvalueWrites = new OWrites[JsValue] {
println("implicit val jsvalueWrites ...")//getting the message on Play console
override def writes(o: JsValue): JsObject = o match {
case o : JsObject => o
case _ => throw new IllegalArgumentException("Only JsObjects can be stored")
}
}
def createJson = Action.async(parse.json) {
println("createJson calling...")//getting the message on Play console
request =>
jsinfocollection.insert(request.body).map{
println("jsinfocollection.insert...")//getting the message on Play console
r => Created(s"JsonInfo is Saved with result $r")
}
}
}
I created a collection in Mongodb already like: >db.createCollection("mycollection")
{ "ok" : 1 }
but if I give: >db.mycollection.count() - is giving 0, otherwise if I give: db.mycollection.find() - is giving nothing
Please let me know that how can I insert my json data into my required collection("mycollection"). Thanks in advance.
I am getting the following on console:
implicit val jsvalueWrites ...
createJson calling...
jsinfocollection.insert...
[error] play - Cannot invoke the action, eventually got an error: java.lang.IllegalArgumentException: Only JsObjects can be stored
Internal server error, for (POST) [/createJson]
As you are using Action.async(parse.json) you have a JsValue in the request.body
jsinfocollection is only able to store JsObjects (JsObjects are only one type of JsValues, other types are JsArray, JsString, ...)
The following code should do what you are looking for
import play.api.libs.json.{JsObject, JsValue, OWrites}
import play.api.mvc.{Action, Controller}
import play.modules.reactivemongo.MongoController
import play.modules.reactivemongo.json.collection._
import scala.concurrent.ExecutionContext.Implicits.global
object Application extends Controller with MongoController {
def jsinfocollection: JSONCollection = db.collection[JSONCollection]("mycollection")
implicit val jsvalueWrites = new OWrites[JsValue] {
override def writes(o: JsValue): JsObject = o match {
case o : JsObject => o
case _ => throw new IllegalArgumentException("Only JsObjects can be stored")
}
}
def createJson = Action.async(parse.json) {
request =>
jsinfocollection.insert(request.body).map{
r => Created(s"JsonInfo is Saved with result $r")
}
}
}
Maybe there is a simpler way to create the jsvalueWrites
No Json serializer as JsObject found for type play.api.libs.json.JsObject
And maybe updating to the latest version helps
Note: Validation that the string is a valid JsValue will be done by the framework in parse.json, validation that is an object is done in jsvalueWrites if you need total control you could implement OWrites[String] and remove the parse.json
I wanted to test a class by running it from a scala worksheet. When running this test script:
import ping.GcmRestServer
val server = new GcmRestServer("AIzaSyCOn...")
server.send(List("dcGKzDg5VOQ:APA91bHNUDaBj01th..."), Map(
"message" -> "Test Message",
"title" -> "Test Title"
))
Were the tested class GcmRestServer is
package ping
import play.api.Logger
import play.api.libs.ws.WS
import play.api.libs.json.Json
/**
* Created by Lukasz on 26.02.2016.
*/
class GcmRestServer(val key: String) {
def send(ids: List[String], data: Map[String, String]) = {
import play.api.Play.current
import scala.concurrent.ExecutionContext.Implicits.global
val body = Json.obj(
"registration_ids" -> ids,
"data" -> data
)
WS.url("https://android.googleapis.com/gcm/send")
.withHeaders(
"Authorization" -> s"key=$key",
"Content-type" -> "application/json"
)
.post(body)
.map { response => Logger.debug("Result: " + response.body)}
}
}
Gives the following result:
import ping.GcmRestServer
server: ping.GcmRestServer = ping.GcmRestServer#34860db2
java.lang.RuntimeException: There is no started application
at scala.sys.package$.error(test.sc2.tmp:23)
at play.api.Play$$anonfun$current$1.apply(test.sc2.tmp:82)
at play.api.Play$$anonfun$current$1.apply(test.sc2.tmp:82)
at scala.Option.getOrElse(test.sc2.tmp:117)
at play.api.Play$.current(test.sc2.tmp:82)
at ping.GcmRestServer.send(test.sc2.tmp:16)
at #worksheet#.get$$instance$$res0(test.sc2.tmp:4)
at #worksheet#.#worksheet#(test.sc2.tmp:19)
Could somebody explain me what I did wrong, and how to fix this?
The line import play.api.Play.current requires a running Play application.
I've never used scala worksheets, but it seems the same problem.
On tests, the solution is to run a fake application, as written in the documentation.
You are using play WS which requires a play application running. Just start one with a code like this:
import play.api.libs.ws.WS
import play.api.{Play, Mode, DefaultApplication}
import java.io.File
import play.api.Logger
import play.api.libs.json.Json
import scala.concurrent.ExecutionContext.Implicits.global
class GcmRestServer(val key: String) {
val application = new DefaultApplication(
new File("."),
Thread.currentThread().getContextClassLoader(),
None,
Mode.Dev
)
def send(ids: List[String], data: Map[String, String]) = {
val body = Json.obj(
"registration_ids" -> ids,
"data" -> data
)
WS.client(application).url("https://android.googleapis.com/gcm/send")
.withHeaders(
"Authorization" -> s"key=$key",
"Content-type" -> "application/json"
)
.post(body)
.map { response => Logger.debug("Result: " + response.body)}
}
}
Or if you don't want to use WS.Client(application) you may run Play.start(application), import it with import play.api.Play.current and then WS.url will work.
I'm working on a web application using Play Framework (2.2.6) / scala / mongoDB, and I have a problem with reactivemongo.bson.BSONObjectID. (I'm a beginner in both ReactiveMongo and Scala)
My controller contains this code :
val actForm = Form(tuple(
"name" -> optional(of[String]),
"shortcode" -> optional(of[String]),
"ccam" -> mapping(
"code" -> optional(of[String]),
"description" -> optional(of[String]),
"_id" -> optional(of[BSONObjectID])
)(CCAMAct.apply)(CCAMAct.unapply)
));
def addAct = AsyncStack(AuthorityKey -> Secretary) { implicit request =>
val user = loggedIn
actForm.bindFromRequest.fold(
errors => Future.successful(BadRequest(errors.errorsAsJson)), {
case (name, shortcode, ccam) =>
val newact = Json.obj(
"id" -> BSONObjectID.generate,
"name" -> name,
"shortcode" -> shortcode,
"ccam" -> ccam
)
settings.update(
Json.obj("practiceId" -> user.practiceId.get),
Json.obj("$addToSet" -> Json.obj("acts" -> Json.obj("acte" -> newact)))
).map { lastError => Ok(Json.toJson(newact)) }
})
}
The CCAMAct class is defined like this :
import models.db.Indexable
import play.api.libs.json._
import reactivemongo.bson.BSONObjectID
import reactivemongo.api.indexes.{Index, IndexType}
import models.db.{MongoModel, Indexable}
import scala.concurrent.Future
import play.modules.reactivemongo.json.BSONFormats._
import models.practice.Practice
import play.api.libs.functional.syntax._
case class CCAMAct(code:Option[String],
description:Option[String],
_id: Option[BSONObjectID] = None) extends MongoModel {}
object CCAMAct extends Indexable {
private val logger = play.api.Logger(classOf[CommonSetting]).logger
import play.api.Play.current
import play.modules.reactivemongo.ReactiveMongoPlugin._
import play.modules.reactivemongo.json.collection.JSONCollection
import scala.concurrent.ExecutionContext.Implicits.global
def ccam: JSONCollection = db.collection("ccam")
implicit val ccamFormat = Json.format[CCAMAct]
def index() = Future.sequence(
List (
Index(Seq("description" -> IndexType.Text))
).map(ccam.indexesManager.ensure)
).onComplete { indexes => logger.info("Text index on CCAM ends") }
}
Thus the compiler throws me this error :
Cannot find Formatter type class for reactivemongo.bson.BSONObjectID. Perhaps you will need to import play.api.data.format.Formats._
"_id" -> optional(of[BSONObjectID])
^
(Of course I have already imported "play.api.data.format.Formats._")
I also tried to add a custom Formatter following answers from similar posts on the web.
object Format extends Format[BSONObjectID] {
def writes(objectId: BSONObjectID): JsValue = JsString(objectId.stringify)
def reads(json: JsValue): JsResult[BSONObjectID] = json match {
case JsString(x) => {
val maybeOID: Try[BSONObjectID] = BSONObjectID.parse(x)
if(maybeOID.isSuccess)
JsSuccess(maybeOID.get)
else {
JsError("Expected BSONObjectID as JsString")
}
}
case _ => JsError("Expected BSONObjectID as JsString")
}
}
...without any success.
[UPDATED POST]
Finally I'm not able to find a play form type (for handling POST requests in controller) in order to map a class containing BSONObjectID type...
Anyone knows a clean solution to resolve this problem?
The JSON Format for the BSON types from ReactiveMongo are not provided by Play itself in Formats._, but by the ReactiveMongo plugin for Play.
import play.modules.reactivemongo.json.BSONFormats._
I am writing a Customer Serializer. In that Serializer I would like to somehow say: "and this thing you already know how to serialize".
My current approach looks like that:
import org.json4s.native.Serialization._
import org.json4s.JsonDSL.WithBigDecimal._
object WindowSerializer extends CustomSerializer[Window](format =>
( [omitted],
{
case Window(frame, size) =>
( "size" -> size ) ~
( "frame" -> parse(write(frame)) )
}))
That parse(write(frame)) things is both ugly and inefficient. How to fix that?
You can call Extraction.decompose(a: Any)(implicit formats: Formats): JValue which produces a JValue from some value using runtime reflection.
import org.json4s._
import org.json4s.jackson.JsonMethods._
import org.json4s.JsonDSL._
import java.util.UUID
case class Thing(name: String)
case class Box(id: String, thing: Thing)
class BoxSerializer extends CustomSerializer[Box](format => ({
case jv: JValue =>
val id = (jv \ "id").extract[String]
val thing = (jv \ "thing").extract[Thing]
Box(id, thing)
}, {
case b: Box =>
("token" -> UUID.randomUUID().toString()) ~
("id" -> box.id) ~
("thing" -> Extraction.decompose(box.thing))
}))
implicit val formats = DefaultFormats + new BoxSerializer
val box = Box("1000", Thing("a thing"))
// decompose the value to JSON
val json = Extraction.decompose(box)
println(pretty(json))
// {
// "token" : "d9bd49dc-11b4-4380-ab10-f6df005a384c",
// "id" : "1000",
// "thing" : {
// "name" : "a thing"
// }
// }
// and read a value of type Box back from the JSON
println(json.extract[Box])
// Box(1000,Thing(a thing))
I have this Scala/Play application and I have to fetch a bunch of templates via AJAX. I'm doing something like this now:
def home = Action {
Ok(views.html.home())
}
def about = Action {
Ok(views.html.about())
}
def contact = Action {
Ok(views.html.contact())
}
//etc
But this is just creating an action for every template. Can I do something like this instead:
def loadTemplate(templateName) = Action {
//Load template from "views" with name being value of parameter templateName
}
Is this possible on Play Framework? If so then how?
Play Framework 2.2.1 / Scala 2.10.3 / Java 8 64bit
UPDATE: My original question might have been misunderstood. I don't want to compile a template, I want to fetch already compiled one in a more dynamic way.
UPDATE2: I think I found something very close, if not exactly what I need on this answer, but it's in Java and I need it in Scala.
Using scala reflection:
object Application extends Controller {
import reflect.runtime.universe._
val currentMirror = runtimeMirror(Play.current.classloader)
val packageName = "views.html."
def index(name: String) = Action {
val templateName = packageName + name
val moduleMirror = currentMirror.reflectModule(currentMirror.staticModule(templateName))
val methodSymbol = moduleMirror.symbol.typeSignature.declaration(newTermName("apply")).asMethod
val instanceMirror = currentMirror.reflect(moduleMirror.instance)
val methodMirror = instanceMirror.reflectMethod(methodSymbol)
Ok(methodMirror.apply().asInstanceOf[Html])
}
}
Based on #Nilanjan answer and for play-2.4 scala-2.11.7
def page(page: String) = Action.async { implicit request =>
Future.successful(Ok(loadTemplate(page)))
}
import reflect.runtime.universe._
import play.api._
import play.twirl.api.Html
val currentMirror = runtimeMirror(Play.current.classloader)
val packageName = "views.html."
private def loadTemplate(name: String) = {
val templateName = packageName + name
val moduleMirror = currentMirror.reflectModule(currentMirror.staticModule(templateName))
val methodSymbol = moduleMirror.symbol.info.member(TermName("apply")).asMethod
val instanceMirror = currentMirror.reflect(moduleMirror.instance)
val methodMirror = instanceMirror.reflectMethod(methodSymbol)
methodMirror.apply().asInstanceOf[Html]
}
Here we go again! #Nilanjan answer adopted to Scala 2.12 and Play 2.6 (as moving to DI play.api.Play.current, TermName and declaration have been deprecated). For app, I had to use injector because #Inject()(app: Application) was causing circular dependency
class HomeController #Inject()(injector: Injector, cc: ControllerComponents) extends AbstractController(cc) {
def index(name: String = "index") = Action { implicit request: Request[AnyContent] =>
import reflect.runtime.universe._
val app = injector.instanceOf[Application]
val currentMirror = runtimeMirror(app.classloader)
val packageName = "views.html."
val templateName = packageName + name
val moduleMirror = currentMirror.reflectModule(currentMirror.staticModule(templateName))
val methodSymbol = moduleMirror.symbol.typeSignature.decl(TermName("apply")).asMethod
val instanceMirror = currentMirror.reflect(moduleMirror.instance)
val methodMirror = instanceMirror.reflectMethod(methodSymbol)
Ok(methodMirror.apply("some", "content").asInstanceOf[Html])
}
}
It's not quite good idea to allow search view by any text (for security reasons) as such can be passed in param, instead, you can resolve this quite easy with match statement - it will allow you restrict request to allowed views only and will handle wrong requests as well, probably Scala geeks can demonstrate nicer code, but this will work for you out of the box:
def loadTemplate(templateName: String) = Action {
templateName match {
case "about" => Ok(about())
case "home" => Ok(home())
case "contact" => Ok(contact())
case _ => NotFound(notFoundView())
}
}
route:
GET /load-template/:templateName controllers.Application.loadTemplate(templateName)
Additional benefit is that you can grab additional data from request and pass it to the resolved view depending on templateName param
In your Template file
general_template.scala.html
#(templateName : String = "none")
#templateName match {
case "about" => { //html elements}
case "home" => {//html elements}
case "contact" => {//html elements}
case _ => { }
}
and in your controller
def loadTemplate(templateName) = Action {
Ok(views.html.general_template(templateName))
}