Igor Artamonov
Igor Artamonov

Reputation: 35961

Grails replace object in one-to-one association

I have two following domains:

User {
   UserData userData
}

UserData {
   static belongsTo = [user: User]
}

and at some point I want to merge two users into one. I mean delete one instance of User and attache userData to another user.

I've tried:

User zombieUser
User liveUser

UserData data = zombieUser.userData
zombieUser.delete()
liveUser.userData = data
userData.user = liveUser
userData.save()
liveUser.save()

Actually I've tried different variants, different order, seems that all possible ways. But it always fails with an exceptions. Current code will fail with:

deleted object would be re-saved by cascade (remove deleted object from associations): [UserData#1]

If i've put zombie.delete() to bottom, after *.save(), I will get:

Field error in object 'User' on field 'userData': rejected value [UserData : 1]; codes ... default message [Property [{0}] of class [{1}] with value [{2}] must be unique]

Are there any way to reconnect existing object from one object to another?


Working code:

UserData userData = zombieUser.userData

userData.user = null
zombieUser.userData = null
zombieUser.save(flush: true)
userData.save()

liveUser.userData = userData
userData.user = liveUser
liveUser.save()
userDate.save(flush: true)

Upvotes: 1

Views: 1348

Answers (2)

Antoine
Antoine

Reputation: 5198

The problem is that because of your belongsTo declaration, and according to cascading rules, userData is deleted from the database as soon as zombieUser is deleted. When liveUser.save() is called, userData would be saved again. By the way, you call to userData.save() is not needed as again, because of this belongsTo declaration, liveUser.save() is cascaded to userData.

I see two options for solving your problem:

  1. Perform a deep copy of userData to a new UserData object, and attach this object to liveUser, like this:

    withTransaction {
      UserData data = new UserData(prop: zombieUser.userData.prop)
      liveUser.userData = data
      zombiUser.delete()
      liveUser.save()
    }
    

    The old UserData object will be deleted by cascade when deleting zombieUser and a new one will be created when saving liveUser.

  2. Remove belongsTo from userData. You won't benefit of cascading anymore, and will have to manage saving userData yourself, and make sure user.userData is deleted from the database when no user reference it (don't forget to make transactions for that, as always when you have several call to save or delete in your method).

Which approach you choose depends on how coupled your objects are. Typically, if they are very coupled, you will benefit of cascading (through belongsTo) and may go for 1. If they are not very coupled, (i.e. there may be several users with the same userData), then belongsTo is wrong and you should choose approach 2.

The concepts presented in GORM gotchas will greatly help you if you have a GORM model with complicated dependencies.

Upvotes: 1

gotomanners
gotomanners

Reputation: 7906

You need to add a mapping to your domain class to tell it what to do when you delete an object. For example, should deleting a parent also delete all its children??

static mapping = {
    userData cascade: "all-delete-orphan"
}

Upvotes: 0

Related Questions