Posts Tagged ‘validation’

Why belongsTo is important for validation

October 15, 2009

Grails domain classes often work together. Various one-to-one relations and one-to-many-relations may exist. On validate and save, you want to cascade as much as possible. In this article, we’ll show you how you can do that. As you’ll see, the most important thing is that you always add a belongsTo statement in the referenced class.

Validating one-to-many

Suppose you have a person, and a person can have one or more addresses. This is implemented in the following two domain classes:

class Person {
    static hasMany = [addresses: Address]
}
class Address {
    String street
    static constraints = {
        street nullable:false
    }
}

Cascading save

Now let’s create a person and give him an address. If we save the person, we want the address to be saved too, without explicitly having to specify that. This is expressed by the following integration test:

import grails.test.*

class PersonTests extends GrailsUnitTestCase {
    void testCascadingSave() {
        def personCount = Person.count()
        def addressCount = Address.count()
        def person = new Person()
        def address = new Address(street:"Weena")
        person.addToAddresses(address)
        assert person.validate()
        assert person.save()
        assert 1 == Person.count() - personCount
        assert 1 == Address.count() - addressCount
    }
}

Cascading validation

If you run the test, you’ll see that one-to-many relations are automatically cascaded on save. But what happens with validation? The street property of Address is not nullable. If I validate a person, does grails also automatically validate his or her addresses? Let’s test.

    void testCascadingValidate() {
        def personCount = Person.count()
        def addressCount = Address.count()
        def person = new Person()
        def address = new Address() // WRONG, empty street!
        person.addToAddresses(address)
        assert !person.validate()
    }

The test fails. Grails does not check if the address is valid. There is one important rule we have to remember:

The referenced class must have a belongsTo statement.

Add the following to the address class, and it works:

    static belongsTo = [person: Person]

This extra line adds a property person to the Address class. If you call person.addToAddresses(address), the person property of address is automatically set. However, the reverse is not true. If you set the person property of an address, the address is not automatically added to the person instance. Therefore, you must always use the addTo-method to add one-to-many-relations.

Validating one-to-one

Now, suppose a person can only have zero or one address. This is expressed in the domain class:

class Person {
    Address address
}

In the above tests, change

person.addToAddresses(address)

to

person.address = address

The two tests will still succeed. But, if we remove the belongsTo statement from the address class, both tests will fail. The belongsTo statement arranges two things:

  1. The address instance is saved automatically if the person object it
    belongs to is saved.
  2. The address instance is validated automatically if the person object it
    belongs to is validated.

So, we need the belongsTo statement for both one-to-many and many-to-many relations.

There is however one difference. With one-to-many relations, the person property of the address class was automatically set when calling addToAddresses. With a one-to-one-relation, the person property is NOT automatically set. Since this is very confusing, we’ll get rid of the property. Simply change the belongsTo statement to:

    static belongsTo = Person

The address instance is still saved and validated automatically, whenever its corresponding person is saved or validated. However, the address won’t have a person property anymore. In the one-to-one case, this is a good thing, since it didn’t have the proper value anyway. With one-to-many relations, you may choose yourself which variant of the belongsTo statement you use, depending if you need the extra person property.

Note that we wrote the above tests as integration tests. If we use mockDomain to mock the Person and Address classes, and run the tests as unit tests, they will both fail. The mockDomain method is quite useless if you want to mock collaberating classes.

Advertisements

Easier i18n of Spring error messages

August 4, 2009

When validating fields, Grails provides an easy way to validate complete error messages like “The property Last Name of User cannot be blank”. However, using a default message “The property {0} of {1} cannot be blank”, and providing translations for {0} = Last Name and {1} = User only, is not possible. In this post, we will show you how you can tweak Grails to give you this feature.

Grails i18n introduction

  • With Grails, you can easily put localisable messages in your view. Within a GSP, simply use the <g:message> tag:
    <g:message code="user.create" default="Create User" />

    When Grails encounters this tag, it looks for a code “user.create” in your grails-app/i18n/messages.properties. If Grails cannot find the code, it uses the value of the default argument.

  • By default, there are message.properties files for many languages. If you want to see a page in your application in another language, simply add “?lang=es” to the url. Replace “es” by any ISO language code for which you have provided a translation.
  • The standard scaffolded (and generated) templates of your application are not internationalised. You can replace the standard scaffolding (and generating) templates by your own versions, by simply putting them into src/templates/scaffolding. Writing templates is almost as much fun as writing Lisp macros! But wait, others have already done the work. Simply use the I18n Templates Plugin and you’re done.

Validation messages

  • Grails uses Spring validation for rendering error messages. The messages are looked up in the messages.properties file. There are default messages for all kinds of validation errors. For example:
    default.blank.message=Property [{0}] of class [{1}] cannot be blank
  • Spring automatically substitutes {0} by the property name and {1} by the class name. For example, the user would see the friendly message:
    Property [lastName] of class [com.acme.myfirstgrailsapp.security.User] cannot be blank.
  • It becomes even more user friendly when you translate it. Let’s say in Dutch:
    Attribuut [lastName] van entiteit [com.acme.myfirstgrailsapp.security.User] mag niet leeg zijn.

