Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play Framework Form only 18 params

I have observed that when I add more than 18 parameters to a Play Framework Form-class I get a long (and for me incomprehensible) compilation error.

Is this a documented limitation? I need to take in as much as 29 parameters in a form post. I don't decide on the design and number of parameters as I am implementing a protocol from an open standard.

I'm mapping like this:

val registration = Form(mapping(
    "client_type" -> nonEmptyText,
    "client_id" -> optional(nonEmptyText),
    ... up to 29 args, all optional(nonEmptyText)
    ){ (clientType, clientId ...) => RegistrationRequest(clientType, clientId ...) }
     { req => None })

My strategy was to do the mapping this way instead of apply/unapply and create an heirarchy of case classes. The reason is to work around the 22 arguments limit in Case classes, which was the first seemingly arbitrary limit I ran into. Up to 18 args mapping works, after that I get a long compilation error.

The error message can be found here (too long to include): https://gist.github.com/2928297

I'm looking for suggestions on how I can get around this limitation. I know it is bad design to send in 29 parameters in a form Post, but it should still be possible.


Hack/Workaround/Solution

Ok, here is my hacked together workaround (writing this post took much longer than implementing, I hacked for ~30min on this)

I wrote functions that preprocesses the request params and adds a group prefix to group certain params. I then use the resulting Map[String, String] and continue processing with the form class, doing validation etc as usual. This allows me to use nested case classes in the mapping and get below the 18 params limit.

Beware: ugly code ahead! I should probably not show early hacky code like this, but I'm hoping it will help someone else who wants a workaround.

def preprocessFormParams(prefix:String, replace:String)(implicit request:Request[AnyContent]):Map[String, String] = request.body.asFormUrlEncoded.map( _.filterKeys( _.startsWith(prefix)).map( m => m._1.patch(0, replace, prefix.length)  -> m._2.head )).getOrElse(Map.empty)
def unprocessedFormParams(prefixes:Set[String])(implicit request:Request[AnyContent]):Map[String, String] = request.body.asFormUrlEncoded.map( _.filterKeys( !prefixes.contains(_) ).map( m => m._1 -> m._2.head )).getOrElse(Map.empty)

So these functions should probably be for comprehensions or split up, but here goes: preprocessedFormParms takes a prefix and replaces it:

val clientParams = preprocessFormParams("client_", "client.")
("client_id" -> "val1", "client_type" -> "val2") becomes ("client.id" -> "val1", "client.type" -> "val2")

When I have the parameters in the form of group.key1, group.key2 I can nest the case classes in the form like so

Form(mapping("client" -> mapping("type" -> nonEmptyText
    "id" -> optional(nonEmptyText),
    "secret" -> optional(nonEmptyText))
    (RegisterClient.apply)(RegisterClient.unapply)
    ... more params ...)
    (RegisterRequest.apply)(RegisterRequest.unapply)

In my action I go ahead and filter out each of my groups

implicit request =>
val clientParams = preprocessFormParams("client_", "client.")       
val applicationParams = preprocessFormParams("application_", "application.")
val unprocessedParams = unprocessedFormParams(Set("client_", "application_"))
val processedForm = clientParams ++ applicationParams ++ unprocessedParams

Lastly I can apply my form like normal but now I get the nested structure I that reduces the number of arguments and hopefully makes the case class more manageable.

clientRegistrationForm.bind(processedForm).fold( ... )

Using this approach you can keep the number of parameters down. If your parameters don't have the same prefix for easy grouping like my problem, then you can still use the same basic approach but filter on other criterias.

like image 982
Magnus Avatar asked Jun 14 '12 06:06

Magnus


People also ask

Is Play framework still used?

Play is rock-solid and used by hundreds of thousands of Java and Scala developers every month. Play is still extremely relevant to today's application and web development and has a passionate and very capable community around it ensuring that it has many good years left.

What is form in play framework?

Play's form handling approach is based around the concept of binding data. When data comes in from a POST request, Play will look for formatted values and bind them to a Form object. From there, Play can use the bound form to value a case class with data, call custom validations, and so on.

Is Play framework backend?

Play comes with two configurable server backends, which handle the low level work of processing HTTP requests and responses to and from TCP/IP packets. Starting in 2.6. x, the default server backend is the Akka HTTP server backend, based on the Akka-HTTP server.

What is activator in play framework?

The activator command can be used to create a new Play application. Activator allows you to select a template that your new application should be based off. For vanilla Play projects, the names of these templates are play-scala for Scala based Play applications, and play-java for Java based Play applications.


2 Answers

The mapping method you use is not a single method, but it is overloaded. For a single parameter, it has two type parameters, one for the result type and one for the element you're consuming. It constructs an ObjectMapping1. For two parameters, it has three type parameters and it constructs an ObjectMapping2.

These ObjectMappingX classes are defined up to ObjectMapping18, as you've noticed. You can find it in Play's source code in play/api/data/Forms.scala

The recommended solution is to avoid non-nested forms of this size. If that is unavoidable, you can either use a different library than the built-in Play one, or you can define the missing ObjectMappingX objects and corresponding methods to construct them yourself.

like image 113
Erik Bakker Avatar answered Sep 21 '22 12:09

Erik Bakker


I opened a ticket on this issue a couple of weeks ago.

If you vote for it, perhaps it will get a look from Play devs.

Doubt it's high on their priority list (unfortunate given that it's more or less just a copy-paste to tack on the 19, 20, 21, and 22 Mapping[T])

If you are desperate, you could fork Play; otherwise, come up with a workaround, for example, utilizing nested forms or splitting up > 22 field model into separate forms.

like image 42
virtualeyes Avatar answered Sep 21 '22 12:09

virtualeyes