views:

42

answers:

3

I have a domain object called User:

class User{
  String username;
  String firstName;
  String lastName;
  Zipcode zip;
}

I also have a Zip Code object:

class Zipcode {
  String zip;
  String city;
  String state;
  Float lat;
  Float long;
}

The zipcode table should never be modified as it contains static reference data prepopulated

A user belongs to one zipcode. The user enters the zipcode as part of the User creation.

How should I model the domain objects relationship? I would like like to make sure that GORM does not attempt to update zipcodes. I would like to make sure that the user only enters valid zipcode numbers. (Which are found in the zipcode table) How do I configure the constraints on the User object? In the controller, I do the following:

def userInstance = new User(params) // where params are form values

How do I set the proper zipcode on the object?

A: 

use Domain event callback

   transient beforeUpdate = { 
      // check to make sure that the zip code value remains the same
      // and is never changed... 
   } 
Aaron Saunders
I thought about this. The only way this would work is if I create a transient parameter called zipCodeString in the User domain object. In the beforeUpdate method, I would have to look up ZipCode and set the zipCode with result from the query. Is this what you were implying?
Tihom
@Tihom that was the approach I was suggesting
Aaron Saunders
Do you know how I would prevent GORM from updating the zipcode table?
Tihom
@Tihom never tried it but you could do the same trick on beforeUpdate event and just throw an exception
Aaron Saunders
+1  A: 

This sounds like more of an UI issue. Do a Zipcode object lookup in the controller and set the the object located on the user. Otherwise, I can't see how a Zipcode could have been altered upon creation of a user.

save = {
  params.zip.id = Zipcode.findByZip(params.zip)
  def userInstance = new User(params)
}

or

save = {
  def userInstance = new User(params)
  userInstance.zip = Zipcode.findByZip(params.zip) 
}

You should include some validation logic (if the zip is incorrect) and also consider renaming params.zip to params.userProvidedZip or something like that.

mfloryan
Will GORM attempt to update the zipcode table?
Tihom
@mfloryan this wont prevent the zipcode object from being updated?
Aaron Saunders
+1  A: 

You would not let GORM manage the zip property (and restrict GORM from doing so at a second stage), at all.

That's what mfloryan's approach tells, too; however, his approach doesn't separate concerns, properly (separation of concerns paradigm): In the MVC (Model-View-Controller) pattern, it's not the controllers' task to "model" the data model, but it's the task of the data access layer (which is - in case of GORM - the domain classes theirselves).

Thus, the User class would be implemented like that:

class User {
    String userName
    String firstName
    String lastName
    String zip

    ZipCode retrieveZipCode() {
        ZipCode.findByZip(zip)
    }

static constraints = {
    zip nullable: false, blank: false, matches: /^\d{5}/,
    /* not tested at my machine: */
    validator: {
        if(!retrieveZipCode(it)) {
            return false
        }
    }
}
}

Note the retrieveZipCode() method. It's not called getZipCode() as, otherwise, Hibernate would throw an exception about a "missing setter method". You can also experiment with adding a zipCode property, a getZipCode() method (that does nothing or, alternatively, throws an exception), and adding the zipCode property to the transinients definition. - Everything of this (in any combination) will not work.

Also note the constraints definition: It matches when the zip consists of exactly five digits. (I believe that's the format of ZIP codes there in the USA.) It should also make sure that the database contains an entry for the user's ZIP code (syntax not tested).

I've changed the ZipCode class slightly (partly, to avoid a compilation error):

class ZipCode {
    String zip;
    String city;
    String state;
    Float latitude;
    Float longitude;
}

And finally, there's an integration test:

class UserTests extends GroovyTestCase {
    def testUserCreation() {
        User user = new User(
            userName: "foo", firstName: "bar", 
            lastName: "baz", zip: "12345")
        assert user.validate()
        assert user.retrieveZipCode()
        user.save()
    }
}

Thanks

robbbert