Sequence of maps not working - scala play framework - scala

I'm having some problems when trying to map some different objects so that I can extract some fields from it.
I've the function in my controller like this:
def index = SecuredAction.async { implicit request =>
transportService.allNonActive().map { transports =>
val sourceEmailsListBuffer = ListBuffer[String]()
val destinyEmailsListBuffer = ListBuffer[String]()
val sortingCenterStockListBuffer = ListBuffer[SortingCenterStock]()
val transportsListBuffer = ListBuffer[Transport]()
transports.map { transport =>
transportsListBuffer.append(transport)
// gets SC of this transport
sortingCenterStockService.retrieve(transport.idSCStock).map { sortingCenterStock =>
Logger.debug(s"Entry on SCS")
sortingCenterStockListBuffer.append(sortingCenterStock)
}
// gets email from source
userDAO.find(transport.idSourceUser).map { option =>
option.map { user =>
user.email.map { email =>
sourceEmailsListBuffer.append(email)
Logger.debug(s"Entry on Source Email")
}
}
}
// gets email from destiny
userDAO.find(transport.idDestinyUser).map { option =>
option.map { user =>
user.email.map { email =>
destinyEmailsListBuffer.append(email)
Logger.debug(s"Entry on Destiny Email")
}
}
}
}
Logger.debug(s"Size of source emails: ${sourceEmailsListBuffer.size}")
Logger.debug(s"Size of destiny emails: ${destinyEmailsListBuffer.size}")
Logger.debug(s"Size of scs: ${sortingCenterStockListBuffer.size}")
Logger.debug(s"Size of transp: ${transportsListBuffer.size}")
Ok(views.html.transports.index(request.identity, sourceEmailsListBuffer.toList, destinyEmailsListBuffer.toList, sortingCenterStockListBuffer.toList, transportsListBuffer.toList))
}
}
When I load the page for the first time (with any minor change, i.e. I change the string I use to indicate what I'm debugging), it gets the info from the last map userDAO.find(transport.idDestinyUser).map. When I refresh the page, the list's size destinyEmailsListBuffer is 0 and it is returned to the view before doing the map (at least I think so).
This is what I get after refreshing, after getting the correct output for the first time:
second load of the page
Thanks in advance, I hope you can help me!

I think your general structure is wrong. For instance:
userDAO.find(transport.idDestinyUser).map { option =>
option.map { user =>
user.email.map { email =>
destinyEmailsListBuffer.append(email)
Logger.debug(s"Entry on Destiny Email") // This returns Unit!
}
}
}
So you are using map operations and chaining those results to other functions, but instead if returning lists of items, you are incrementing an existing list that is never returned. Either return destinyEmailsListBuffer after logging or re-write to use forEach and to pick up the right values from somewhere.

Related

How make sort of backup for future

This code looks for faculties and compares it to received faculty from form.
If in DB it finds that faculty, than it adds this group to DB and after that - redirects it, actually, to the same page, but in GET (this is POST method).
The problem is that I need redirecting if it not added that group. Maybe, someone knows how to make this simple and tidily?
def addGroup() = Action.async {
implicit request =>
GroupForm.form.bindFromRequest.fold(
errorForm => ???,
data => {
(for {
seqOfFaculties <- FacultyService.getAllFaculties
future <- GroupService.addGroup(Group(0, data.nameGroup, data.faculty)) if seqOfFaculties.exists(_.name == data.faculty)
} yield future).map(_ => Redirect(routes.GroupController.get()))
})
}
Maybe something like this:
FacultyService.getAllFaculties.map { seqOfFaculties =>
if (seqOfFaculties.exists { _.name == "something" }) {
Ok // or something else...
} else {
Redirect(routes.GroupController.get())
}
}
You should be careful when using if statement inside a for-comprehension with Futures. You could get an exception... :) (with nothing that handles it)

akka-http: How to set response headers

