Jun 26 2008

Hiberailooving, Part 3: Adding (and Testing!) Relationships and Rules

Posted by Joe Rinehart at 12:30 PM
3 comments
- Categories: Hiberailooving

The Hiberailooving series chronicles the construction of a development environment and server configuration for creating redistributable JEE+CFML applications using Groovy, Spring, Hibernate, and Railo.

Last time, we set up a simple Groovy-based model of a Bike and a TestNG test case for verifying its behavior and state. This time, we're not going to break much new ground on the framework front. We'll still only be using Groovy and TestNG, but we're going to add a bit of complexity to our Model in order to learn a touch more about Groovy and to put Hibernate to a bit more of a test when we're ready to persist.

Bikes have Wheels

To write my bike shop application, I need to keep track of what Wheels are on what Bikes. I've got a simple business rule to follow as well: a Bike can't have more than two Wheels.

I guess that means I need to model a Wheel. I'll give it an id, name, and bike property, letting us know to which Bike the Wheel belongs. While I'm add it, I've added "Name" to Bike as well.

Wheel.groovy

package com.firemoss.bikeshop.model

class Wheel {

int id
private Bike bike
String name
}

Collecting Wheels

A Bike has a Collection of Wheels. (Ok, so I could and probably should create props of frontWheel and backWheel, but it's the one-to-many I'm really after here). In the Java world, and according to Java Persistence With Hibernate, I now need to know and choose from the Java collections API an appropriate collection for this relationship.

Sure, that's all available in Groovy, but I'd rather just use its Maps and Lists semantics.

In Groovy, much like CFML, you've basically got a choice between an Array (a "List") or a Struct (a "Map"). We've all done this before: a List is what we're after here.

Creating a List in Groovy is pretty simple:

def list = []

Yep, it's a lot like those ColdFusion 8 operators. Except this time they work (nested, for function calls, etc.). Also, note that a List is zero-indexed. It's really a java.util.List implementation.

For laughs, here's a Map:

def map = [id:1, name:"John"]

Yep, they used colons. Like the rest of the known world.

For more on Groovy collections (they're very, very cool, and take the Java Collections API to a new level), check out the User Guide.

Back to our Bike

Our bike needs a collection of wheels, and we're using a List. We want to have a bidirectional relationship, so we'll add a method "addWheel" that adds to a given Bike's collection of Wheels, assigning its "bike" property. While we're add it, we'll throw an error if some tricyclist tries to build a 3-wheeled bike. I wish I could ban recumbents and 650cc triathlon setups as well, but this is a simple example.

Sound complicated? It's not. Check out how clear the Groovy code is:

Bike.groovy, managing wheel collection.

package com.firemoss.bikeshop.model

class Bike {

int id
boolean sold = false
Date dateSold;
String name
List wheels = []

void sell()
{
sold = true
dateSold = new Date()
}

Bike addWheel(wheel)
{
// business rule: 2 wheels per bike. if (wheels.size >= 2)
throw new Exception("You can only have two wheels!");

wheels.add(wheel)

// add reflexive relationship if necessary if (wheel.bike != this)
wheel.bike = this;

// allow method chaining return this;
}

Bike removeWheel(wheel)
{
if (wheels.contains(wheel))
{
wheels.remove(wheel)
wheel.bike = null
}

return this;
}
}

Notice that I set up addWheel() to return the Bike itself. No real reason....I was just playing around with things like bike.addWheel(new Wheel()).addWheel(new Wheel()) to show that it works as you'd expect.

Wheel.bike needs a setter!

We've built addWheel() to change the passed wheel's "bike" property, but now we could end up in a bad place: what if the Wheel was already assigned to another Bike? It'd be in both of the Bike's "wheels" collections, which should be an impossibility! We need to add some behavior to the assignment of a Bike to a Wheel.

By default, Groovy will create (and invoke) getters and setters for public properties (the default access type). We're free to override those. We'll just set up Wheel with a setBike() method that overrides the generated to clean up the relationships, checking to make sure we don't enter a nasty bit of recursion:

Wheel.groovy

package com.firemoss.bikeshop.model

class Wheel {

int id
String name
public Bike bike


public Bike getBike()
{
return this.bike
}

public void setBike(Bike bike)
{
// remove from parent if assigned if (this.bike && this.bike != bike && this.bike.wheels.contains(this))
this.bike.removeWheel(this)

this.bike = bike

// add to parent if not in parent and not nulling the relationship if (bike && !bike.wheels.contains(this))
bike.wheels.add(this)
}
}

Whoa, what about tests?

Ok, I promise, I wrote the tests first. Really. I just wanted to get to the meat of what we were up to. I've updated the test to account for both the "two wheels per bike" rule and to give the association methods a pretty solid workout. Here it is:

BikeTest.groovy

package com.firemoss.bikeshop.test


import com.firemoss.bikeshop.model.*;
import org.testng.annotations.*
import org.testng.TestNG
import org.testng.TestListenerAdapter
import static org.testng.AssertJUnit.*;

public class BikeTest {

public static void main(String[] args){
def testng = new TestNG()
testng.setTestClasses(BikeTest)
testng.addListener(new TestListenerAdapter())
testng.run()
}


@Test
final void testSellBike(){
def bike = new Bike();

assertFalse(bike.sold && bike.dateSold)

bike.sell();

assertTrue(bike.sold && bike.dateSold);
}

@Test
final void testTooManyWheels(){
def bike = new Bike();
def ex = false;

try
{
bike.addWheel(new Wheel()).addWheel(new Wheel());
}
catch (e)
{
ex = true;
}

assertFalse("Shouldn't error on two wheels.", ex);

bike = new Bike();

try
{
bike.addWheel(new Wheel()).addWheel(new Wheel()).addWheel(new Wheel());
}
catch (e)
{
ex = true;
}

assertTrue("This ain't a tricycle.", ex);
}

@Test
final void testBikeWheelAssociation()
{
def bike1 = new Bike();
def bike2 = new Bike();
def wheel = new Wheel();

bike1.addWheel(wheel);
assertTrue(wheel.bike == bike1);

wheel.bike = bike2;
assertTrue(bike2.wheels.contains(wheel))
assertFalse(bike1.wheels.contains(wheel))

bike1.addWheel(wheel);
assertTrue(bike1.wheels.contains(wheel))
assertFalse(bike2.wheels.contains(wheel))


}

}

Conclusion

Ok, we've got a good sized Model now. It's got a one-to-many relationship (Many-to-many is overrated. I always get a ticket that requires turning the relationship into a model.) and a business rule, and we're testing the whole shebang.

Now if we could only save it to a database...

Comments

Barney

Barney wrote on 06/26/08 3:44 PM

Your map example won't work. Groovy uses [] for list and [:] for maps, because {} is reserved for closures.
Joe Rinehart

Joe Rinehart wrote on 06/26/08 3:53 PM

Ugh...sorry about the typo. I concentrated on how it was different from CF8, and typed in exactly how CF8/AS3 start/end the expression, which is absolutely wrong in Groovy. Fixed to use the proper brackets.
Joe Rinehart

Joe Rinehart wrote on 06/26/08 3:57 PM

Also, just fixed an issues in Wheel not property handling getting its Bike set to null, and added the missing removeWheel() method to Bike.

Write your comment



(it will not be displayed)