postgrailsdata-bindinggrails-controllercommand-objects

Grails 3: Binding multiple command objects via POST


I have a controller with a method that I want to bind multiple command objects too. When I call the method via GET, it works great and both objects get bound. The issue is if I call the method via POST, only the first command object gets bound and the second one is completely ignored.

Simple example:

def register(MembershipCommand cmd1, RegisterCommand cmd2) {
        println(cmd1.email);
        println(cmd2.pass);
        respond([:]);
}

If I call /register?email=test&pass=test then cmd1 and cmd2 get populated

If I call /register with POST data {email:test,pass:test}, cmd1 gets populated and cmd2.pass is null.

Is there a way to make this data binding work using POST? I can't really use GET because file uploads are involved and plus the forms are fairly large.

I know that another option would be to just split the method into 2, 1 for each object and have my form submit to each separately but I want to avoid that if I can.

Any ideas?


Solution

  • I have created a minimal working project to test your idea. It works like a charm. Below is the snippets.

    RegisterCmd.groovy

    class RegisterCmd {
        String email
    }
    

    edit.gsp

    <g:form resource="${this.player}" method="POST" action="customisedUpdate">
            <g:hiddenField name="version" value="${this.player?.version}" />
            <fieldset class="form">
                <f:field bean="player" property="name" />
                <f:field bean="player" property="game" />
                <f:field bean="player" property="region" />
                <label>Email</label><g:field type="text" name="email"/>
            </fieldset>
            <fieldset class="buttons">
                <input class="save" type="submit" value="${message(code: 'default.button.update.label', default: 'Update')}" />
            </fieldset>
        </g:form>
    

    PlayerController.groovy

        @Transactional
        def customisedUpdate(Player player, RegisterCmd registerCmd) {
            println "Calling save ${player.dump()}"
            println "RegisterCmd: ${registerCmd.dump()}"
            //end::save[]
            //tag::save-handleErrors[]
            if (player == null) {
                render status: HttpStatus.NOT_FOUND
                return
            }
    
            if (player.hasErrors()) {
                respond player.errors, view: 'create'
                return
            }
            //end::save-handleErrors[]
    
            player.save flush: true
    
            request.withFormat {
                form multipartForm { redirect player }
                '*' { respond player, status: HttpStatus.CREATED }
            }
            //tag::save[]
        }
    

    The output looks like:

    Calling save <com.itersdesktop.javatechs.grails.Player@1c25113d name=Alexis Barnett game=Pandemic region=EAST wins=96 losses=30 id=1 version=4 org_grails_datastore_mapping_dirty_checking_DirtyCheckable_
    _$changedProperties=[name:HUE THI MY NGO] org_grails_datastore_gorm_GormValidateable__errors=org.grails.datastore.mapping.validation.ValidationErrors: 0 errors org_grails_datastore_gorm_GormValidateable
    __skipValidate=false>
    RegisterCmd: <com.itersdesktop.javatechs.grails.RegisterCmd@7ee6bb8c email=alexis.barnett@gmail.com grails_validation_Validateable__beforeValidateHelper=org.grails.datastore.gorm.support.BeforeValidateH
    elper@3409999e grails_validation_Validateable__errors=grails.validation.ValidationErrors: 0 errors>
    

    If you are interested in the project, please consult it at https://bitbucket.org/itersdesktop/command-objects/src/master/