Writing a custom grails unique constraint that must work on command objects and domain objects

StackOverflow https://stackoverflow.com/questions/22159728

  •  19-10-2022
  •  | 
  •  

Question

The task is to support usernames being stored in the database as they are originally typed upon creation, and to check for uniqueness using the 'ilike' criteria in place of 'eq'.

In digging around, I saw 2 implementations of UniqueConstraint, one provided by grails-datastore-gorm and one provided by grails-hibernate. I understand that I need to perform this lookup in a block where I reset the FlushMode on the session object, so hibernate does not persist the change to the db before the data is validated. Here is the custom validator I wrote:

    @Override
protected void processValidate(final Object target, final Object propertyValue, Errors errors) {
    def reject = false
    doWithManualSessionIfAppropriate {
        def targetId = null
        try {
            targetId = InvokerHelper.invokeMethod(target, 'ident', null)
        } catch (Exception e) {
            throw new GrailsRuntimeException('Could not determine id of target')
        }

        def results = []

        results += constraintOwningClass."findAllBy${GrailsNameUtils.getClassName(constraintPropertyName, '')}Ilike"(propertyValue)

        reject = results.any {
            try {
                def existingId = InvokerHelper.invokeMethod(it, 'ident', null)
                targetId != existingId
            } catch (Exception e) {
                // the existing field is not in the db
                false
            }
        }
    }

    if (reject) {
        errors.rejectValue(constraintPropertyName, 'unique')
    }
}

This made our integration tests pass, but when I used it in a controller that calls importFrom to validate the username, invokeMethod fails to find an id on the command object. For example:

RealUser.groovy:

class RealUser {
    String username, passwordHash
    static constraints = { 
        username iunique: true // where iunique is the name of my registered constraint
    }
}

UserController.groovy:

@Validateable
class UserCommandObject {
    String username, password, passwordConfirmation
    static constraints = {
        importFrom RealUser
    }
}

When I had unique: true on RealUser, the UserCommandObject validated just fine. When I changed it to my custom validator iunique: true, UserCommandObject complains that it's not a domain class.

I can't possibly see how unique: true works for command objects, since both implementations I see only work on domain objects (and throw a GrailsRuntimeException when called with no domain object).

Any ideas? Am I thinking too much into this?

Approaching from the other point of view, is there any compelling reason to not support storing usernames as-entered, and just calling lower() on the input before validating?

No correct solution

OTHER TIPS

Would findByPropertyNameIlike(val) within a custom validator do the trick?

static constraints = {
    propertyName blank: false, validator: { val, obj, errors ->

        def results = this.findByPropertyNameIlike(val)

        if(results) {
            errors.rejectValue('propertyName', 'unique.propertyName')
            return false
        }

        return true
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top