6 min read

(For more resources on Groovy DSL, see here.)

Storing and retrieving simple objects is all very well, but the real power of GORM is that it allows us to model the relationships between objects, as we will now see. The main types of relationships that we want to model are associations, where one object has an associated relationship with another, for example, Customer and Account, composition relationships, where we want to build an object from sub components, and inheritance, where we want to model similar objects by describing their common properties in a base class.

Associations

Every business system involves some sort of association between the main business objects. Relationships between objects can be one-to-one, one-to-many, or many-to-many. Relationships may also imply ownership, where one object only has relevance in relation to another parent object.

If we model our domain directly in the database, we need to build and manage tables, and make associations between the tables by using foreign keys. For complex relationships, including many-to-many relationships, we may need to build special tables whose sole function is to contain the foreign keys needed to track the relationships between objects. Using GORM, we can model all of the various associations that we need to establish between objects directly within the GORM class definitions. GORM takes care of all of the complex mappings to tables and foreign keys through a Hibernate persistence layer.

One-to-one

The simplest association that we need to model in GORM is a one-to-one association. Suppose our customer can have a single address; we would create a new Address domain class using the grails create-domain-class command, as before.

class Address {
String street
String city

static constraints = {
}
}

To create the simplest one-to-one relationship with Customer, we just add an Address field to the Customer class.

class Customer {
String firstName
String lastName
Address address

static constraints = {
}
}

When we rerun the Grails application, GORM will recreate a new address table. It will also recognize the address field of Customer as an association with the Address class, and create a foreign key relationship between the customer and address tables accordingly.

This is a one-directional relationship. We are saying that a Customer “has an” Address but an Address does not necessarily “have a” Customer.

We can model bi-directional associations by simply adding a Customer field to the Address. This will then be reflected in the relational model by GORM adding a customer_id field to the address table.

class Address {
String street
String city
Customer customer

static constraints = {
}
}

mysql> describe address;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | | |
| city | varchar(255) | NO | | | |
| customer_id | bigint(20) | YES | MUL | NULL | |
| street | varchar(255) | NO | | | |
+-------------+--------------+------+-----+---------+----------------+
5 rows in set (0.01 sec)


mysql>

These basic one-to-one associations can be inferred by GORM just by interrogating the fields in each domain class via reflection and the Groovy metaclasses. To denote ownership in a relationship, GORM uses an optional static field applied to a domain class, called belongsTo. Suppose we add an Identity class to retain the login identity of a customer in the application. We would then use

class Customer {
String firstName
String lastName
Identity ident
}

class Address {
String street
String city
}

class Identity {
String email
String password

static belongsTo = Customer
}

Classes are first-class citizens in the Groovy language. When we declare static belongsTo = Customer, what we are actually doing is storing a static instance of a java.lang.Class object for the Customer class in the belongsTo field. Grails can interrogate this static field at load time to infer the ownership relation between Identity and Customer.

Here we have three classes: Customer, Address, and Identity. Customer has a one-to-one association with both Address and Identity through the address and ident fields. However, the ident field is “owned” by Customer as indicated in the belongsTo setting. What this means is that saves, updates, and deletes will be cascaded to identity but not to address, as we can see below. The addr object needs to be saved and deleted independently of Customer but id is automatically saved and deleted in sync with Customer.

def addr = new Address(street:"1 Rock Road", city:"Bedrock")
def id = new Identity(email:"email", password:"password")
def fred = new Customer(firstName:"Fred",
lastName:"Flintstone",
address:addr,ident:id)

addr.save(flush:true)

assert Customer.list().size == 0
assert Address.list().size == 1
assert Identity.list().size == 0

fred.save(flush:true)

assert Customer.list().size == 1
assert Address.list().size == 1
assert Identity.list().size == 1

fred.delete(flush:true)

assert Customer.list().size == 0
assert Address.list().size == 1
assert Identity.list().size == 0

addr.delete(flush:true)

assert Customer.list().size == 0
assert Address.list().size == 0
assert Identity.list().size == 0

Constraints

You will have noticed that every domain class produced by the grails create-domain- class command contains an empty static closure, constraints. We can use this closure to set the constraints on any field in our model. Here we apply constraints to the e-mail and password fields of Identity. We want an e-mail field to be unique, not blank, and not nullable. The password field should be 6 to 200 characters long, not blank, and not nullable.

class Identity {
String email
String password

static constraints = {
email(unique: true, blank: false, nullable: false)
password(blank: false, nullable:false, size:6..200)
}
}

From our knowledge of builders and the markup pattern, we can see that GORM could be using a similar strategy here to apply constraints to the domain class. It looks like a pretended method is provided for each field in the class that accepts a map as an argument. The map entries are interpreted as constraints to apply to the model field.

The Builder pattern turns out to be a good guess as to how GORM is implementing this. GORM actually implements constraints through a builder class called ConstrainedPropertyBuilder. The closure that gets assigned to constraints is in fact some markup style closure code for this builder. Before executing the constraints closure, GORM sets an instance of ConstrainedPropertyBuilder to be the delegate for the closure. We are more accustomed to seeing builder code where the Builder instance is visible.

def builder = new ConstrainedPropertyBuilder()
builder.constraints {
}

Setting the builder as a delegate of any closure allows us to execute the closure as if it was coded in the above style. The constraints closure can be run at any time by Grails, and as it executes the ConstrainedPropertyBuilder, it will build a HashMap of the constraints it encounters for each field.

We can illustrate the same technique by using MarkupBuilder and NodeBuilder. The Markup class in the following code snippet just declares a static closure named markup. Later on we can use this closure with whatever builder we want, by setting the delegate of the markup to the builder that we would like to use.

class Markup {
static markup = {
customers {
customer(id:1001) {
name(firstName:"Fred",
surname:"Flintstone")
address(street:"1 Rock Road",
city:"Bedrock")
}
customer(id:1002) {
name(firstName:"Barney",
surname:"Rubble")
address(street:"2 Rock Road",
city:"Bedrock")
}
}
}
}
Markup.markup.setDelegate(new groovy.xml.
MarkupBuilder())
Markup.markup() // Outputs xml
Markup.markup.setDelegate(new groovy.util.
NodeBuilder())
def nodes = Markup.markup() // builds a node tree

LEAVE A REPLY

Please enter your comment!
Please enter your name here