Playframework [Scala]: Creating forms - scala

I'm beginner to Scala and Playframework. I have some code in Java and I have a problem to translate it into Scala. Can you suggest something how can I do it? I check documentation about ScalaForms but still I can't understand how to do it. My code is as following:
// Injecting FormFactory
public Result create(){
Form<Book> bookForm = formFactory.form(Book.class);
return ok(create.render(bookForm));
}
public Result save(){
Form<Book> bookForm = FormFactory.form(Book.class).bindFromRequest();
Book book = bookForm.get();
Book.add(book);
return redirect(routes.BooksController.index());
}
Ok so I have something like this:
def index = Action {
val books: Set[Book] = Book.allBooks
Ok(views.html.index(books))
}
def create = Action{
Ok(views.html.create())
}
def save = Action {
implicit request =>
val (id, title, price, author) = bookForm.bindFromRequest.get
Book.addBook(id = id, title = title, price = price, author = author)
Redirect(routes.BooksController.index())
}
My routes:
GET /books controllers.BooksController.index
GET /books/create controllers.BooksController.create
POST /books/create controllers.BooksController.save
And the problem is with Redirect, I have an error: "object java.lang.ProcessBuilder.Redirect is not a value"

Ok so for Scala version of dealing with form, this is what I do:
You use bindFromRequest to do, most of the times, two things: 1. If there is an error within the form return it back to the user and 2. If there is no error within the form, use the given data by the user to do some other operations (e.g., call a third party API, or internal API, etc.)
If things are fine (second sub step of the above first step), I return an HTTP response to the user.
So, I would have something like this in my controller method:
def save() = Action {
implict request =>
bookForm.bindFromRequest.fold(
formWithErrors => {
BadRequest(views.html.whateverTheViewIs(bookForm))
}, formData =>
//Do whatever you need to do
Ok("saved")//Again, here you can use any views; and pass the required parameters.
)
}
It might be the case, that you have difficulty on defining the bookForm itself, so in Scala, you first define your data model using a case class. Lets define a very simple one:
case class Book(title: String, author: String, isbn: String)
Then you define your form:
val bookForm = Form(
mapping(
"title": String -> NonEmptyText
"author" : String -> NonEmptyText
"isbn" : String -> NonEmptyText
)(Book.apply)(Book.unapply)
)
Note on Redirection: For the redirect, as you mentioned in the comment, you could use the URL used. For example ,if I have:
GET /confirmation controllers.Book.confirmation (bookId: String)
Then I want to redirect to the confirmation URL, knowing that I need to also provide the `userId', I can do the following:
val bookId = "1" //You need to get the Id, from a database or an API.
Redirect(s"/confirmation?bookId=$bookId")
Further Step [Use Future API]: The important thing here is that, for simplicity; I did not use the Future API, for asynchronously respond to the request. This should be your aim, because you want to write non-blocking response, especially if you want to call other APIs, for processing/storing the form's valid input.

Related

Using requestContinuation to make pagination in Spring-Data-CosmosDB

I'm using spring-data-cosmosdb 2.2.2 with azure-cosmos 3.6.0 and Scala.
I'm using CosmosTemplate to create custom DocumentQueries using sorting and pagination.
My problem is to navigate through the pages in my request using requestContinuation. Every request (POST) is returning a requestContinuation like that:
"requestContinuation": "{\"compositeToken\":\"{\\"token\\":null,\\"range\\":\\"{\\\\"min\\\\":\\\\"05C1DFFFFFFFFC\\\\",\\\\"max\\\\":\\\\"FF\\\\",\\\\"isMinInclusive\\\\":true,\\\\"isMaxInclusive\\\\":false}\\"}\",\"orderByItems\":[{\"item\":\"8b90e589-09d8-42e7-a3e2-d26cf2c38a63\",\"map\":{\"item\":\"8b90e589-09d8-42e7-a3e2-d26cf2c38a63\"}}],\"rid\":\"I9lLALNXyUNECgEAAAAACA==\",\"inclusive\":true}"
The first thing I saw is the inner "token" is always null.
I tried to copy the whole string and put it on my request as parameter but it didn't work:
POST https://baseurl/api/documents/recent?page=1&requestContinuation=...
The requestContinuation never changes and the documents returned in the page are always the same. I'm using Postman and Insomnia.
I don't find good examples to use requestContinuation.
Am I missing something? Maybe encoding the token with base64?
I don't know if the problem is just to pass the requestContinuation or if there's something wrong in my code.
In resume I'm just using paginationQuery method to execute a documentQuery with some criterias and a "pageable" object.
My Controller:
#RestController
#RequestMapping(path = Array("/api"))
class DocumentController(#Autowired private val service: DocumentService) extends BaseController {
#PostMapping(path = Array("/documents/recent"), produces = Array("application/json"))
def findRecentDocuments(pageable: Pageable,
#RequestBody params: DocumentParams,
#RequestHeader(name="apiKey") token: String
): Page[Document] = {
service.findRecentDocuments(pageable, Option(params))
}
My service:
class DocumentService(#Autowired private val documentRepository: DocumentRepository, #Autowired private val template: CosmosTemplate) extends BaseService {
def findRecentDocuments(pageable: Pageable, params: Option[DocumentParams]): Page[Document] = {
val documentClass = classOf[Document]
val collectionName = template.getCollectionName(documentClass)
val defaultSort = Sort.by(Direction.ASC, "id")
val sort = pageable.getSortOr(defaultSort)
val criteria = QueryUtils.getCriteria(params, documentClass)
getCriteriaInfo(criteria)
documentRepository.findRecentDocuments(pageable, criteria, sort, documentClass, collectionName)
}
My repo:
class DocumentRepository(#Autowired private val template: CosmosTemplate) {
def findRecentDocuments(pageable: Pageable, criteria: Criteria, sort: Sort, documentClass: Class[Document], collectionName: String): Page[Document] = {
val query = new DocumentQuery(criteria)
template.paginationQuery(query.`with`(sort).`with`(pageable), documentClass, collectionName)
}
}
Thanks for any help.
Did you try the recommended way of pagination provided in the readme section of spring-data-cosmosdb: https://github.com/microsoft/spring-data-cosmosdb/blob/master/README.md
Section: Supports Spring Data pageable and sort.
Also documented here: https://learn.microsoft.com/en-us/azure/java/spring-framework/how-to-guides-spring-data-cosmosdb#pagination-and-sorting
As I already commented in the original issue on MS Github I found the solution changing the "Pageable" object to "CosmosPageRequest" object which uses requestContinuation.
This worked for me.
Here's the code:
https://github.com/microsoft/spring-data-cosmosdb/issues/484
Thanks.
Lucas Porto, If you open to using the Java SDK, I have implemented infinite scroll with reactive Java SDK 4.0.
The "next" method in the following controller class is the server side entry point for the pagination used by the jQuery Ajax call.
Code on GitHub

Trouble using case class for multiple fields in POST body Finatra

I am working on a simple Finatra API example, but having trouble using a case class for the POST request when using more than one field in the request body.
Here is an example of my case class and controller:
class SaleResource extends Controller {
post("/sales") { sale: Sale =>
s"$sale"
}
}
case class Sale(
user: Option[String],
streetAddress: Option[String]
)
I send a post request to that route with the following request body JSON:
{
"user": "Foo Barrington",
"streetAddress":"Baz Street"
}
The response:
Sale(Some(Foo Barrington),None)
The response shows that the user is being properly deserialized, but for some reason I cannot get the streetAddress field to populate.
Also, I noticed when I set either of these fields to String instead of Option[String] I only get unsuccessful 500 responses.
Things I have tried:
case statements matching Some(streetAddress) to that fields string value or "none found" when it is None. In these cases it still is saying streetAddress is None when it is not.
Making the request with both curl and Postman.
I can always access the user field from the Sales object, but never the streetAddress (or any other field from the request body for that matter if I add test elements to the case class.
I would expect both fields to be recognized since they are both provided in the request. I am newer to Scala/Finatra in general, so it is possible I am just using the Finatra library or Case classes incorrectly.
EDIT:
It seems as if changing the field names to not be mixed/camelcase fixes all issues, but this seems like odd behavior.
Finatra uses Jackson library behind the scenes. The default configuration uses PropertyNamingStrategy.SNAKE_CASE which seems like:
{
"user": "Foo Barrington",
"street_address":"Baz Street"
}
You need to change it to PropertyNamingStrategy.LOWER_CAMEL_CASE to parse that JSON.
In order to do that, you need to define a custom FinatraJacksonModule and tell the app to
use it.
object CustomFinatraJacksonModule extends FinatraJacksonModule {
override val propertyNamingStrategy = PropertyNamingStrategy.LOWER_CAMEL_CASE
}
class MyFinatraHttpServer extends HttpServer {
override protected def jacksonModule: Module = CustomFinatraJacksonModule
}
Jackson Integration provides more information about the topic.

Playframework Scala - Delete Route problems

I'm working with playframework for final project at university and I'm getting a problem when routing a delete or put method.
When I'm requesting a DELETE or PUT methods I'm getting:
[info] play.api.Play - Application started (Dev)
[debug] a.ErrorHandler - onClientError: statusCode = 404, uri = /Rest/deleteCity, message ="
My JQuery ajax call is:
$("#scalaDelete").click(function(){
$("#result").empty();
$.ajax({
url: "http://localhost:9000/Rest/deleteCity",
method: "DELETE",
data: {city: "Alvorada"},
dataType: "json",
success: function(result){
$("#result").append("Result: "+result.Result);
},
error: function (request, status, error) {
alert(status);
}
});
});
My Route Play Route:
DELETE /Rest/deleteCity controllers.RestController.deleteCity()
My Controller Method:
case class UserDelete(city:String)
class RestController #Inject()(db: Database, cc: ControllerComponents) extends AbstractController(cc) {
val userDeleteForm = Form(
mapping(
"city" -> text
)(UserDelete.apply)(UserDelete.unapply)
)
def deleteCity = Action{ implicit request=>
val userPar = userDeleteForm.bindFromRequest.get
//DatabaseDelete
Ok(jsonResult)
}
}
I've already activated cross domain in chrome, I've used a CORS extension for it.
Thanks for helping
This seems related to Restful http delete in play, i.e. DELETE with data can be sketchy.
Instead of passing data, I would just move this to the url:
DELETE /Rest/deleteCity/:city controllers.RestController.deleteCity(city: String)
# or with a query string
DELETE /Rest/deleteCity controllers.RestController.deleteCity(city: String)
and then do
http://localhost:9000/Rest/deleteCity/Alvorada
# or with a query string
http://localhost:9000/Rest/deleteCity?city=Alvorada
Personally I prefer the latter.
I agree with #AndyHayden.
Play ignores the body of the DELETE request, that is the correct behavior to my mind, but you can work around by explicitly passing a body parser:
def delete = Action(parse.json) { implicit request =>
val json = request.body
val someProp = (json \ "someprop").as[String]
Ok(s"Prop is: $someProp")
}
(this example was given by one of the developers of the Play itself:
https://github.com/playframework/playframework/issues/4606#issuecomment-109192802.)
About the doubts in comments:
I've seen another post here where a guy said some browsers just support get and post method.
POST and GET are only valid for the method attribute of the form tag.
You are using javascript request, so you can use any method that server supports. i.e. DELETE is completely fine there.
But something interesting for you to know is that playframework uses akka and this framework does not support DELETE request for security reasons, in fact it wasn't well explained on post. Then if you wanna make a DELETE method you gotta make a post method for complete your code.
Akka HTTP supports the DELETE request (as well as Play Framework): https://doc.akka.io/docs/akka-http/current/scala/http/routing-dsl/directives/method-directives/delete.html

Grails rest-api app to handle multiple params

Using Grails 3.1.3, I created a rest-api so that I am able to capture GET requests that not only query for one parameter, but multiple if needed. I don't know how to code this correctly inside the UrlMappings file. Here are the details.
Domain class:
class ProdDetail {
Integer pid
String title
String category
Integer year
}
And some of these inside the BootStrap:
new ProdDetail(pid:'101', title:'No Highway', author:'Nevil Shute', category:'fiction', year:1948).save(failOnError:true)
new ProdDetail(pid:'214', title:'In the Country of Men', author:'Hisham Matar', category:'misery', year:2007).save(failOnError:true)
Controller:
protected List<ProdDetail> listAllResources(Map params) {
println params
try {
ProdDetail.where {
if (params.category && params.maxYear) {
category == params.category && year <= params.int('maxYear')
} else if (params.category) {
category == params.category
} else if (params.maxYear) {
year <= params.int('maxYear')
} else {
pid > 0
}
}.list()
} catch (Exception e) {
[]
}
}
UrlMappings:
static mappings = {
"/prodDetails"(resources:'prodDetail')
"/prodDetails/category/$category?"(controller:'prodDetail', action:'index')
"/prodDetails/yearUnder/$maxYear?"(controller:'prodDetail', action:'index')
// the line below is not right I think, what's the correct format?
"/prodDetails/combo/$category?&$maxYear?"(controller:'prodDetail', action:'index')
}
Now, where as these two curls would work:
curl localhost:8080/prodDetails/category/misery
curl localhost:8080/prodDetails/yearUnder/2007
This one fails to go into the desired clause in the controller to detect both params:
curl localhost:8080/prodDetails/combo/?category=misery&maxYear=2007
It just detects 'category' but not the 'maxYear' which it considers as 'null'.
How can I cater for such a curl please?
It kind of depends on what you want your URLs to look like, but assuming you want your requests to look like this:
http://localhost:8080/prodDetails/combo/misery?maxYear=2007&title=common
The UrlMappings should look like
static mappings = {
"/prodDetails/combo/$category"(controller:'prodDetail', action:'index')
}
Then the params object in the controller should have both whatever's in the place of $category, in this example misery, and the other parameters after the ? as well.
If you want the parameters to be in the path you can do this:
static mappings = {
"/prodDetails/combo/$category/$title/$maxYear"(controller:'prodDetail', action:'index')
}
And the request would then be:
http://localhost:8080/prodDetails/combo/misery/common/2007
One other option would be to use a command object. So if you had:
static mappings = {
"/prodDetails/combosearch"(controller:'prodDetail', action:'comboSearch')
}
And then created an object beside the controller called ComboSearchCommand.groovy that looked like:
import grails.validation.Validateable
class ComboSearchCommand implements Validetable {
String category
String title
int maxYear
static constraints = {
category blank: false, nullable: true
title blank: false, nullable: true
maxYear blank: false, nullable: true
}
}
(Which you can do validation on just like a domain object)
And then in your controller you have the method take the command object instead of params
protected List<ProdDetail> comboSearch(ComboSearchCommand command) {
println command.category
}
Then your URL would be
http://localhost:8080/prodDetails/combosearch?category=misery&maxYear=2007&title=common
And the parameters will bind to the command object.
I've used that quite a bit, you can share validations or have your command object inherit validations from domain objects, lots of flexibility.
https://grails.github.io/grails-doc/latest/guide/single.html#commandObjects
You don't need to specify the parameters in UrlMappings if those params are not part of the URL:
No need of this:
"/prodDetails/combo/$category&?$maxYear?"(controller:'prodDetail', action:'index')
Yes you need this to match the URL to a controller/action (but remove the ?)
"/prodDetails/yearUnder/$maxYear?"(controller:'prodDetail', action:'index')
Also, you don't need Map params in listAllResources(Map params)
"params" is an injected property of controllers, the println params will work OK with: listAllResources()
What I would do is to define:
listAllResources(String category, int maxYear, ...) where ... are all the params that action can receive, most would be optional, so you will receive a null value if not included in your request.
Remember: UrlMappings are to map URLs to controller/actions, and you have the same controller/action, so I would remove all the mappings and process the optional parameters in the action just checking which are null or not.
Edit (considering comments)
Q: the method is not overloaded to handle params like that
A: methods are dynamic, this is Grails / Groovy, not Java. It will call the action method even if all the params are null. I would recommend you to go through the Grails controller documentation in detail.
Q: found that the listAllResources method was never called
A: remove the protected keyword from the action, only subclasses would be able to invoke that method. Also, you can add an UrlMapping to avoid users to invoke that URL (match the URL and return 404 Not Available or something like that)
Q: I want to handle a GET request like this localhost:8080/prodDetails/combo?category=misery&year=2016&title=commonTitle, how exactly should the i) entry in UrlMappings, and ii) the listAllResources method look like?
A:
static mappings = {
// if "compo" comes in the action portion, map that to the listAllResources method
// as I said, if all parameters comes in the query string, no further actions are needed, if you need parameters to be part of the URL path, then you need to play with the $xxxx in the URL
"/prodDetails/combo"(controller:'prodDetail', action:'listAllResources')
}
def listAllResources()
{
println params
// logic here
render "OK"
}
Check:
https://grails.github.io/grails-doc/latest/ref/Controllers/params.html
https://grails.github.io/grails-doc/latest/ref/Controllers/render.html
How does grails pass arguments to controller methods?

How to handle optional query parameters in Play framework

Lets say I have an already functioning Play 2.0 framework based application in Scala that serves a URL such as:
http://localhost:9000/birthdays
which responds with a listing of all known birthdays
I now want to enhance this by adding the ability to restrict results with optional "from" (date) and "to" request params such as
http://localhost:9000/birthdays?from=20120131&to=20120229
(dates here interpreted as yyyyMMdd)
My question is how to handle the request param binding and interpretation in Play 2.0 with Scala, especially given that both of these params should be optional.
Should these parameters be somehow expressed in the "routes" specification? Alternatively, should the responding Controller method pick apart the params from the request object somehow? Is there another way to do this?
Encode your optional parameters as Option[String] (or Option[java.util.Date], but you’ll have to implement your own QueryStringBindable[Date]):
def birthdays(from: Option[String], to: Option[String]) = Action {
// …
}
And declare the following route:
GET /birthday controllers.Application.birthday(from: Option[String], to: Option[String])
A maybe less clean way of doing this for java users is setting defaults:
GET /users controllers.Application.users(max:java.lang.Integer ?= 50, page:java.lang.Integer ?= 0)
And in the controller
public static Result users(Integer max, Integer page) {...}
One more problem, you'll have to repeat the defaults whenever you link to your page in the template
#routes.Application.users(max = 50, page = 0)
In Addition to Julien's answer. If you don't want to include it in the routes file.
You can get this attribute in the controller method using RequestHeader
String from = request().getQueryString("from");
String to = request().getQueryString("to");
This will give you the desired request parameters, plus keep your routes file clean.
Here's Julien's example rewritten in java, using F.Option: (works as of play 2.1)
import play.libs.F.Option;
public static Result birthdays(Option<String> from, Option<String> to) {
// …
}
Route:
GET /birthday controllers.Application.birthday(from: play.libs.F.Option[String], to: play.libs.F.Option[String])
You can also just pick arbitrary query parameters out as strings (you have to do the type conversion yourself):
public static Result birthdays(Option<String> from, Option<String> to) {
String blarg = request().getQueryString("blarg"); // null if not in URL
// …
}
For optional Query parameters, you can do it this way
In routes file, declare API
GET /birthdays controllers.Application.method(from: Long, to: Long)
You can also give some default value, in case API doesn't contain these query params it will automatically assign the default values to these params
GET /birthdays controllers.Application.method(from: Long ?= 0, to: Long ?= 10)
In method written inside controller Application these params will have value null if no default values assigned else default values.
My way of doing this involves using a custom QueryStringBindable. This way I express parameters in routes as:
GET /birthdays/ controllers.Birthdays.getBirthdays(period: util.Period)
The code for Period looks like this.
public class Period implements QueryStringBindable<Period> {
public static final String PATTERN = "dd.MM.yyyy";
public Date start;
public Date end;
#Override
public F.Option<Period> bind(String key, Map<String, String[]> data) {
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN);
try {
start = data.containsKey("startDate")?sdf.parse(data.get("startDate") [0]):null;
end = data.containsKey("endDate")?sdf.parse(data.get("endDate")[0]):null;
} catch (ParseException ignored) {
return F.Option.None();
}
return F.Option.Some(this);
}
#Override
public String unbind(String key) {
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN);
return "startDate=" + sdf.format(start) + "&" + "endDate=" + sdf.format(end);
}
#Override
public String javascriptUnbind() {
return null;
}
public void applyDateFilter(ExpressionList el) {
if (this.start != null)
el.ge("eventDate", this.start);
if (this.end != null)
el.le("eventDate", new DateTime(this.end.getTime()).plusDays(1).toDate());
}
}
applyDateFilter is just a convienence method i use in my controllers if I want to apply date filtering to the query. Obviously you could use other date defaults here, or use some other default than null for start and end date in the bind method.