tags:

views:

204

answers:

2

How can I basically carry out a unique constraint on a string data-type field.

class User{
  String username
  String Email

  static hasMany = [roles:Roles]

     static constraints = {
     Email(email:true)
     username(unique:true)

    }
}

Is there any simple way to implement username(unique:true)

or I will have to manually check the database by the in-built methods like .findByNameLike.

My need is that username should be unique and should be case insensitive

+2  A: 

So, if you want to have unique and case insensitive usernames, there are two possible approaches.

The simple one:

  • Store them upper or lower case and use the unique constraint.

or, regarding performance, more expensive:

  • Store them in mixed case and use a custom validator, which checks the database by comparing given and existing usernames case insensitive.

Now it depends, if you just want to give the user the freedom to enter his username in the case he wants (first possibility) or you want to keep the usernames case for displaying reasons (second possibility).

Your question sounds like the second one, so a custom validator would look like this:

class User { 
  String username 
  String email

  static hasMany = [roles:Roles]
  static constraints = {
    email(email:true)
    username(validator: {
              return !User.findByUsernameILike(it)
            })
  }
}

Hope that helps.

[Edit]

As Heinrich states in his comment, the validator above will cause problems when users are able to change their username.

Quick & dirty, but I think this solves the issue:

username(validator: { val, obj ->
                      def similarUser = User.findByUsernameILike(val) 
                      return !similarUser || obj.id == similarUser.id
                    })

Beware, it's not tested and I'm not sure if you're able to define variables in validators.

Meta: I would never let the users change their username ;)

air_blob
Good answer! I think you custom validator might have problems when "username" is updated. See http://n4.nabble.com/Case-Insensitive-Constraint-td1359204.html. Not sure though...
Heinrich Filter
Omg!yes @Heinrich you are right it does fail when I try to update the same field.
WaZ
WaZ
That only checks if the instance has an id ... but you're right, obj.id == similarUser.id is enough. Edited, I'm not that good in writing rock-solid code from scratch without testing :)
air_blob
Btw. consider writing unit tests, especially for validators like this, Grails provides awsome possibilities for TDD.
air_blob
Hi Blob,I know I should be writing Unit tests, but I am a newbie in the grails world. I am just confused, how can I mock the above snippet for testing. As when I unit test the User domain I get the following error.User.findByUserNameIlike() is applicable for argument types: (java.lang.String).Does `mockForConstraintsTests` automatically generate the code to run something like `findByUsernameILike`Much appreciatedWB
WaZ
@ WaZ: Can you describe yout test setup a lottle bit mor precise?
air_blob
I tried the following test:void testSomething() {def _user= new User(username :"abc",Email:"[email protected]") mockForConstraintsTests(User,[_user]) assertTrue _user.validate()}
WaZ
Hm, not sure if you can mock a database for the validation, but anyway, you can bootstrap your test in the setUp() method or, if the project gets bigger in a dedicated script. Means, in setUp(), just create a User instance and save it, then proceed with test methods i.e. testUsernameChange(), testSameNameCreation() etc. There you fetch the previously created object and mess around with it.
air_blob
+1  A: 

username(unique:true) is a valid constraint.

To make the constraint case insensitive you would need to write a custom validator. See this discussion thread for more information.

Heinrich Filter
Mate, when I implement it I get the following error:No such property: id for class: com.myCompany.User
WaZ