Posts Tagged ‘cascading’

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.