Grails - save the transient instance before flushing grails Error?
Asked Answered
K

3

6

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

Kanara answered 22/10, 2012 at 13:14 Comment(10)
Put the springsecurityservice in the controller, not the domain-class.Wadley
Can you provide me with an example please? as the springsecurityservice has always been thereKanara
Would the error not be related to the "com.website.Area" class like the error says? ThanksKanara
Sorry, I didn't read the details...of course it's gonna be in the User domain-class. You might have imported the wrong Area, check your imports and package-names.Wadley
The area's get populated into a dropdown list which does have the right content in. The issue occurs when I try and save the data, I believe it has something to do with the way GORM Works. Any ideas on this please? I could really use the help :-)Kanara
I have looked at that however I don't really see how that applies to my issue? Is there a special way to save Relational objects within grails that I'm missing?Kanara
can anyone please help me with this???Kanara
I tried looking through the code again, try saving the Area before you save the user. It doesn't automatically create the Area for you I think. Anyway, this post might help you debug - #537101Wadley
I have updated my question with more information, thanks :-)Kanara
What I would try is making it all transactional, do user.addToAreas(area) and save the user.Wadley
P
8

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.

Platinocyanide answered 23/10, 2012 at 9:34 Comment(1)
Life saver :-) I had already fixed the problem in one part of the solution however this really helped me solve another :-DKanara
F
0

Try to turn bi-directional dependency to the other side

class Areas {

    String name
    static belongsTo = [User] 
    static hasMany = [users:User]
}

Don't forget to remove belongsTo from User

Felten answered 23/10, 2012 at 9:9 Comment(0)
H
0
class Areas {
    String name
    static belongsTo = [User] 
    static hasMany = [users:User]
}
Hill answered 8/1, 2014 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.