I've a route as follows:
val route = {
logRequestResult("user-service") {
pathPrefix("user") {
get {
respondWithHeader(RawHeader("Content-Type", "application/json")) {
parameters("firstName".?, "lastName".?).as(Name) { name =>
findUserByName(name) match {
case Left(users) => complete(users)
case Right(error) => complete(error)
}
}
}
} ~
(put & entity(as[User])) { user =>
complete(Created -> s"Hello ${user.firstName} ${user.lastName}")
} ~
(post & entity(as[User])) { user =>
complete(s"Hello ${user.firstName} ${user.lastName}")
} ~
(delete & path(Segment)) { userId =>
complete(s"Hello $userId")
}
}
}
}
The content type of my response should always be application/json as I've it set for the get request. However, what I'm getting in my tests is text/plain. How do I set the content type correctly in the response?
On a side note, the akka-http documentation is one of the most worthless piece of garbage I've ever seen. Almost every link to example code is broken and their explanations merely state the obvious. Javadoc has no code example and I couldn't find their codebase on Github so learning from their unit tests is also out of the question.
I found this one post that says "In spray/akka-http some headers are treated specially". Apparently, content type is one of those and hence can't be set as in my code above. One has to instead create an HttpEntity with the desired content type and response body. With that knowledge, when I changed the get directive as follows, it worked.
import akka.http.scaladsl.model.HttpEntity
import akka.http.scaladsl.model.MediaTypes.`application/json`
get {
parameters("firstName".?, "lastName".?).as(Name) { name =>
findUserByName(name) match {
case Left(users) => complete(users)
case Right(error) => complete(error._1, HttpEntity(`application/json`, error._2))
}
}
}

Play 2.2.1 Scala - Redirect for 413 REQUEST_ENTITY_TOO_LARGE

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) => {
...
}
})
}

spray routing access to url path

I have a route with a portion like this:
...
(pathEnd | path("summary")) {
parameters(...).as(Query) { query =>
onSuccess(model ? query) {
case MyResponse(list) =>
// at this point I would like to know if I hit pathEnd or
// path(summary) so I can complete with summary or full response.
if (???)
complete(OK, list)
else
complete(OK, list map (_.toSummary))
}
}
}
...
Essentially there's a lot of parameter wrangling and querying of the model that is identical, but I'm doing an extra transformation of the response to shed some data if the summary endpoint is hit. Is it possible to do this in some way?
I tried adding a ctx => after the (pathEnd | path("summary")) { ctx => but that didn't work at all. (The route didn't match, and never returned anything.)
I gave this custom directive a quick unit test and it seems to work:
def pathEndOr(p: String) =
pathEnd.hmap(true :: _) | path(p).hmap(false :: _)
You can use it in you example like so:
...
pathEndOr("summary") { isPathEnd =>
parameters(...).as(Query) { query =>
onSuccess(model ? query) {
case MyResponse(list) =>
// at this point I would like to know if I hit pathEnd or
// path(summary) so I can complete with summary or full response.
if (isPathEnd)
complete(OK, list)
else
complete(OK, list map (_.toSummary))
}
}
}
...

How to include a picture type in a form in Play!2 in Scala?

According to this guide, one can upload files by writing the html form by hand. I want to handle file upload as part of a bigger form that includes text fields (for example name and email). Here is what I have to far (quite ugly):
def newUser = Action(parse.multipartFormData) { implicit request =>{
//handle file
import play.api.mvc.MultipartFormData.FilePart
import play.api.libs.Files.TemporaryFile
var uploadSuccessful = true
var localPicture: FilePart[TemporaryFile] = null
request.body.file("picture").map { picture =>
localPicture = picture }.getOrElse {
uploadSuccessful = false }
//process the rest of the form
signupForm.bindFromRequest.fold(
errors => BadRequest(views.html.signup(errors)),
label => {
//file uploading code here(see guide), including error checking for the file.
if(uploadSuccesful){
User.create(label._1, label._2, label._3._1, 0, "NO PHOTO", label._4)
Redirect(routes.Application.homepage).withSession("email" -> label._2)
} else {
Redirect(routes.Application.index).flashing(
"error" -> "Missing file"
}
})
} }
This looks tremendously ugly to me. Note that I have defined a signupForm somewhere that includes all fields (apart from the file upload one). My question is: Is there a prettier way of going about this? Perhaps by including the file field in the signupForm and then handling errors uniformly.
So far I think it's not possible to bind binary data to a form directly, you can only bind the reference (e.g. the picture's ID or name). You could however reformulate your code a bit:
def newUser() = Action(parse.multipartFormData) { implicit request =>
import play.api.mvc.MultipartFormData.FilePart
import play.api.libs.Files.TemporaryFile
request.body.file("picture").map { picture =>
signupForm.bindFromRequest.fold(
errors => BadRequest(views.html.signup(errors)),
label => {
User.create(label._1, label._2, label._3._1, 0, picture.absolutePath(), label._4)
Redirect(routes.Application.homepage).withSession("email" -> label._2)
}
)
}.getOrElse(Redirect(routes.Application.index).flashing("error" -> "Missing file"))
}
You can use the asFormUlrEncoded, like below:
def upload = Action(parse.multipartFormData) { request =>
val formField1 = request.body.asFormUrlEncoded("formField1").head;
val someOtherField = request.body.asFormUrlEncoded("someOtherField").head;
request.body.file("photo").map { picture =>
...
}
}