GrailsInAction / graina2

Source code for the 2nd edition of Grails in Action
90 stars 92 forks source link

MEAP v13 ch07 Listing 7.10 register controller code not validating against profile object #77

Closed danhyun closed 10 years ago

danhyun commented 10 years ago

Using Grails 2.3.4

The listing should also validate against user.profile

def register() {
    if (request.method == "POST") {
        def user = new User(params)
        if (user.validate() && user?.profile?.validate()) {
            user.save()
            flash.message = "Successfully Created User"
            redirect(uri: "/")
        } else {
            flash.message = "Error Registering User"
            return [user: user, profile: user.profile]
        }
    }
}

The register.gsp should also be updated to list the validation errors

<g:hasErrors>
    <div class="errors">
        <g:renderErrors bean="${user}" as="list"/>
        <g:renderErrors bean="${user.profile}" as="list"/>
    </div>
</g:hasErrors>

I had to return profile as a top level key because <g:hasErrors/> was not picking it from the user object. I'm sure there's a better way to do this but without profile validation, the register action will only validate the user object and fail if profile does not validate causing an exception to be thrown as well as storing user sans profile.

One more consideration is that the profile object having a null user will crop up as an error. To the end user this is meaningless so hopefully there is a way to blacklist this error.

PS: This updated register code will fix the null user on profile object validation

def register() {
    if (request.method == "POST") {
        def user = new User(params)
        if (user.validate()) {
            def profile = user.profile
            user.profile = null
            user.save()

            profile.user = user
            if (profile.validate()) {
                profile.save()
                flash.message = "Successfully Created User"
                redirect(uri: "/")
            } else {
                flash.message = "Error Registering User"
                return [user: user, profile: user.profile]
            }
        } else {
            flash.message = "Error Registering User"
            return [user: user, profile: user.profile]
        }
    }
}

This messy code can be used to gain an even greater appreciation for the Command objects for data binding and validation.

pledbrook commented 10 years ago

We will need to investigate this, but the associations should be validated as well automatically, as described in the Grails reference guide. You'll see that the deepValidate option defaults to true.

danhyun commented 10 years ago

Thanks for the response, I tried both null map and deepValidate: true versions of user.validate and still could not get it to validate the profile object. Please let me know if you need my version of hubbub to reproduce if you cannot reproduce in 2.3.4.

pledbrook commented 10 years ago

I uncovered what the issue here is: the hasOne on User cascades saves, but it does not cascade validation. To get validation across associations, you need a belongsTo on the other side. I have made a note of this in the manuscript and updated the sample source code.