In my Grails 2.3.8 app, I've defined the following controller action
class RegisterController {
def register(User user) {
render text: "User name is '$user.name'"
}
}
The user argument is a domain class instance. If I invoke this controller with the URL
http://localhost:8080/myapp/register/register
I get a NullPointerException. However my understanding of databinding is that if this action is invoked without any parameters, the argument should be assigned a new User()
However my understanding of databinding is that if this action is
invoked without any parameters, the argument should be assigned a new
User()
That is not necessarily the case. For domain class command objects if no parameters are present a new instance is only created for POST requests.
From http://grails.org/doc/2.4.0.RC1/guide/theWebLayer.html#commandObjects...
If the command object's type is a domain class and there is no id
request parameter then null will be passed into the controller action
unless the HTTP request method is "POST", in which case a new instance
of the domain class will be created by invoking the domain class
constructor.
That text may be missing from the 2.3.8 docs. I will verify that and add it if necessary.
What if you modify:
'$user.name'
To be:
'${user?.name}'
Related
I have a User domain object with the usual fields (name, password etc)
I generated the views and conltroller using:
grails generate-all User
The generaged UserController has this:
def create() {
respond new User(params)
}
When I run the app, and click on create user button, I get this error:
Error 500: Internal Server Error
URI
/user/create
Class
groovy.lang.MissingPropertyException
Message
null
Caused by
No such property: controller for class: gi.dam.desk.core.User
Around line 23 of grails-app/controllers/gi/dam/desk/admin/UserController.groovy
20: }
21:
22: def create() {
23: respond new User(params)
24: }
If I debug the app, and look at the contents of "params", its:
controller: user
format: null
action: create
it seems odd that this would be passed to creating a domain object. Either way, it fails.
If I change the code thusly:
def create() {
respond new User()
}
It gets further - it creates the user, but fails on the redirect after.
That looks like your User class is not being recognized as a domain class. I can tell that because the Map constructor that we add to domain classes will ignore entries in the Map that don't have corresponding properties in the class. The error you are seeing is what comes out of the default Groovy Map constructor, which is why I think your User class is not being recognized as a domain class.
Confirm that your User domain class is defined in grails-app/domain/gi/dam/desk/core/User.groovy and if the problem is still happening, verify that the problem occurs when running the app from the command line ./gradlew bootRun. This will rule out a number of things you might be doing that relate to an IDE being improperly configured.
I have a weird problem with integration testing restful controllers... In the following code snippet, when I make a post request from tests, the save method of the parent, RestfulController class is called instead of the save method of the child class, MyController and because they have different signatures, this ends up resulting in a UNPROCESSIBLE_ENTITY response.
class MyController extends RestfulController<MyDomain> {
static responseFormats = ['json', 'xml', 'hal']
MyController() {
super(MyDomain)
}
def save(MyCommand command) {
...
}
}
When I run the following test, the save() action of my controller's parent class, RestfulController gets executed, thus leading to UNPROCESSIBLE_ENTITY response, since I am using a Command object which is different from my domain class.
void "Test the save action correctly persists an instance"() {
when: "The save action is executed with valid data"
response = restBuilder.post(resourcePath) {
accept('application/json')
header('Authorization', "Bearer ${accessToken}")
json validJson
}
then: "The response is correct"
response.status == CREATED.value()
response.json.id
Vote.count() == 1
}
What can I do to fix this, please?
Overloading controller actions is not supported. You can override them, but you can't overload them.
What is happening is the framework is invoking the no-arg save() action in the parent class, which never invokes your method (nor should it).
You can rename your save(MyCommand command) so it doesn't have the same name as an action in your parent class and then provide a corresponding URL mapping and you will be on your way. Depending on what you want to do in the action, that may or may not be the best thing, but that is 1 path you can take.
I hope that makes sense.
When a domain class is used as a command object and there is an id request parameter, the framework will retrieve the instance of the domain class from the database using the id request parameter.
How can i disable this feature, i do not want the framework go to the database to retrieve the domain instance
You can still do databinding from request params with a slightly more verbose style; change
def myAction(MyDomainClass myDomainClass) {
...
}
to
def myAction() {
MyDomainClass myDomainClass = new MyDomainClass(params)
...
}
This will not trigger a database call if there's an id in params and will ignore id and version properties since they're not bindable by default.
When I use generate-all package.DomainObject, it generates a controller where create action is generated as:
def create() {
respond new DomainObject(params)
}
When I call the localhost:8080/DomainObject/create even without making any code change, it throws an exception:
groovy.lang.MissingPropertyException: No such property: controller for
class: package.DomainObject
It looks like introspection is failing for properties that params map has and DomainObject does not have. This is surprising because in the grails 2, introspection used to just ignore the non-matching properties and it also used to do necessary type conversions on fields as well (now if DomainObject has an int property, it will throw a type mismatch exception because params map passes it as String). This is really inconvenient. Did something change or I am missing something?
Using the map constructor and setting properties in bulk with a map in Grails is basically the same as in Groovy, but it has logic to exclude 'controller', 'action', and 'format' keys to keep controller code like this uncluttered. That broke in 3.x and has been reported in the issue tracker. It's not marked fixed but works correctly for me in a simple 3.0.4 test app.
As a temporary workaround you can copy the params map and remove values stored under those keys and use the 'fixed' map for the constructor:
def create() {
def fixedParams = ([:] + params) // copy
['controller', 'format', 'action'].each { fixedParams.remove it }
respond new Thing(fixedParams)
}
In my Grails application, I have created a User class. In one controller, I query the database to confirm that the information a user has given is valid. Then, I would like to pass that User object to another controller to do some more processing on it. Is this possible? Thanks!
For your question on passing objects between controllers, if you are using a redirect or a forward you can add objects in the param map:
redirect(action: "actionName", user: userInstance)
or
forward(action: "actionName", user: userInstance)
Another solution would be to store the user in the flash object (a temporary storage map cleared after the next request) or session:
flash.user = userInstance
or
session.user = userInstance
But in your case, as stated by tim_yates, you should create a service to handle the User entity.
To do so execute the command:
create-service com.package.user
Then add all the processing you are doing on a User in the newly created class and inject the User service in your controller like this:
def controller{
def userService
def action(){
userService.validate(...)
}
}
Essentaily what #tim_yates was getting at was the logic for all of your controllers should be in services. Then any action in any controller can execute that logic without redirecting/forwarding a request. This also is the way it should be due to the transactional nature of services.