Grails Controller Action Abstract Command Object Parameter

718 views Asked by At

Is there any support for using abstract command objects in controller action parameters? Then depending on the given parameters in a JSON request it would select the correct command object?

For example something like:

class SomeController {

    def someAction(BaseCommand cmd){
        // cmd could be instance of ChildCommandOne or ChildCommandTwo
    }

    class BaseCommand {
        String paramOne
    }

    class ChildCommandOne extends BaseCommand {
        String paramTwo
    }

    class ChildCommandTwo extends BaseCommand {
        String paramThree
    }

}

As of now I've been using request.JSON to detect the passed in parameters and instantiate the correct Command object. Is that my only option to handle this sort of case?

EDIT :

To clarify the use case here. I have two domain models that share the same base class domain model and I'm modeling the inheritance in the database using the default table-per-hierarchy model.

In my case, one of the child domain models Model A requires a non-nullable String called body, that is a Text entry, while the other Model B requires a non-nullable String called directUrl. These represent announcements that can be made on the platform. Model A being a write in entry that contains the announcement body while Model B represents a link to a third party site that contains the actual announcement.

In these sort of scenarios I've traditionally put an if statement in the controller action that determines which related command object to instantiate but I am hoping for a cleaner method.

2

There are 2 answers

2
injecteer On BEST ANSWER

It won't work this way. Grails needs a concrete class (with default public constructor) to bind request params to a command object instance. Therefore this class is to be defined explicitely as action's argument.

1
rgrebski On

I guess you will have to call binding manually depending on what map contains.
See RootModel.from(Map map). In your case Map would be params from Controller

import static com.google.common.base.Preconditions.checkNotNull

import spock.lang.Specification
import spock.lang.Unroll

class CommandHierarchySpec extends Specification {

    @Unroll
    def "should create object of type #type for map: #map"() {
        when:
            def modelObj = RootModel.from(map)
        then:
            modelObj.class == type
        where:
            type   | map
            ModelA | [body: 'someBody', test: 'test']
            ModelB | [directUrl: 'directUrl', test: 'test']
    }

    def "should throw ISE when map does not contain neither body nor url"() {
        when:
            RootModel.from(a: 'b')
        then:
            thrown(IllegalStateException)
    }
}


abstract class RootModel {
    static RootModel from(Map map) {
        checkNotNull(map, "Parameter map mustn't be null")

        RootModel rootModel
        if (map.body) {
            rootModel = new ModelA()
        } else if (map.directUrl) {
            rootModel = new ModelB()
        } else {
            throw new IllegalStateException("Cannot determine command type for map: $map")
        }

        map.findAll { key, value -> rootModel.hasProperty(key) }
                .each {
            rootModel.setProperty(it.key, it.value)
        }

        rootModel
    }
}

class ModelA extends RootModel {
    String body
}

class ModelB extends RootModel {
    String directUrl
}