12
votes

I have a parent domain class the has a hasMany of another domain class. Both the parent and the child domain classes have the lastUpdated and the dateCreated fields. My issue is that when I update a child domain class, I need the parent domain class to reflect that change and update its lastUpdated field as well.

Is there any mapping or other configuration between the parent and child that Grails provides that would implement this feature?

Update

I added the following lines to the child domain class:

def beforeUpdate = {
    parent.lastUpdated = new Date()
}

I also had to make sure in the controller that when I updated a child, I also had to save the parent as well to persist the new lastUpdated field. This seems to work fine, but I would still like to know if there is a mapping or something similar that would do this.

3
"I also had to make sure in the controller that when I updated a child, I also had to save the parent as well" <--- I think is not necessary... try to remove the save() on the parent and look if it works even without itFabiano Taioli
With Grails 2.2.0, the beforeUpdate technique works. Saving the parent is not necessary and you won't have any double save behaviour. So you don't need any formula, just the listener.Guillaume

3 Answers

2
votes

I have a feeling your suggested implementation will be buggy. You're updating the parent by setting the lastUpdated date manually, which might cause grails to update lastUpdated again after it does dirty checking on it. If that's the case, you would actually end up with a lastUpdated time that occurred after the date you original set. In your testing, this might be only a few (milli)seconds, but you can't guarantee that.

Not only that, but your implementation is harder to maintain since you have increased the coupling of Parent and Child.

Might I suggest another implementation? The lastUpdated field is supposed to represent the time the specific domain object was updated. The date you're looking for is not quite the same thing, so I wouldn't try to use the existing convention in the "wrong" way. It sounds like the date you want for the parent object is "the last time a child was modified".

Use a formula instead.

To do that, you could use a formula. With a formula, you get exactly what you want without having to directly modify the parent object, and you can still use dynamic finders and other Grails sugar.

class Parent {
    ...
    Date lastChildUpdated

    static hasMany = [ children: Child ]

    static mapping = {
        ...
        lastChildUpdated formula: '(SELECT max(c.last_updated) FROM child c WHERE c.parent_id = id)'
    }
}

GORM will load the value of the formula in whenever you read the object from the database. Now, whenever you save a Child, the parent will have an accurate value for that property without having to touch the Parent.

1
votes

I used a hack. I have added a Long updateTrigger field to my parent domain class and a touch method:

class Parent {
    Long updateTrigger

    static mapping = {
        autoTimestamp true
    }

    static constraints = {
        updateTrigger(nullable:true)
    }

    public touch() {
        if (updateTrigger == null) updateTrigger = 0
        updateTrigger++
        this
    }

In the update/save actions of the child controller, I just call:

child_instance.save() // save the child
child_instance.parent.touch().save() // updates parent's time stamp

This will increment the updateTrigger value, and the save() will automatically update the lastUpdated field thanks to the autoTimestamp set to true in the mapping. updatedTrigger is set to be nullable so that it doesn't invalidate any existing database table and therefore can be added anytime to any domain class.

0
votes

In one of my project where the domain was like. A Program has many AdvertisingMaterial and we have subclasses of AdvertisingMaterial, FlashAd, ImageAd etc. The user want the ability to filter the programs which has flashAds, imageAds etc. Now I need to do the filtering on the basis of the class property that we have in database table (When table tablePerHierarchy is true). So I did some changes in my domain class to get this property.

class AdvertisingMaterial {
        String className

        static constraints = {
                className(nullable: true)
        }

       static mapping = {
               className formula: 'CLASS'
       }
} 

Now what I can use this className field in my dynamic finders and criteria query as well. So I can do something like

List<AdvertisingMaterial>adMaterials=AdvertisingMaterial.findAllByClassName("com.project.FlashAd")

static mapping = {
               fullName formula: "CONCAT(FIRST_NAME,'  ',LAST_NAME)"
               totalAmount formula: "SUM(AMOUNT)"
}