Tomasz Kalkosiński
Tomasz Kalkosiński

Reputation: 3723

beforeUpdate not called when only transients has changed

I use Grails 2.0.0.RC2 and I have an User class like this:

class User {
    String username
    String password

    // Idea from http://grailsrecipes.wordpress.com/2009/04/19/grails-user-registration-and-login/ .
    String formPassword
    String formPasswordConfirm

    // Constraints and validation ommited

    static transients = ['formPassword', 'formPasswordConfirm']

    def beforeUpdate() {
        println("Inside beforeUpdate")
        if (formPassword != null)
            encodePassword()
    }

    protected void encodePassword() {
        password = formPassword // Just for this case
    }
}

When user asks for his password reset, I send him email with a link to reset password page. Reset password form is simple - it contains only two fields: formPassword and formPasswordConfirm. I do a simple action in controller: user.formPassword = params["formPassword"] and user.formPasswordConfirm = params["formPasswordConfirm"]. Then I do (user.save()) - and problem begins.

My problem is that beforeUpdate() is not called. I thought it was a validation problem (it's omiited here), but it's not. As it turns out user.save() didn't persist user to database! Why? I wanted it to be persisted, password should be changed. But user.isDirty() is false just before user.save(). That's because none of persistent properties were changed. That's true. Since user.save() wasn't called - beforeUpdate() wasn't called either.

Is this desired behaviour? Maybe it's a Grails bug and beforeUpdate() should be always called before update and then isDirty() should be checked? What do you think?

Upvotes: 2

Views: 1671

Answers (2)

Dónal
Dónal

Reputation: 187529

I don't know if beforeUpdate should be called when transient fields are updated, but given the choice between

  1. it's a Grails/Hibernate bug
  2. this is the expected behaviour because no DB updates would happen when transient fields are modified

I would bet a lot of money on 2, and very little on 1.

So leaving that aside, I think you've made your domain class a bit more complicated that it needs to be. Specifically, you should be able to achieve your goal with 2 password fields instead of 3.

class User {

    static transients = ['passwordConfirm']
    def springSecurityService

    String password
    String passwordConfirm

    static constraints = {
        password blank: false, validator: {password, self ->

            // We only need to check the password confirmation when it is not empty, i.e.
            // when a user registers or resets their password
            if (self.passwordConfirm) {
                password == self.passwordConfirm
            }
        }
    }

    def beforeInsert() {
        encodePassword()
    }

    def beforeUpdate() {
        if (isDirty('password')) {
            encodePassword()
        }
    }

    private void encodePassword() {
        password = springSecurityService.encodePassword(password)
        passwordConfirm = springSecurityService.encodePassword(passwordConfirm)
    }

}

Your reset password controller action should look like this:

def resetPassword = {

    User user = User.findByUsername(params.username)
    user.password = params.formPassword
    user.passwordConfirm = params.formPasswordConfirm

    if (user.save()) {
        // It worked, send them to the login page or whatever...
    } else {
        // Validation failed, send them back to the reset password page    
    }
}

Upvotes: 3

Oliver Tynes
Oliver Tynes

Reputation: 964

This is desired behavior.

The semantics are pretty crystal clear in my opinion, you have not changed any persistent fields, ergo there is nothing to save. The .save() has no way of guessing that in this case some transient property should make you run through the save() (and then beforeUpdate()).

Just add a method that updates the password and you're good to go.

Upvotes: 2

Related Questions