0

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.

Charles Wood
  • 864
  • 8
  • 23
  • Using a command object as you have here is the correct approach. There isn't a way to bind a list of abstract classes to concrete instances using the built in binding in Grails. However, you could explore using a factory and making your GSP more generic e.g. `pet[0].someProperty`, `pet[0].type' and `pet[1].someProperty`, `pet[1].someOtherProperty`, `pet[0].type'. Hoever, depending on how complex your domain model is that could get very difficult to maintain. Just some things to think about. – Joshua Moore Sep 03 '14 at 23:38
  • That's an interesting thought. Would you use a `LazyList` with factory to instantiate the pets inside the command object? And I suppose in the GSP, I would loop through an array provided by the controller and pull in templates based on... something... – Charles Wood Sep 03 '14 at 23:52
  • You are correct with the command object using a `LazyList` in conjunction with the factory. I kind of assumed the GSP would be static but if you wanted to render it based on some type of configuration or metadata I guess you could. Depends on your requirements really. – Joshua Moore Sep 04 '14 at 13:13
  • Yeah, my department can never manage to have simple requirements ;) – Charles Wood Sep 04 '14 at 15:33
  • 1
    Then using some type of metadata and a factory should be able to address your needs. – Joshua Moore Sep 04 '14 at 16:20
  • Hm; the factory will return a _new_ Pet, but if I pass in, e.g., `pet['bird'].id = 32`, will the command object know to bind that onto the new Pet? – Charles Wood Sep 04 '14 at 17:03
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/60642/discussion-between-joshua-moore-and-charles-wood). – Joshua Moore Sep 04 '14 at 17:08

1 Answers1

0

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.

Charles Wood
  • 864
  • 8
  • 23
  • FYI it basically works. The command object does not bind more than one layer deep, but that was probably fixed with the new binding in 2.3. I just end up manually assigning unbound params in the domain.update() method. – Charles Wood Oct 10 '14 at 16:15