Question

I have a class, DigitalObject which is basically a container for a url and the date that it was last modified.

class DigitalObject {
  String url
  Date lastUpdated
}

I have two classes that use DigitalObjects to reference different urls related to them

class Video {
   DigitalObject englishVersion
   DigitalObject frenchVersion
}

class Image {
   DigitalObject thumbnailImage
   DigitalObject fullSizeImage
}

I'm having a ton of trouble getting the mapping correct.

I want to be able to delete a DigitalObject and have GORM take care of removing it from it's parent.

If I leave it the way it is above when I try and delete the digitalObject I get a foreign key constraint violation.

If I add hasOne mapping on the parent side I get an error saying to specify the other side of the relationship, but I am unclear on where to do that as a DigitalObject can belong to either an Image or Video. Like such.

class Video {
   static hasOne = [englishVersion: DigitalObject, frenchVersion: DigitalObject]
}

Adding belongsTo in the child class results in the same foreign key constraint error. Like such.

class DigitalObject {
  String url
  Date lastUpdated

  static belongsTo = [Image, Video]
}

Am I going to have to just give up and handle removing it from its parent manually?

Thanks in advance for any assistance, and sorry if this is answered else ware I was unable to come up with a search query that provides results that deal with a case such as mine although I'm sure this can't be an uncommon use case.

EDIT 24 Jan 2014

Attempting Andrew's suggestion, my actual domain classes are a big more complex than the examples I used therefore my my beforeDelete() ends up like what follows, which is very ugly and could potentially be a pain to maintain.

def beforeDelete() {
    DigitalObject.withNewSession {
        def imageThumbnailImages = Image.findAllByThumbnailImage(this)
        if (imageThumbnailImages) {
            imageThumbnailImages.each { image ->
                image.thumbnailImage = null
                image.save(flush: true)
            }
        }
        def imagePreviewImages = Image.findAllByPreviewImage(this)
        if (imagePreviewImages) {
            imagePreviewImages.each { image ->
                image.previewImage = null
                image.save(flush: true)
            }
        }
        def imageFullImages = Image.findAllByFullImage(this)
        if (imageFullImages) {
            imageFullImages.each { image ->
                image.fullImage = null
                image.save(flush: true)
            }
        }
        def videoThumbnailImages = Video.findAllByThumbnailImage(this)
        if (videoThumbnailImages) {
            videoThumbnailImages.each { image ->
                image.thumbnailImage = null
                image.save(flush: true)
            }
        }
        def videoPreviewImages = Video.findAllByPreviewImage(this)
        if (videoPreviewImages) {
            videoPreviewImages.each { image ->
                image.previewImage = null
                image.save(flush: true)
            }
        }
        def videoFullVideosEng = Video.findAllByFullVideoEng(this)
        if (videoFullVideosEng) {
            videoFullVideosEng.each { video ->
                video.fullVideoEng = null
                video.save(flush: true)
            }
        }
        def videoFullVideosFra = Video.findAllByFullVideoFra(this)
        if (videoFullVideosFra) {
            videoFullVideosFra.each { video ->
                video.fullVideoFra = null
                video.save(flush: true)
            }
        }
        def captionsEngVideos = Video.findAllByCaptionsEng(this)
        if (captionsEngVideos) {
            captionsEngVideos.each { videos ->
                videos.captionsEng = null
                videos.save(flush: true)
            }
        }
        def captionsFraVideos = Video.findAllByCaptionsFra(this)
        if (captionsFraVideos) {
            captionsFraVideos.each { video ->
                video.captionsFra = null
                video.save(flush: true)
            }
        }
        def signLanguageEngVideos = Video.findAllBySignLanguageEng(this)
        if (signLanguageEngVideos) {
            signLanguageEngVideos.each { video ->
                video.signLanguageEng = null
                video.save(flush: true)
            }
        }
        def signLanguageFraVideos = Video.findAllBySignLanguageEng(this)
        if (signLanguageFraVideos) {
            signLanguageFraVideos.each { video ->
                video.signLanguageFra = null
                video.save(flush: true)
            }
        }
    }
Was it helpful?

Solution 2

This is ugly and probably not the best method for doing this but as I could not get the other methods suggested to work reliably, and due to the fact that I wanted to make the minimum changes as possible to the domain classes as my project is not the only one that uses them.

I added the following method to my DigitalObjectController.

def deleteFromMediaAsset(Long id, String parentClass, String parentProperty, String parentId) {
    def digitalObjectInstance = DigitalObject.get(id)
    if (!digitalObjectInstance) {
        flash.message = message(code: 'default.not.found.message', args: [
            message(code: 'digitalObject.label', default: 'DigitalObject'),
            id
        ])
        redirect(controller: parentClass, action: 'edit', id: parentId)
        return
    }

    try {
        def classOfParent = grailsApplication.domainClasses.find {
            it.clazz.simpleName == parentClass.capitalize()
        }.clazz

        def parentInstance = classOfParent.get(parentId)
        parentInstance.setProperty(parentProperty, null)
        digitalObjectInstance.delete(flush: true)
        flash.message = message(code: 'default.deleted.message', args: [
            message(code: 'digitalObject.label', default: 'DigitalObject'),
            id
        ])
        redirect(controller: parentClass, action: 'edit', id: parentId)
    }
    catch (DataIntegrityViolationException e) {
        flash.message = message(code: 'default.not.deleted.message', args: [
            message(code: 'digitalObject.label', default: 'DigitalObject'),
            id
        ])
        redirect(controller: parentClass, action: 'edit', id: parentId)
    }
}

Basically I pass the parent Image or Video class and ID along with the attribute to which the DigitalObject refers to and the ID of the DigialObject I wish to delete. I set the property to null on the parent object and then I delete the DigitalObject.

Ugly but it works for now, wish me luck.

OTHER TIPS

I have a similar domain class that can be owned by different parents and my cascade deletes work as expected. So using you class, have you tried this:

class DigitalObject {
    String url
    Date lastUpdated

    Image image
    Video video

    static belongsTo = [
         image: Image, 
         video: Video
    ]

EDIT: removed square brackets from below constraints!

    static constraints = {
        image( nullable : true )
        video( nullable : true )
    }

}

Not sure whether your hasOne on Image/Video is required at all. But try with and without.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top