apache / apache/grails-core

Grails Controller Web Binding does not work with Serializable or Generic Binding Objects

Open
#13,634 6 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.