Translating lastName and com.acme.myfirstgrailsapp.security.User

  • Of course we do not want the user to see texts like “lastName” and “com.acme.myfirstgrailsapp.security.User”. “Last name” and “User” would be better options. Or in Dutch: “Achternaam” and “Gebruiker”.
  • Spring has a mechanism to overwrite the default message for specific classes and properties. If we add
    user.lastName.blank.error=Property last name of user cannot be blank

    Spring will render the above message if the last name of a user is blank. For all other properties that are left blank, Spring will fall back to the default message above.

  • If our application has 20 domain classes with 20 properties, we only have to supply 400 variants of the default message “Property [{0}] of class [{1}] cannot be blank”.
  • But wait, Spring does not only check for blank fields, but also for null fields, minimum values, maximum values, minimum size, maximum size, regex patterns… Each check has its own message. So, we do not only get 400 variants of the “blank” message, but also 400 variants of a null message, 400 variants of a minimum value message, 400 variants of a maximum value message, …

Making it easier

  • Spring enables us to modify and translate the message “Property [{0}] of class [{1} cannot be blank”, but it does not enable us to modify and translate the filled in values for {0} and {1}. Let’s tell Grails to do so.
  • Grails uses the <g:renderErrors> tag to render the above messages. This tag is implemented in the Grails class ValidationTagLib.
  • Filling in the values for {0} and {1} is done in the “message” closure in the ValidationTagLib class. We will provide an alternative implementation of this closure, in our own TagLib. First, generate the tag lib:
    grails create-tag-lib i18n-errors
  • We start by inheriting all default functionality from the ValidationTagLib class, by simply extending it. Also, we will provide a namespace i18m (short for I18n Messages) for the tag lib, so we can type <i18m:renderErrors>. The code of our newly created tag lib is now:
    import org.springframework.web.servlet.support.RequestContextUtils as RCU
    import org.codehaus.groovy.grails.plugins.web.taglib.ValidationTagLib
    import org.springframework.context.NoSuchMessageException
    
    class I18nErrorsTagLib extends ValidationTagLib {
    
      static namespace = "i18m"
      
    }
  • Now insert an adjusted version of the “message” closure. The closure is a copy from the original ValidationTagLib, with a few lines added.
      private String tryResolveMessage(messageSource, String code, locale) {
        try {
            messageSource.getMessage(code, new Object[0], locale) ?: null
        }
        catch (NoSuchMessageException e) {
          null
        }
      }
    
      private String abbreviateFullClassName(String classname) {
        def index = classname.lastIndexOf('.') + 1
        classname.substring(index, index + 1).toLowerCase() + classname.substring(index + 1)
      }
    
      def message = {attrs ->
        def messageSource = grailsAttributes.getApplicationContext().getBean("messageSource")
    
        def locale = RCU.getLocale(request)
        def text
    
        if (attrs['error']) {
          def error = attrs['error']
    
          // We adjusted the original "message" closure here.
          // Instead of just passing the error arguments, we try to resolve the
          // arguments in message.properties.
          def propname = error.arguments[0]
          def classname = abbreviateFullClassName(error.arguments[1].toString())
          error.arguments[0] = tryResolveMessage(messageSource, "${classname}.${propname}", locale) ?: error.arguments[0]
          error.arguments[1] = tryResolveMessage(messageSource, "${classname}", locale) ?: error.arguments[1]
    
          def message = messageSource.getMessage(error, locale)
          if (message) {
            text = message
          }
          else {
            text = error.code
          }
        }
        if (attrs['code']) {
          def code = attrs['code']
          def args = attrs['args']
          def defaultMessage = (attrs['default'] != null ? attrs['default'] : code)
    
          def message = messageSource.getMessage(code,
                  args == null ? null : args.toArray(),
                  defaultMessage,
                  locale)
          if (message != null) {
            text = message
          }
          else {
            text = defaultMessage
          }
        }
        if (text) {
          out << (attrs.encodeAs ? text."encodeAs${attrs.encodeAs}"() : text)
        }
      }

Making it work

  • Replace <g:renderErrors> by <i18m:renderErrors> wherever you wish to use the alternative error rendering.
  • If you placed your own scaffolding templates in src/templates/scaffolding (for example because you’re using the I18n Templates Plugin), you can do the replacement here.
  • Put names for the com.acme.myfirstgrailsapp.security.User class and its lastName property, in messages.properties:
    user=User
    user.lastName=Last Name
  • If you do not put a user.lastName.blank.error in your messages.properties, Grails will use the default message “Property [{0}] of class [{1}] cannot be blank”, with {0} = User and {1} = Last Name.
  • If you do not specify user and user.lastName in messages.properties, Grails will fall back to default behaviour (fully qualified class name and exact property name).
  • Optionally, you can still override specific messages for specific properties of specific classes. But you don’t have to.
    user.lastName.blank.error = Property Last Name of class User cannot be blank,
    unlike the First Name which you can leave out