I'm having problem inserting (or updating) new data to database using anorm in Play Framework 2.0. My model is a blog post defined in the following code:
case class Post(title: String,
content: String,
created: String,
lastUpdate: String,
writer: Long,
id: Long = 0)
Then I do this on the insert function:
def create(title: String, content: String, userId: Long) = {
DB.withConnection { implicit connection =>
SQL("INSERT INTO post (title, content, created, writer) VALUES ({title}, {content}, NOW(), {writer})")
.on(
'title -> title,
'content -> content,
'writer -> userId).executeUpdate()
}
And the form:
val postForm = Form(
tuple(
"title" -> nonEmptyText,
"content" -> nonEmptyText))
I did not have a userId field in the form because I don't trust the user's input on their own id. I get it from session. Anyway, that's why I can't directly put the validation code in postForm's declaration (because I think I can't access session both from the form and from model). And here's when it becomes ugly. If the post is invalid, anorm throws an Exception so I need to pass the error to user AFTER the fold function like:
postForm.bindFromRequest.fold(
formWithErrors => BadRequest(views.html.post.newPost(formWithErrors)),
newPost => {
try {
val userId = request.session.get("id").getOrElse("0")
models.Post.create(newPost._1, newPost._2, userId.toLong)
} catch {
case e => BadRequest(views.html.post.newPost(postForm.fill(newPost)))
}
Redirect(routes.Application.index)
})
First, the try-catch is ugly. Second, the BadRequest call don't work. What am I doing wrong? What is the best way to handle errors in insert / update? I have the same problem with a login form, but it's not as bad as this because I can actually handle errors at login form declaration.
Thanks before.
Assuming the error is on an integrity constraint like 0 not existing in the database, what about something like this ?
postForm.bindFromRequest.fold(
formWithErrors => BadRequest(views.html.post.newPost(formWithErrors)),
newPost => {
request.session.get("id").map({ userId=>
models.Post.create(newPost._1, newPost._2, userId.toLong)
Redirect(routes.Application.index)
}).getOrElse{
BadRequest(views.html.post.newPost(postForm.fill(newPost)))
}
}
)
You don't give invalid information to anorm and don't get an error ...
ps : I don't have a scala compiler on hand to check the syntax right now, I might have missed a paren or mixed a paren with a curly :)
Related
I am a new in Scala and got some problems with casting from String to Long. I try to get Gatling session value as Long in request. Before in exec() part, I try to set the userId value
def setUserId(): ChainBuilder = {
exec(session => session
.set("userId", Random.nextLong())
)
}
Next, in request creator I want to use it like that because I need a new userId every call:
object UserRequestCreator {
def sampleUserRequest(currency: String): Request = {
Data data = new Data()
data.setUserId("${userId}".toLong)
data.setCurrency(currency)
}
}
Test scenario:
exec(setUserId())
.exec(http("postUser")
.post(endpointUser).asXml
.headers(headers)
.body(StringBody(toXmlString(sampleUserRequest("EUR"), classOf[Request])))
.check(status.is(200))
but receive error:
java.lang.NumberFormatException: For input string: "${userId}"
How to fix that in Scala?
I also try Long.valueOf, JLong.parseLong("${userId"}, 16), Try(BigDecimal(...)) and more but nothing can help. I think the problem is with $ symbol, but I don't see any different way to get this value from the session. Maybe it is possible to store Long in the Gating session?
From the documentation and based on your current code, one way to do it is like that:
// with a function payload
http("name").post("/")
.body(StringBody(session => s"""{ "foo": "${session("dynamicValueKey").as[String]}" }"""))
Thus, in your case:
StringBody(session => toXmlString(sampleUserRequest(session)("EUR"), classOf[Request])
def sampleUserRequest(session: Session)(currency: String): Request = {
//...
data.setUserId(session("userId").as[Long])
}
I am new to spray and I am trying to write a custom directive. I would like the directive to reject the request if the header value is not valid otherwise leave the request alone.
I've tried to absorb this page:
http://spray.io/documentation/1.1.2/spray-routing/key-concepts/directives/
Specifically, the part about the responder chain. I'm trying to create something at the level of the bar Directive in the illustration. I'm just not getting how to pass the context unchanged to the inner route.
My else block below is not correct but expresses what I am trying to do. I just can't figure out how to implement it.
Any help would be greatly appreciated.
trait ApiKeyDirective {
import spray.routing.directives.HeaderDirectives._
import spray.routing.directives.BasicDirectives._
def validateApiKey(): Directive1 = {
headerValueByName("api-key") {key =>
val valid = key == "123"
if (!valid) reject() else pass
}
}
}
object ApiKeyDirective extends ApiKeyDirective
You can combine
headerValueByName:
def headerValueByName(headerName: String): Directive1[String]
with validate:
def validate(check: ⇒ Boolean, errorMsg: String): Directive0
For example:
def validateApiKey(route: Route) =
headerValueByName("api-key") { key =>
validate(key == "123", "Invalid API key") {
route
}
}
or without validate:
def validateApiKey(route: Route) =
headerValueByName("api-key") { key =>
if (key == "123")
route
else
reject(ValidationRejection("Invalid API key"))
}
Usage:
lazy val route = ...
... ~
pathPrefix("test_directive") {
get {
validateApiKey {
complete("ok")
}
}
} ~
...
Test from cmd/shell:
# curl http://localhost:8080/test_directive
Request is missing required HTTP header 'api-key'
# curl http://localhost:8080/test_directive -H 'api-key: bad'
Invalid API key
# curl http://localhost:8080/test_directive -H 'api-key: 123'
"ok"
I'm just not getting how to pass the context unchanged to the inner
route.
Spray does that for you!
Your code is mostly correct, there are just 2 simple problems to fix!
Firstly, you need to flatMap headerValueByName("api-key") directive.
Secondly, the return type will be Directive0 because the directive won't provide any value.
So final code would look like this:
object ApiKeyDirective {
import spray.routing.Directives._
val validateApiKey: Directive0 =
headerValueByName("api-key").flatMap { key =>
val valid = key == "123"
if (!valid) reject() else pass
}
}
Also, I recommend you to add a custom rejection to reject() block so that API users will be informed when their api key is invalid.
I have the following Controller action as form post resolver:
def importCompletionsSubmit(indexName: String) = AuthenticatedAction {
Action.async {
implicit request => {
completionsForm.bindFromRequest().fold(
errors => Future.successful(Ok(html.crudindex.importCompletionsForm(indexName, errors))),
completions => {
val autoCompletionService = new AutoCompletionService(new Elasticsearch)
autoCompletionService.importCompletions(indexName, completions.text) map {
result: BulkImportResult =>
if (result.error) Redirect(routes.ListIndices.index(Option.empty[String])).flashing("error" -> Messages("error.bulkItemsFailed", result.failures))
else Redirect(routes.ListIndices.index(Option.empty[String])).flashing("success" -> Messages("success.completionsAdded", result.requests))
}
}
)
}
}
}
I know that I can change the max length value for this action but what I would like to do is sending the user back to the form with a nice error message when he enters too much text.
If the request body is exceeding the default max length I get a completly blank page and only the browser console shows "413 (Request Entity Too Large)". I tried to catch this error in my global object but that did not change anything. It seems to me that the global onError trigger is not entered when a parser sends back an errorpage. Still a blank page. I also tried to catch that error inside the action but it seems to me that the action code is not entered because the body parser is already throwing this blank error page.
Is there a way to send the user back to the form action when the body exceeds the max length?
Something like this should work for you:
def test = Action.async(parse.maxLength(1024, parse.multipartFormData)) { implicit request =>
Future(request.body match {
case Left(MaxSizeExceeded(length)) => Ok(your_pretty_error_page.scala.html)
case Right(body) => {
...
}
})
}
I've started using Play and the Play-ReactiveMongo plugin and testing for a 404 response on a GET "document by id" scenario. Unfortunately instead of Play returning a 404 NotFound response I get this exception:
java.util.NoSuchElementException: JsError.get
at play.api.libs.json.JsError.get(JsResult.scala:11) ~[play_2.10.jar:2.1.1]
at play.api.libs.json.JsError.get(JsResult.scala:10) ~[play_2.10.jar:2.1.1]
at play.modules.reactivemongo.json.collection.JSONGenericHandlers$StructureBufferWriter$.write(jsoncollection.scala:44) ~[play2-reactivemongo_2.10-0.9.jar:0.9]
at play.modules.reactivemongo.json.collection.JSONGenericHandlers$StructureBufferWriter$.write(jsoncollection.scala:42) ~[play2-reactivemongo_2.10-0.9.jar:0.9]
at reactivemongo.api.collections.GenericQueryBuilder$class.reactivemongo$api$collections$GenericQueryBuilder$$write(genericcollection.scala:323) ~[reactivemongo_2.10-0.9.jar:0.9]
at reactivemongo.api.collections.GenericQueryBuilder$class.cursor(genericcollection.scala:333) ~[reactivemongo_2.10-0.9.jar:0.9]
The getById function below successfully returns a single document if the id parameter matches an existing document, but an exception on the line "one[JsValue]" if document not found.
Route file:
GET /items/:id controllers.ItemsController.getById(id: String)
Controller object:
object ItemsController extends Controller with MongoController {
def itemsCollection: JSONCollection = db.collection[JSONCollection]("items")
def getById(id: String) = Action {
Async {
val query = Json.obj("_id" -> Json.obj("$oid" ->id))
val futureItem = itemsCollection.
find(query).
one[JsValue]
futureItem.map {
case Some(item) => Ok(item)
case None => NotFound(Json.obj("message" -> "No such item"))
}
}
}
}
Maybe I missed something in the docs?
There is partial example documented here:
https://github.com/sgodbillon/reactivemongo-demo-app#simple-query
The mandubian coast-to-coast example handles BadRequest as well NotFound scenario, but code is maybe out of date as it doesn't use the newer looking find(...).one[...] semantics?
http://mandubian.com/2013/01/13/JSON-Coast-to-Coast/#action-get
Turns out the ID needs to be a valid ObjectId, e.g. 24 characters and no illegal tokens.
If the ID is valid but does not reference an existing document then I get a 404 as expected.
If the ID format is invalid (e.g. only 12 characters or contains illegal tokens like '#') then I get an Exception.
When I compare behaviour with an equivalent Node.js + Mongoose app results are very similar.
For example if deliberately querying with a malformed 12 character ID I get this stacktrace in Node:
{ message: 'Cast to ObjectId failed for value "51bded70543f" at path "_id"',
name: 'CastError',
type: 'ObjectId',
value: '51bded70543f',
path: '_id' }
Not sure if this exception is the underlying error in the Play app too but it gave enough of a clue.
The answer would seem to be pre-validate IDs before calling find(query).one[T].
I have run into a problem with a Play 2.1.0 form that contains a file upload and an additional input field. I use
def uploadTaxonomy() = Action(parse.multipartFormData) {
implicit request =>
request.body.file("xml").map { file =>
val xml = scala.io.Source.fromFile(file.ref.file).mkString
taxonomyForm.bindFromRequest().fold(
formWithErrors => BadRequest(views.html.index(formWithErrors)),
result => {
Taxonomies.create(result._1, xml)
Redirect(routes.Application.index())
}
)
}.getOrElse {
Redirect(routes.Application.index())
}
}
and my form is this:
val taxonomyForm = Form(
tuple(
"label" -> text,
"xml" -> text
)
)
The problem is that bindFromRequest() always fails (causing a bad request to be returned to the client).
Does anybody have an idea where the problem might lie?
Note: I am aware that there is a bug in 2.1.0 that manifests when no files are selected in an upload field; it does not seem to be related, however.
As far as I know the xml should not be part of the form definition as you get it directly from the request body.