Search code examples
grailsgrails-orm

Grails domain object storing Map<K,V>


I know from the Grails/GORM documentation that if you wish to store a Map of objects, both the key and value need to be Strings: Map<String,String>.

I'd like to use another domain object (Animal) as the key within this map, but due to the constraint above, that's not possible. The identifier of Animal can be easily converted to a string type, but if I were to do that, I don't believe GORM would be smart enough to perform the mapping when retrieving the parent object.

Has anyone run into this?


Solution

  • I think this should work:

    Assuming you have a domainclass that has a map:

    class Test {
       String name
       Map myAnimals=[:]
    
      //When given a specific key it will return actual animal object
      def findAnimal(String myKey) {
        return myAnimals.find{it.key==myKey}?.value
        //What above is doing can be seen broken down here:
        //def ani = myAnimals.find{it.key==myKey}
        //println "ANI::: ${ani} ${ani.value}"
        //def animal = ani?.value
        //return animal
    
      }
    }
    

    In a service when saving to map

    class TestService {
        def save(values) {
        Test test1 = new Test()
        test1.name='something'
        // so to add 3 of animal objects above values would be
        def animal1=Animal.find()
        String key1='ANI'  //For animals 
        def animal2=Monkey.find()  // where Monkey extends Animal (hence the keys)
        String key2='MON'  //For monkeys only
        test1.myAnimals[key1]=animal1
        test1.myAnimals[key2]=animal2
        test1.save()
    
          /**
          * When you have a real values map that contains useful method you can
          * do it this way - left commented out FYI on manual process above
          Test test = new Test()
          // you now have some map of values coming in that contains a key 
          // and actual object so you could make this up if you are testing
         values.animals?.each{k,v->
                    test.myAnimals[k]=v
                }
        }    
        test.save()
        */        
     }
    

    So the first example the values.each is where you have built your own map that contains a key and actual domain object being saved.

    The second test1 example is where I did it manually without having automated values to pass in as a test and possibly best starting point.

    Then when you have test object to get actual animal (as you can see limited to ) key so one animal one monkey one bird etc

    When you have

    Test test = Test.findByName('something)
    def animal = test.findAnimal('ANI')
    def monkey = test.findAnimal('MON')
    println "animal is ${animal} ${animal.getClass()} monkey is ${monkey} ${monkey.getClass()}"
    

    This now will look up the domain class method and attempt to return animal object as per call

    Before launching this I will need 1 animal and 1 Monkey added since it gets or does a find or first object above. So in my bootstrap:

    import test.Animal
    import test.Monkey
    
    class BootStrap {
    
        def init = { servletContext ->
    
            Animal animal = new Animal()
            animal.name='Daffy Duck'
            animal.save(flush:true)
            Monkey monkey = new Monkey()
            monkey.name='King Kong'
            monkey.save(flush:true)
        }
        def destroy = {
        }
    }
    

    When I run it I get from that println:

    animal is class test.Animal class java.lang.Class monkey is class test.Monkey class java.lang.Class
    

    Also getting some class to string error unsure which bit is causing it - the println from the output looks as you desire.

    You may have to choose a different method to hold the keys and use Animal as the main class to query so some other code for the key then you can change the findByAnimal to always return Animal object:

    //When given a specific key it will return actual animal object
        Animal findAnimal(String myKey) {
          Animal animal = myAnimals.find{it.key==myKey}?.value
          return animal
        }
    

    Higher call got changed due to it being Animal or Monkey which extended Animal. Two missing classes from above example:

    package test
    
    class Animal {
        String name
    
    }
    package test
    
    class Monkey extends Animal {
    
    
    }