grailsdata-bindingabstract-classinstantiationcommand-objects

Instantiating abstract class multiple times with one action


So I have a, let's say, Pet abstract class, and three concrete implementations of it -- let's say Cat, Dog, and Bird. As concrete classes do, these guys share common fields and also have unique fields -- e.g., they all have Legs, but Birds alone have Wings. So, the GSPs are different, the update methodology is different, etc.

The trick is that I need to be able to instantiate, validate, and persist an indeterminate number of instances in a single action. A straightforward implementation is essentially as follows:

create.gsp

Cat Hair Color: <g:textField name="catInstance.hairColor"/>
Dog Hair Color: <g:textField name="dogInstance.hairColor"/>
Bird Feather Color: <g:textField name="birdInstance.featherColor"/>

PetCommand.groovy

class PetCommand {

    Cat catInstance
    Dog dogInstance
    Bird birdInstance

}

PetController.groovy

def save(PetCommand cmd) {

    def catInstance = cmd.catInstance
    def dogInstance = cmd.dogInstance
    def birdInstance = cmd.birdInstance

    /* do stuff */

}

Of course in a real application this gets significantly messier, and this completely defeats the purpose of using abstract classes.

Is there some way instead to bind multiple Pet instances in a single fell swoop and then just loop through them and e.g., pass in parameters to update? I don't know, this whole thing is very confusing.

Command objects are not strictly necessary, but they fix a lot of the annoying redundancy of Grails controllers.


Solution

  • So based on Joshua Moore's comments above, it looks like the answer will be something like this:

    Template (e.g., _bird.gsp)

        Bird Feather Color: <g:textField name="pets['bird'].featherColor"/>
    

    GSP

        /* based on other logic, render appropriate templates */
    

    Command Object

        List pets = [].withLazyDefault { petType -> return PetFactory.createPet(petType) }
    

    Factory (naive implementation)

        def createPet(String petType) {
            switch (petType) {
                case 'bird': return new Bird(); break;
            // ...etc...
        }
    

    Controller

    def update(PetCommand cmd) {
        cmd.pets?.each {
            petService.update(it, params)
        }
        /* ...etc... */
    }
    

    Service

    def update(Pet petInstance, Map params) {
        petInstance.update(params)
    }
    

    Domain

    def update(Map params) {
        /* domain-specific business logic based on user input */
    }
    

    This is not yet implemented; I'll try to update it if it turns out not to work.