Monday, February 23, 2009

grails property conundrums

Been writing an application using grails. Very nice framework, but as always there's the usual slew of learning curve conundrums. One in particular had me perplexed (and obsessed) for days.

I needed a class to hold plans; here it is:

------------Plan.groovy---------------------
class Plan {

  String name
  Date startDate
  Date endDate
  static hasMany = [tasks : Task]

  Integer numDays
  public void setNumDays(Integer numDays) {
    this.numDays=numDays
  this.endDate = this.startDate.plus(this.numDays)
  }
}

Simple really; only thing remotely out of the ordinary is the setNumDays() method which automatically calculates the end date from the start date and duration. And here's the corresponding test class:

import java.text.SimpleDateFormat
class PlanTests extends GroovyTestCase {

  void testDates() {
    def sdf = new SimpleDateFormat("yyyy-MM-dd")
    def plan = new Plan(name:"test1",
        startDate: sdf.parse("2009-02-22"),
        numDays: 7 )

    //this shouldn't be necessary!?!
    plan.setNumDays(7)

    plan.save()
    assert plan.name == "test1"
    assert plan.startDate == sdf.parse("2009-02-22")
    assert plan.numDays == 7

    //Fails without call to setNumDays() above
    assert plan.endDate == sdf.parse("2009-03-01")

  }
}

The strange thing is that without the explicit call to plan.setNumDays() the final assertion fails. This was perplexing. I tried testing as a native groovy class with accompanying test script; contents exactly as above (minus the hasMany in the class and call to plan.save() in the test script). This worked without the explicit setNumDays() call.

I tried various things but couldn't get to the bottom of it - until finally I initialised the startDate field:

------------Plan.groovy---------------------
class Plan {
//...
Date startDate = new Date()
//...
}

With that in place the test works without the explicit setNumDays() call. Initial theory was that numDays was being set before startDate (I had assumed groovy would initialse in the order defined in the new() method call - but maybe not).

However, that doesn't seem to be the case. It doesn't matter which of the date fields is initialised, as long as at least one of them is. But it fails if neither is. Explanation? I'm really not sure...