grails / grails-core

The Grails Web Application Framework
http://grails.org
Apache License 2.0
2.79k stars 949 forks source link

Grails 3.0.1 Cannot return a command object to my view #9002

Closed bodiam closed 9 years ago

bodiam commented 9 years ago

I'm trying to return a command object to my view instead of a Domain object, because in this view, I don't use domain objects. So, my code looks like this:

def index = {
    respond new Registration(params)
}

Which gives me the following error:

URI /registration
Class groovy.lang.MissingPropertyException
Message No such property: controller for class: registration.Registration
jeffscottbrown commented 9 years ago

Is the problem only with the respond method or do you see the same behavior if you return a Map as the model? Does the problem have anything to do with command objects?

jeffscottbrown commented 9 years ago

I am trying to recreate the problem. I have tried the following:

// grails-app/controllers/demo/DemoController.groovy
package demo

class DemoController {

    def showWidget(Widget widget) {
        [widget: widget]
    }

    def respondWithWidget(Widget widget) {
        respond widget
    }
}

class Widget {
    String name
}

I expect there is some other piece of the puzzle that is relevant. Can you create a simple sample app which demonstrates the problem, push that to a public github repo and provide a link here?

Thanks for your feedback.

jeffscottbrown commented 9 years ago

I see now that I can recreate the problem if I do something like this instead of using a command object...

respond new Widget(params)

Will investigate.

bodiam commented 9 years ago

Yeah, the respond is indeed the relevant part, sorry if I wasn't clear on that!

jeffscottbrown commented 9 years ago

The problem here doesn't have anything to do with respond or passing command objects to the view. The problem is that you are passing a Map as an argument to the constructor of your Registration class. That class doesn't have a property named controller but the Map you are passing does contain a controller entry. The MissingPropertyException is standard Groovy behavior. If you just do def foo = new Registration(params) I think you will see the same behavior. Domain classes have a special constructor which ignore extra entries in the Map. Registration apparently is not a domain class.

jeffscottbrown commented 9 years ago

It isn't clear why you are creating the Registration yourself, but you should be able to do something like this...

def index(Registration r) {
    respond r
}
jeffscottbrown commented 9 years ago

Yeah, the respond is indeed the relevant part

I don't think that is correct. See my comments above. Can you confirm, or is there another piece of this that I am missing.

Thanks for the feedback.

bodiam commented 9 years ago

Registration is indeed not a domain class. I'm creating the Registration class myself, because I copied that from the standard controllers generated by Grails:

    def create() {
        respond new Person(params)
    }

I thought the index(Registration r) only works when you pass a parameter in the URL

bodiam commented 9 years ago

No, you are right, this what's happening. However, I had to turn it into a domain class now, because of this: https://github.com/grails3-plugins/fields/issues/7

jeffscottbrown commented 9 years ago

I'm creating the Registration class myself, because I copied that from the standard controllers generated by Grails.

There shouldn't be any standard controllers generated by Grails that contain code like that which is instantiating anything other than a domain class.

I thought the index(Registration r) only works when you pass a parameter in the URL

That is not correct. That will work regardless of what parameters are in the URL. If there are parameters in the URL (or a body which can parsed that contains a recognized content type) then that information will be used to do data binding.

bodiam commented 9 years ago

I'm not saying that Grails code is generating code which doesn't return non-domain classes. I just figured that returning objects like this was a best practice. And in my case it wasn't a domain class, but a command object. That didn't seem like a big difference to me.

And thanks for the update on the parameter handling, I didn't know!

jeffscottbrown commented 9 years ago

I just figured that returning objects like this was a best practice.

It is. The problem here doesn't have anything to do with returning objects though. The problem is that the construction of your object is failing. You are not even getting to the return.

Is is as if you did this in standard Groovy (no Grails)...

class Widget {
    String title
}

def w = new Widget(controller: 'something', action: 'something', age: 2112)

That code will not work for the same reason that your code doesn't work.

jeffscottbrown commented 9 years ago

In general in Groovy when you pass a Map to a constructor like that, the entries in the Map have to correspond to properties in the instance being created. Domain classes have a special constructor in them which provides different behavior, but since your class isn't a domain class, you have the standard Groovy constructor behavior with respect to this.