grails 3.3.8 generated controller fails to create objects - grails

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.

Related

Grails3 generate-all generates faulty create action code

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)
}

Grails Session scope for service not working as expected

I'm making a web app that stores reports of various types as domain objects, so I have a domain object HeadOfHousehold which contains name data, and references to other domain objects such as the reports, addresses, and any dependants. I am trying to build a list of recently viewed/created HeadOfHousehold objects. After multiple Google searches, and scouring the manual, it appeared that a service would be an appropriate solution. So I created ClientListService:
#Transactional
class ClientListService {
static scope = "session"
String message // right now I'll be happy to just see the same message across
// pages I can add a list and manipulate it later.
}
I thought I could then reference it in my various controllers, and it would persist Something like this:
def clientListService
def index(){
hasSearched = false
clientListService = new ClientListService(message: "Hello")
[errorMessage: params.errorMessage, clients:clientListService]
}
Which should be available in a later controller:
class HeadOfHouseHoldController {
def clientListService
def index() {
[customer: HeadOfHousehold.get(params.id), clients: clientListService]
}//...
However when I try to get the message, it appears as if the object is null.
From my index.gsp:
***************${clients?.message}********************
So I don't know if I am not defining session properly (I'm not doing anything special to do so), if I'm misunderstanding how the session scope works, or something else. I do see the proper message on the original page which has defined the object, however I don't see it on any subsequent pages.
Also, I'm not sure if this is the proper way to go about this; right now all I really need is the list of HeadOfHouseholds that I would need (so I can add to the list from other pages), however I can see possibly adding other logic and items into such a class.
I think you understood the session scope correctly. Each Spring bean with a session scope is bound to the HTTP session.
But your first controller listing does it all wrong. You are not supposed to instantiate the service class yourself. This is what Spring (Grails) does.
class FooController {
def clientListService // gets autowired by Grails/Spring
def index(){
hasSearched = false
clientListService.message = 'Hello' // only assign a String value to the service
[errorMessage: params.errorMessage, clients:clientListService]
}
}
This means you cannot not do something like
clientListService = new ClientListService(message: "Hello")
and expect your code to work. Hope this helps.

Grails controller unit tests pass on the command line and not in Intellij

I am using Grails 2.3.8 and recently I started fixing some outdated tests that stopped working in the past couple of Grails 2.3.x versions.
During a Spock controller test, and when responding with a domain instance from a Controller, assertions against the Model fail/pass depending on the test execution environment. Specifically, when executing tests using the Grails console vs IntelliJ (Junit), the domain instance on the model has a different property name.
I have been hedging against it by making this type of assertion in my then or expect blocks in my Spock specifications:
void "show action correctly handles a valid instance"() {
given: "a valid domain instance"
def myDomainObject = MyDomainClass.build()
when: "calling the show action with a valid domain instance"
controller.show(myDomainObject)
then: "respond to the show view with the domain instance set on the model"
view == ‘show’
// the property name ends with ‘Instance’ in one env and not in the other
model.myDomainObjectInstance ?: model.myDomainObject == myDomainObject
}
Not sure if that could be a problem, but I think you should be saving the response from the controller in a variable and also accessing to the view using the renderArgs variable:
void "show action correctly handles a valid instance"() {
given: "a valid domain instance"
def myDomainObject = MyDomainClass.build()
when: "calling the show action with a valid domain instance"
def model = controller.show(myDomainObject)
then: "respond to the show view with the domain instance set on the model"
renderArgs.view == ‘show’
// the property name ends with ‘Instance’ in one env and not in the other
model.myDomainObjectInstance ?: model.myDomainObject == myDomainObject
}
Not sure if that is the problem.

Grails command object initialization

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}'

Grails - passing domain classes between controllers

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.

Resources