pragmus
pragmus

Reputation: 4043

Grails, GORM, relationship. Optional child records

For example, I've parent class Cafee:

class Cafee {    
    String name
    static hasMany = [
         admin: Person
    ]
}

and a child class Person:

class Person {    
    String name
    static belongsTo = [cafee: Cafee]
}

I've done some records to Cafee using:

def user = new Person(name: "Andrew")
def a = new Cafee(name: "Tarelka")
             .addToAdmin(user)
             .save()

Adding child to parent works fine, but when I trying to create Person-instance separately, for example:

def visitor = new Person(username: 'testerUser', password:'password', firstName:'Иван', lastName:'Иванов', email:'[email protected]', isAdminCafee: false)
         visitor.save(flush:true)

I get an error:

ERROR context.GrailsContextLoaderListener  - Error initializing the application: object references an unsaved transient instance - save the transient instance before flushing: restorator.auth.Person; nested exception is org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: restorator.auth.Person
Message: object references an unsaved transient instance - save the transient instance before flushing: restorator.auth.Person; nested exception is org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: restorator.auth.Person

How to fix it?

Upvotes: 2

Views: 726

Answers (1)

Burt Beckwith
Burt Beckwith

Reputation: 75671

There are two things going on here that aren't entirely obvious. One is that this form of belongsTo

static belongsTo = [cafee: Cafee]

is bidirectional, as compared to

static belongsTo = [Cafee]

which is undirectional; in both variants a Cafee can access its associated Person instances via its admin collection, but using the second syntax there's no direct way for a Person to know which Cafee it's associated with.

Declaring a hasMany like you did creates a Set of Persons in the class, and its name is the key you used in the hasMany map, in this case admin; it's as if you had added

Set<Person> admin

but you shouldn't because it's redundant - an AST transform adds that property to the bytecode.

Similarly, when declaring

static belongsTo = [cafee: Cafee]

a field of type Cafee with the name cafee is added to the class. It's as if you added

Cafee cafee

but again, please don't manually add either, just be aware that they're there.

So the problem is that persistent properties are not-null by default unless you override with nullable: true, so if you printed out the errors for that Person instance you'd see at least one complaining that the non-nullable cafee property is null. This works with addToAdmin because that method does a lot. If instantiates the set to a new Set interface implementation (probably just a HashSet) if it's null (which should only ever occurr when the domain class instance is new; a persistent set will never be null, just empty or containing items), then adds the Person to the collection, and finally if the relationship is bidirectional, it sets the backreference on the Person to the owning Cafee.

So all you're missing is setting the Cafee manually, either as part of the map constructor

user = new Person(cafee: a, username: 'testerUser', ...)

or later in the workflow (but before validation or saving)

user = new Person(username: 'testerUser', ...)
...
user.cafee = a
...
user.save()

Upvotes: 2

Related Questions