1

I'm wondering how I would go about adding the functionality of cloning to my grails application. I've attached an image below that explains how my domain classes are associated. One template has many steps and those steps each have many inputs and or outputs.

Temp

Currently I can view my templates on the index.gsp page but I want to be able to clone entire templates along with their steps/inputs/outputs that they contain aswell.

Is this possible and if so how?

Jamie
  • 73
  • 1
  • 13
  • What do you mean by "clone"? Your question doesn't explain what that means and what you expect as a result. – Joshua Moore Nov 30 '14 at 20:38
  • I'm not entirely sure how to word it but I want to be able to take an instance of a Template (Domain class) and be able to duplicate all of its values along with all of the values inside the instances it's related with. Sorry if that's not any clearer. – Jamie Nov 30 '14 at 22:21
  • Nope, that's pretty clear now. You may want to look at these other posts that seem to address your needs: http://stackoverflow.com/questions/20220711/proper-implementation-of-clone-for-domain-classes-to-duplicate-a-grails-domain and http://stackoverflow.com/questions/17614791/how-can-i-duplicate-a-domain-object-in-grails – Joshua Moore Nov 30 '14 at 22:22
  • Thanks! Do you know how to handle the ID's with this? – Jamie Dec 01 '14 at 15:57
  • That all depends on what you mean by "handle the ID's". You should be able to modify the examples given to do whatever you need. – Joshua Moore Dec 01 '14 at 16:02
  • Using integration tests I can see that the ID of the clonedTemplate is 0. I had a feeling getting all of the ID's to be generated correctly would be a struggle but I've no idea where to start with that. – Jamie Dec 01 '14 at 16:18

1 Answers1

3

Here is a version of deep cloning. Though It's a bit customized to meet specific needs, it's very generic. And I'm pretty sure above said scenario is well covered by this.

 Object deepClone(def domainInstanceToClone, def notCloneable) {
        return deepClone(domainInstanceToClone, notCloneable, null)
    }

Object deepClone(def domainInstanceToClone) {
    return deepClone(domainInstanceToClone, null, null)
}

Object deepClone(def domainInstanceToClone, def notCloneable, def bindOriginal) {

    if (domainInstanceToClone.getClass().name.contains("_javassist"))
        return null

    //Our target instance for the instance we want to clone
    def newDomainInstance = domainInstanceToClone?.getClass()?.newInstance()

    //Returns a DefaultGrailsDomainClass (as interface GrailsDomainClass) for inspecting properties
    GrailsClass domainClass = domainInstanceToClone.domainClass.grailsApplication.getDomainClass(newDomainInstance.getClass().name)

    for (DefaultGrailsDomainClassProperty prop in domainClass?.getPersistentProperties()) {
        if (notCloneable && prop.name in notCloneable) {
            continue
        }
        if (bindOriginal && prop.name in bindOriginal) {
            newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
            continue
        }

        if (prop.association) {
            if (prop.owningSide) {
                //we have to deep clone owned associations
                if (prop.oneToOne) {
                    def newAssociationInstance = deepClone(domainInstanceToClone?."${prop.name}", notCloneable, bindOriginal)
                    newDomainInstance."${prop.name}" = newAssociationInstance
                } else {
                    domainInstanceToClone."${prop.name}".each { associationInstance ->
                        def newAssociationInstance = deepClone(associationInstance, notCloneable, bindOriginal)

                        if (prop.oneToMany) {
                            if (newAssociationInstance) {
                                newDomainInstance."addTo${prop.name.capitalize()}"(newAssociationInstance)
                            }
                        } else {
                            newDomainInstance."${prop.name}" = newAssociationInstance
                        }
                    }
                }
            } else {
                if (!prop.bidirectional) {
                    //If the association isn't owned or the owner, then we can just do a  shallow copy of the reference.
                    newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
                }
                // @@JR
                // Yes bidirectional and not owning. E.g. clone Report, belongsTo Organisation which hasMany
                // manyToOne. Just add to the owning objects collection.
                else {
                    //println "${prop.owningSide} - ${prop.name} - ${prop.oneToMany}"
                    //return
                    if (prop.manyToOne) {
                        newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
                        def owningInstance = domainInstanceToClone."${prop.name}"
                        // Need to find the collection.
                        String otherSide = prop.otherSide.name.capitalize()
                        //println otherSide
                        //owningInstance."addTo${otherSide}"(newDomainInstance)
                    } else if (prop.manyToMany) {
                        //newDomainInstance."${prop.name}" = [] as Set
                        domainInstanceToClone."${prop.name}".each {
                            //newDomainInstance."${prop.name}".add(it)
                        }
                    } else if (prop.oneToMany) {
                        domainInstanceToClone."${prop.name}".each { associationInstance ->
                            def newAssociationInstance = deepClone(associationInstance, notCloneable, bindOriginal)
                            newDomainInstance."addTo${prop.name.capitalize()}"(newAssociationInstance)
                        }
                    }
                }
            }
        } else {
            //If the property isn't an association then simply copy the value
            newDomainInstance."${prop.name}" = domainInstanceToClone."${prop.name}"
            if (prop.name == "activationDate") {
                newDomainInstance."${prop.name}" = new Date()
            }
        }
    }
    return newDomainInstance
}

Example usage is :-

Template cloneTemplate = cloneService.deepClone(originalTemplate,["id","name"],["parent"])

1st parameter is original object that is to be cloned 2nd parameter is the list of columns that must not be cloned 3rd parameter is list of properties that must be referenced as it is.e.g. Template might belong to some parent which must remain same during clone.

To save cloned object create another method that meets your custom requirements.Above code will work in other scenarios too.

Vinay Prajapati
  • 7,199
  • 9
  • 45
  • 86
  • 1
    To get the above to work as a service, create a CloneService.groovy and for it to compile, you'll need these two def's right after: class CloneService { def grailsApplication def databaseUtilityService and you'll need these two imports at the top:import org.codehaus.groovy.grails.commons.DefaultGrailsDomainClassProperty import org.codehaus.groovy.grails.commons.GrailsClass – Twelve24 Jun 17 '15 at 17:10