Grails Controller Web Binding does not work with Serializable or Generic Binding Objects
- Dominant language
- Groovy
- Stars
- 2.9k
- Forks
- 975
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 92
Description
The following (understandably) does not work, but a mechanism should be provided to facilitate binding
```groovy
class MyController {
def get(Serializable id) { // given a request to /my/1234
assert(id, null) // true
assert(params.id, != null) // true
assert(params.long('id'), != null) // true
}
def save(T instance) {
assert(instance, null)
}
}
```
[ControllerActionTransformer.java](https://github.com/grails/grails-core/blob/6.2.x/grails-plugin-controllers/src/main/groovy/org/grails/compiler/web/ControllerActionTransformer.java)
`accepts a parameter of type [java.io.Serializable]. Interface types and abstract class types are not supported as command objects. This parameter will be ignored.`
https://github.com/grails/grails-core/blob/8871703dc979e99bbc28669588ccbc48eca39342/grails-plugin-controllers/src/main/groovy/org/grails/compiler/web/ControllerActionTransformer.java#L844-L856
https://github.com/grails/grails-core/blob/9ea9c60bbed69718197549be83b900b2ed42eccf/grails-plugin-controllers/src/main/groovy/grails/artefact/Controller.groovy#L363-L460
# Solution Proposal - `@Bind` annotation
```groovy
@Bind(Sample)
class MyController extends GenericController {
SampleController() {
super(Sample)
}
}
class GenericController {
GenericController(Class domainClass) {
this.domainClass = domainClass
}
def get(Serializable id) { // given a request to /my/1234
assert(id, !null) // true
respond domainClass.get(id)
}
def save(T instance) {
assert(instance.class, domainClass) // true
domainClass.save instance, flush: true
respond instance
}
def update(T instance, @Bind(skip=true) Map model) {
// model is null but this is useful for inheritance where if
// this class is extended and super.update(instance, [message:'Hello world']) is called
// this method could do the update and return the model.
domainClass.save instance, flush: true
respond instance, model: (model?:[message:'Instance updated successfully'])
}
}
```
additionally the following will work the same and allow generic and serializable binding
```groovy
@Scaffold(value = GenericController, domain = User)
class UserController {
def update(User user) {
super.update(user, [message: 'User updated'])
}
}
```
@Bind should
1. `skip=true` allow suppression of controller warning messages.
2. enable binding of Serializable id to the same type of the id of the domain object.
3. enable binding of generic types to the specified object.
4. allow annotating of other annotations where it uses the domain attribute or generic attribute of that attribute.
For instance annotation `@Scaffold` annotation definition with `@Bind` will allow `@Scaffold(domain=Sample)` to also function as `@Bind` and only require 1 attribute.
Contributor guide
Research direction
Start with grails-plugin-controllers/src/main/groovy/org/grails/compiler/web/ControllerActionTransformer.java at the referenced parameter-handling code, then read grails/artefact/Controller.groovy at the linked binding logic. Trace existing controller binding tests and define coverage for Serializable identifiers, generic command objects, @Bind(skip=true), and annotation delegation. Done means these cases bind correctly without the current warning.
Written by the indexing model from the issue text.
Assessment
- Domain
- backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100