I've been working on a bug for most of the day today. From all of my understanding this bug shouldn't have been happening. This is always a pain because it means something is, obviously, wrong with my understanding, but because of that I didn't know where to look.
The issue was that I was getting an unsaved transient object instance error when I tried to save updates to an object from a form. My issue with it was that I wasn't creating any new objects in my Controller code. If I'm not creating any new objects, how can there be a unsaved transient? And thus I spent a few hours learning through fiery trial-and-error, because I couldn't find anything that would actually tell me what object was transient or where it came from.
The solution to this problem was in some of Grails' behind-the-scenes, automagic data-binding. Normally you can data-bind an associated object using a field in your form with a name like "associatedObject.id" and then Grails will automatically setup the relationship for you when you bind the request parameters to your object. What I've now discovered is that Grails will also attempt to look up an associated object if you have a field in your form with a name like "associatedObject" even if you don't use it for anything, and when it fails to find the object it creates a transient for you.
I needed to do something special with that field so I was using "associatedObject" instead of "associatedObject.id" and then in my data binding I was excluding that field from the binding:
bindData(objectInstance, params, [exclude:['associatedObject']])
objectInstance.save(flush:true)
-- unsaved transient exception
Again, the unsaved transient was an automatically created object that Grails created when it failed to lookup a match for the "associatedObject" field--this is regardless of the fact that I never actually tried to use that field for anything yet.
So I had to change the name of the field to something else, let's say 'assocObject'. And once that's done it's perfectly happy to do what I want:
bindData(objectInstance, params)
objectInstance.save(flush:true)
// Saves successfully
Just thought I'd throw this out there since I spent several hours of my life discovering this little nuance and wasn't able to find any useful information on the Internet. I just wouldn't have expected Grails to create a secret object behind the scenes like that when I never actually tried to use the value for anything. Sure, if I attempted to bind it, great, work your magic; but if not, I wouldn't expect it to interfere.