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.
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.