Grails - save the transient instance before flushing grails Error?

user723858 picture user723858 · Oct 22, 2012 · Viewed 7.7k times · Source

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

Answer

user553180 picture user553180 · Oct 23, 2012

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:

  • you are passing back "area.name" from your gsp view, so you'll need to update/override the RegisterCommand to include the Area name
  • is your use of Area "name" as an identifier safe? In your class definition you don't have constraints to indicate that "name" will be unique. Maybe passing back an Area id from your view is safer
  • ideally you should handle the Area lookup with a custom property editor - here is an example: Grails command object data binding

Anyways, hope that helps.