Search code examples
grailsgrails-ormgrails-2.0grails-domain-class

Grails deleting child


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)
            }
        }
    }

Solution

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