I am currently developing a Grails Application and I am working with the Spring Security and UI plug-ins. I have created the relationship between the User Class and another Area Class that I have which can be seen below:
User Class:
class User {
transient springSecurityService
String username
String email
String password
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired
static belongsTo = [area:Areas]
.......
}
Area Class:
class Areas {
String name
static hasMany = [users:User]
}
As you can see from the classes one user can be linked to one area but an area might have many users. This all works fine and when I bootstrap the application all data gets added correctly. however I get the following error when i try to use a form to create a new user:
object references an unsaved transient instance - save the transient instance before flushing: com.website.Area
below is the controller code I am using to save the information:
def save = {
def user = lookupUserClass().newInstance(params)
if (params.password) {
String salt = saltSource instanceof NullSaltSource ? null : params.username
user.password = springSecurityUiService.encodePassword(params.password, salt)
}
if (!user.save(flush: true)) {
render view: 'create', model: [user: user, authorityList: sortedRoles()]
return
}
addRoles(user)
flash.message = "${message(code: 'default.created.message', args: [message(code: 'user.label', default: 'User'), user.id])}"
redirect action: edit, id: user.id
}
and here is a sample of what the GSP looks like:
<table>
<tbody>
<s2ui:textFieldRow name='username' labelCode='user.username.label' bean="${user}"
labelCodeDefault='Username' value="${user?.username}"/>
<s2ui:passwordFieldRow name='password' labelCode='user.password.label' bean="${user}"
labelCodeDefault='Password' value="${user?.password}"/>
<s2ui:textFieldRow name='email' labelCode='user.email.label' bean="${user}"
labelCodeDefault='E-Mail' value="${user?.email}"/>
<s2ui:textFieldRow readonly='yes' name='area.name' labelCode='user.area.label' bean="${user}"
labelCodeDefault='Department' value="${area.name}"/>
<s2ui:checkboxRow name='enabled' labelCode='user.enabled.label' bean="${user}"
labelCodeDefault='Enabled' value="${user?.enabled}"/>
<s2ui:checkboxRow name='accountExpired' labelCode='user.accountExpired.label' bean="${user}"
labelCodeDefault='Account Expired' value="${user?.accountExpired}"/>
<s2ui:checkboxRow name='accountLocked' labelCode='user.accountLocked.label' bean="${user}"
labelCodeDefault='Account Locked' value="${user?.accountLocked}"/>
<s2ui:checkboxRow name='passwordExpired' labelCode='user.passwordExpired.label' bean="${user}"
labelCodeDefault='Password Expired' value="${user?.passwordExpired}"/>
</tbody>
</table>
I have looked into this and I believe it is the way I am trying to save a GORM Object and I should maybe save the parent(Area) before trying to save the user. However the Area will always exist before the user can be created so I don’t need the area to be created again, how do I handle this? I tried the "withTransaction" function with little success as well
I would really appreciate some help on this if possible.
Thanks
EDIT ......
I have tracked the issue to this line in the Controller:
RegistrationCode registrationCode = springSecurityUiService.register(user, command.password, salt)
This say to me that this function is calling a save function for the user object, however with the relationships in place it needs to save the Area first. Does anyone know SpringSecurityUi in order to help me on this?
Thanks
The error message you see
object references an unsaved transient instance - save the transient instance before flushing: com.website.Area
... happens when you're trying to save a non-existent parent (i.e Area) entity from a child (i.e User) entity. It's possible that error is happening in the RegistrationController.register method as you pointed out, if that's where you're saving a new User.
RegistrationCode registrationCode = springSecurityUiService.register(user, command.password, salt)
You just need to update the RegistrationCode.register method with logic to assign the Area to a User (assuming the Area already exists) before the register call - below is a quick example.
class RegistrationController ..
..
def area = Area.findByName(command.areaName)
def user = lookupUserClass().newInstance(email: command.email, username: command.username,
accountLocked: true, enabled: true, area: area)
RegistrationCode registrationCode = springSecurityUiService.register(user, command.password, salt)
A couple of notes:
Anyways, hope that helps.