Update "lastUpdated" Field in Parent Domain Class in Grails
Asked Answered
J

3

13

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.

Jegar answered 26/6, 2012 at 16:1 Comment(2)
"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 itGaribold
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.Nonconformity
B
3

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.

Biology answered 17/7, 2012 at 21:37 Comment(4)
is he really works?, i didnt try this before or any where I have seen this type of thing any more...!!!Octosyllable
Sorry, I gived you a downvote: Suggested OP's solution work and your feeling was wrong. And the formula is bringing a lot of new changes: timestamp for the children too, a formula that might not been called if your parent entity is not flushed and queried again...Nonconformity
I think the sentiment here is correct. You shouldn't really modify the implementation of lastUpdated as you don't really know what else might depend on it. I think this ends up being an elegant solution. However, I think there is an error in the code example, I'll update it now. The example is not storing an additional column as User Guillaume suggests, the column is simply how you would access the value of the formula.Pronto
@Guillaume, perhaps "feeling" isn't the best word to use here. What I was getting at is what Daniel Bower was suggesting -- updating that way may work now, but you're breaking the convention set by Grails, which means it might not work in the future. I agree with the downsides of using a formula that you pointed out, but those problems are dependent on the implementation -- not the framework.Biology
E
1

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.

Earp answered 27/11, 2013 at 21:0 Comment(0)
O
0

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)"
}
Octosyllable answered 29/1, 2013 at 8:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.