Chapter 3. Modeling the domain


This chapter covers

  • What GORM is and how it works
  • Defining domain model classes
  • How domain classes are saved and updated
  • Techniques for validating and constraining fields
  • Domain class relationships (1:1, 1:m, m:n)

In this chapter, we’ll explore Grails’ support for the data model portion of your applications, and if you’re worried we’ll be digging deep into outer joins, you’ll be pleasantly surprised. We won’t be writing a line of SQL, and you won’t find any Hibernate XML mappings here either. We’ll be taking full advantage of the Convention over Configuration paradigm, which means less time configuring and more time getting work done.

We’ll be spending most of our time exploring the basics of how Grails persists domain model classes using GORM—the Grails object relational mapping implementation. You’ll also learn how GORM models various relationships (one to many, many to many, and so on).

But we’re practitioners, not theorists, so we’ll discuss these topics while building the heart of the sample application we’ll use throughout this book: Hubbub. We won’t be spending much time on the user interface (UI) in this chapter, but the concepts we’ll cover are fundamental for building the rock-solid data models that back our applications.

Without further ado, let’s look at our sample application.

3.1. Hubbub: starting our example application

Our goal in this book is to take you to the stage where you could work as a productive Grails developer. We’ll give you a thorough mentoring in all the skills you need to produce world-class applications in record time. And we’ll do it by getting you involved in developing a real application.

The example we’ll be using for the rest of the book is Hubbub, a simple micro-blogging application in the style of Twitter. You might think of it as a system that lets you write one-line blog posts about what you’re hacking on right now. Friends can follow your posts to see what you’re geeking out on and get motivated to check out some things for themselves. You can follow your friends’ posts, too. Figure 3.1 shows a complete version of Hubbub in action.

Figure 3.1. The Hubbub we’re heading towards


Tip

If you haven’t used Twitter yet, it’s time to head on over to twitter.com and see what all the fuss is about. If you need some friends to follow, check out @glen_a_smith and @pledbrook. We have pretty geeky tweets, but we’re mostly harmless.


The basic domain model for Hubbub is simple. Figure 3.2 shows the Hubbub Entity Relationship (ER) model in all its glory.

Figure 3.2. The basic Hubbub data model demonstrates all the basic relationship types.

The User class holds the user’s authentication details (user ID and password). But all good social networking applications let you associate lots of info with each user (email, blog, time zone, and favorite rock star, for example), so we’ll model that in a Profile class (which is a 1:1 relationship—each User has one Profile, and each Profile relates to one, and only one, User).

The point of Hubbub is to let users create posts—one-line blog entries that describe what they’re hacking on right now. A user will write many posts, and each Post has a single author, so that is a classic 1:m relationship.

But what’s a Web 2.0 application without tags? Every time a user creates a Post, they can apply various tags to it—a given Post can have many tags. But that means the reverse is also true. Each Tag can also relate to many Posts. We have an m:n (many-to-many) relationship. We’re also going to link the Tag back to the User object (because it would be handy to see all the tags that a user has available without searching all their posts.

We’ve saved the trickiest part until last: the User object has some self-references. A User can follow many other Users (which we’re calling a “following” relationship). That sounds like it would be exhausting to implement, but it turns out to be straightforward.

Don’t worry about getting it all perfectly straight in your head yet. We’ll be spending plenty of time with these classes over the next few chapters, and all will become clear. Just get a feel for the function of the main objects, and we’ll be on our way.

3.1.1. Domain-driven design

If you’re as impatient as we are, you’re probably wondering why we’re starting with all this domain model design stuff here. Why not something a little more sexy, like an autocompleting Ajax-powered search gizmo? Don’t worry, we’ll certainly get to that.

Grails is designed to be an interactive agile development framework. That means you can start anywhere you like, refactor, make changes, and still end up with a fantastic app. You can start with the UI, domain modeling, service classes, or even the test cases if you like.

But when we’re working on Grails apps, the first thing we usually do is scratch out some screen designs on graph paper, as shown in figure 3.3. This gives us a good feel for how the user will interact with the system, and some sense of how the finished product might end up looking. This gets us in the headspace of the application and gives us some ideas about the user experience.

Figure 3.3. Early screen designs for the Hubbub UI

But the UI is only part of the story. Once we have our UI sketches mocked up, we move on and define the domain model: how all of the persistent data in the system fits together. This gives us a good feel for how the core parts of the system will collaborate, and it helps us flesh out our thinking. Grails makes domain modeling so easy that we usually do this bit directly in code without any real data model on paper. That’s the reason this chapter is our first in-depth one: domain modeling is a great place to start your Grails application development journey.

After we define our data model, we use Grails to generate a quick-and-dirty scaffold UI (we introduced you to these autogenerated UI gems in chapter 1, and we’ll look more closely at them later in this chapter). With our autogenerated UI, we feel like we’ve made some progress—we have a basic app running and persisting to a database—so we’re motivated to move on to the next level of functionality in our app and start implementing our graph paper scratchings as a real UI.

You might be more disciplined and might not need the carrot of seeing things up and running, but you’re stuck with us for this chapter, so let’s get Hubbub to the point where you can see it running in a browser.

3.1.2. Hubbub kick-start: from 0 to first hit

We have the rough version of our screens on paper, and we have a “napkin-level” data model to work from, so it’s time to start generating our application. Let’s create the app:

grails create-app hubbub

We always find it’s good encouragement to do a cd hubbub followed by an initial grails run-app to start our newly created application. Point your browser at http://localhost:8080/hubbub/ to see things up and running, as shown in figure 3.4.

Figure 3.4. The newly created Hubbub application

With the shell of Hubbub created, it’s time to put some meat on the bones. Let’s generate our first domain class.

3.1.3. Introducing GORM (Grails object relational mapping)

Before we generate our first domain class, let’s take a quick look at GORM, the Grails object relational mapping implementation.

Object-relational mapping (ORM) is the process of getting objects into and out of a relational database. It means you can (mostly) be oblivious to the SQL that’s happening behind the scenes and get on with the coding. For example, if you call user.firstName = "Glen", the ORM manages the SQL UPDATE statement to ensure that the object is persisted in your database. In Java applications, that role is usually handled by an ORM like Hibernate or JPA; in Grails, it’s done by GORM, which takes full advantage of Groovy’s dynamic typing to make data access simple.

If you’ve used Hibernate, TopLink, or another Java ORM library, you’ll know that a lot of configuration is required. Often there’s a bunch of XML mapping files to write or annotations to add. GORM, like most of Grails, uses Convention over Configuration to get you up and running without a single line of XML.

Now that you know a bit about GORM, it’s time to define our first domain model object and see things in action.

3.2. Your first domain class object

We outlined the preliminary model at the beginning of this section, and we have our application shell in place, so it’s time to define our data model classes. One of the first things we’ll need to define is a User object, so that our users can sign up and start using the system.

The first step is to ask Grails to create a skeleton of our domain class for us:

grails create-domain-class com.grailsinaction.User

This creates a new class file in /grails-app/domain/com/grailsinaction/User.groovy (and a corresponding unit test in /test/unit/com/grailsinaction/UserTests.groovy). It’s good practice to store classes in packages rather than in the default scope, so we’ll keep all of our source in a package called com.grailsinaction.

Now it’s time to start thinking about some of the fields we’ll want to define for new User accounts. We don’t want the signup process to be too onerous, but we’ll need at least a few basics from our users. Listing 3.1 introduces our first basic User object.

Listing 3.1. Our first basic User object
package com.grailsinaction

class User {

String userId
String password
String homepage
Date dateCreated
}

Types that can be used in domain classes

We’ve used Strings and Dates in our User object so far, but you can use an extensive range of Java types: Integer, Long, Float, Boolean (and their corresponding primitive types), Date, Calendar, URL, and byte[] are all in the list of supported types. Grails will also make sensible choices about an appropriate database type to map what you’re storing. See the Hibernate documentation for a full set of supported types.

Grails provides special support for date fields named dateCreated and last-Updated. If you have fields with such names, Grails will automatically set the current timestamp value on first save to dateCreated or on every save to lastUpdated. We’ll take advantage of dateCreated to preserve the user’s registration time.


We can now store a user’s details. We don’t have a UI to enter anything yet, but we do have the shell of a test case, which Grails created for us. It’s supposed to make you feel guilty right from the start, so let’s ease our conscience.

3.2.1. Saving and retrieving users via tests

We first introduced you to the idea of automated testing in chapter 1, when we created tests for our QuoteService. But tests are useful across your application. So useful, in fact, that we’ll spend chapter 7 on testing strategies for all of the phases of the development lifecycle.

For now, though, tests will give us a chance to show you how GORM saves your objects to the database, and how to get them back. Tests provide a great way for you to tinker with some of these ideas right from the start. Let’s write our first integration test case.

As we discussed, Grails creates a unit test case shell in /test/unit/com/grailsinaction/UserTest.groovy. But we want an integration test, because we want to be able to run against our database. You’ll recall from chapter 1 that we create integration tests with this command:


Unit versus integration tests?

When you create any artifact from the command line, Grails automatically generates a corresponding unit test in /grails-app/test/unit/YourArtifactTest.groovy. Unit tests run in isolation and rely on mocking techniques (which you’ll learn about in chapter 7), which means its up to you to simulate the database and any other infrastructure you need.

In this chapter, we’ll be working with integration tests. For integration tests, Grails bootstraps the database and wires up all components as it would for a running application. That means we can simulate what happens when we create, save, and delete domain objects, and we don’t have to mess with any tricky mocking features yet. Integration tests are a little slower to run, but they’re fantastic for the learning and experimenting we’ll be doing in this chapter.


grails create-integration-test com.grailsinaction.UserIntegration

This generates /test/integration/com/grailsinaction/UserIntegrationTests.groovy.

We’ll first create and save a User object in the database (for the user joe). Then we’ll see if we can query the database to find the user based on the user ID. Listing 3.2 introduces our first saving test.

Listing 3.2. Saving and retrieving a domain object from the database

The process of creating a new instance of a domain object normally consists of constructing the object, and then invoking the save() method . When we invoke save(), GORM generates the SQL to insert our User object into the database. GORM will return the saved User object (or null if save() fails, which we’ll talk about later). Once the User has been saved to the database, it will be assigned an id field in the database . We can then use this id with the get() method to query for the object (you can also use the read() method if you want a read-only copy of the object).

There are much snazzier ways to query for objects than get() and read(), and we’ll cover them when we get to dynamic finders in the next chapter, but get() will serve us well for now.

It’s time to confirm that our test case works, so let’s ask Grails to execute our test case:

grails test-app -integration

You can use grails test-app if you want to run both unit and integration tests, but we’re only interested in integration tests for now. You’ll get lots of output in the console, but somewhere in the middle you’ll see the good news we’ve been looking for:

-------------------------------------------------------
Running 1 Integration Test...
Running test com.grailsinaction.UserIntegrationTests...
testFirstSaveEver...SUCCESS
Integration Tests Completed in 1772ms
-------------------------------------------------------

And we’re all green (that’s what people say when tests pass, because most IDEs display passing tests a with green bar). That “SUCCESS” is telling us that all of our assert calls are passing, as we expected.


What does save() do behind the scenes?

Behind the scenes, save() uses the Hibernate session that Spring puts on the current thread, then adds our User object to that session. In Hibernate lingo, this means the User object moves from being a transient to persistent object.

The flush to the database (the real SQL inserts) from the Hibernate session occurs at the end of the thread’s lifetime, but if you want to force your object to be persisted immediately, you can do an explicit user.save(flush: true). But we’re getting ahead of ourselves.

We’ll cover this in more detail in chapter 13.


3.2.2. Updating user properties

We’ve completed our first save, so let’s try implementing an update routine. Update is a special case of saving, so let’s try our hand at updating joe’s password programmatically.


Note

We have to create our “joe” user every time we run a test cycle because integration tests always return the database to the way they found it. Our changes execute in a transaction that’s rolled back at the end of each test to ensure that the database is clean for each test.


We’ll start with our usual save() and get(), as in our previous test, and then we’ll modify some user fields and repeat the save() and get() to make sure the update has worked. Listing 3.3 takes us through the save-and-update test cycle.

Listing 3.3. Updating users by changing field values and calling save()

You’ll be used to the save() and get() cycle from our previous test. But notice how executing an update is just a matter of changing property values and invoking save() to persist the change to the database. We then requery the database to confirm that the password change has actually been applied .

Let’s confirm that our change is working as we expected by invoking another grails test-app -integration:

-------------------------------------------------------
Running 2 Integration Tests...
Running test com.grailsinaction.UserIntegrationTests...
testFirstSaveEver...SUCCESS
testSaveAndUpdate...SUCCESS
Integration Tests Completed in 1939ms
-------------------------------------------------------

3.2.3. Deleting users

We now have a good feel for loading and saving. But those pesky bots will soon be filling our database with dodgy user registrations, so we’ll need to delete User objects too.

It’s time for our third and final test case. Listing 3.4 shows how to use the delete() method to remove an object from the database.

Listing 3.4. Deleting objects from the database is a one-liner

Deleting gives us a chance to introduce two new domain class methods: delete() and exists(). You can call delete() on any domain class that you’ve fetched from the database . Even though the object is removed from the database, your instance handle won’t be nullified, which is why we can reference foundUser.id in the later exists() call, even after foundUser has been deleted from the database.

You can check for the existence of any domain instance with the exists() method . As you would expect, exists() returns true if that ID exists in the database.

We now have a good handle on saving, updating, and deleting our User objects. But although we’ve tested that our save() calls work correctly, we haven’t encountered any reason for a save() call to fail. The main reason for such failure is a domain class field constraint-validation failure, and it’s time to introduce you to the features Grails offers for validation.

3.3. Validation: stopping garbage in and out

We’ve created our new User object and successfully tested saving it to the database, but you might already be thinking a little defensively: “What keeps clients from putting all sorts of junk (including nulls and blanks) into our domain object and saving them?” The answer is, nothing yet. That’s our cue to talk about validation.

Grails goes out of its way to make all the common validations easy, and when things don’t quite match your validation needs, it’s not hard to customize them. Say we want to make sure that all passwords have at least six characters but not more than eight. We can apply this sort of constraint through a special constraints closure that uses a comprehensive DSL to specify constraints. There are validators available to limit the size of fields, enforce non-nullability, check (via patterns) whether a field contains a URL, email address, or credit card number, and so on.

Let’s add some basic constraints to our User object. We’ll make sure the userId and password fields have size limits and that the homepage contains a valid URL. Listing 3.5 shows our updated domain class with the new constraints.

Listing 3.5. Grails makes adding constraints straightforward

The size constraint makes sure the userId field is between 3 and 20 characters. When applied to a String field, size checks the length of the string. But if you apply it to a numeric field, it will ensure that the number entered is within the range. For example, an Integer field called quantity could be constrained to ensure the user doesn’t order more than 10 items with quantity (size: 0..10). We’ve also specified a unique constraint on the User to ensure that two users don’t have the same userId.

You don’t have to list all fields in your constraints block—only those you want to supply specific validations for. One thing to note, though, is that fields aren’t nullable by default, so if you want a field to be optional, you have to specify the nullable constraint explicitly. We allow our homepage field to be optional (nullable), but if it’s supplied, we force it to match a URL pattern. This kind of combination gives you a lot of power for specifying validations concisely, yet expressively.

What happens if the user tries to save an object that doesn’t satisfy the constraints on an object? Let’s write a test case and see. It’s time to introduce you to the validate() method that’s available on every domain class. When you call validate(), Grails checks to see if the constraints have been satisfied and provides an errors object that you can interrogate to see which fields failed.

Listing 3.6 augments our UserIntegrationTests.groovy file with a new test that attempts to save an instance that doesn’t satisfy the constraints.

Listing 3.6. Interrogating the results of a failed validation

As we mentioned, the validate() method checks whether all of the constraints on the domain class have been satisfied, and it returns true or false to let you know. As a result, this is a common idiom you’ll see in Grails controllers:

if (user.validate()) {
user.save()
} else {
// go and give them another crack at it... or
user.discard()
}

After you have checked for validation, you can access the domain object’s errors property to see what went wrong . The returned errors object holds a collection of fieldError objects, each representing a different field in your domain object. Each fieldError object has a code that describes the type of validation that failed and a rejectedValue that contains the data the user entered. If the field has no errors, its fieldError object will be null, which is the case for our userId .

In case you’re wondering about those error codes, we’ll give you a full set of them in the next section (in table 3.1). But for now, just know that you can find out exactly what is failing the validators. In chapter 7, we’ll show you how to do all of these checks in a unit test, which makes things a lot more concise.

Table 3.1. Grails gives you lots of validators out of the box.

Name

Description

Example

Error properties

blank

Ensures string isn’t blank (or null)

password(blank:false)

blank

email

Ensures field is a well-formed email address

userEmail(email:true)

email.invalid

inList

Ensures value appears in supplied range or collection

country(inList:['Australia',
'England')

not.inList

matches

Ensures field matches the supplied regular expression

userId(matches: '[0-9]{7}
[A-Za-z]')

matches.invalid

maxSize

Ensures size of field in database doesn’t exceed supplied value

orderQuantity(maxSize:100)

maxSize.exceeded

minSize

Ensures size of field in database always exceeds supplied value

orderQuantity(minSize:10)

minSize.notmet

nullable

Specifies whether the property is allowed to be null

password(nullable: false)

nullable

size

Specifies a range for min and max length of a string or size of an int or collection

userId(size:3..20)

size.toosmall or size.toobig

unique

Specifies whether the property must be unique or not

userId(unique:true)

unique

url

Ensures that the field contains a valid URL

homepage(url:true)

url.invalid

validator

Allows custom validation by supplying a closure

See section 3.3.3

validator.error

Now that we know how to cause an error to occur (by violating a constraint), let’s write a test case that repairs the damage after a bad save attempt. This isn’t something you’d typically be able to do when processing a web request, but it will help demonstrate how these validations work. Listing 3.7 shows a test case that first attempts a save() with invalid data and then repairs the damage and performs a valid save().

Listing 3.7. Recovering from a failed validation

Our original User object had an invalid URL and password , which caused the object to fail validation . After correcting the troublesome fields, validate() is happy again and the errors object is reset .

We’ve now exercised our constraints and have some confidence that our database fields will be persisted consistently. So far, we’ve only exposed you to size and URL constraints, but there are several Grails validators available, which we’ll explore next.

3.3.1. Standard validators

Now that you know how the basic constraint mechanism works, you may be wondering what Grails validators are available out of the box. There are plenty, and table 3.1 lists the most common ones.

You can find a complete set of validators in the Grails reference documentation at http://grails.org/doc/1.1/.


Blank isn’t null?

You may have noticed in table 3.1 that there are separate validators for nullable and blank. This is important, because when you submit HTML forms with empty fields, they’re presented to Grails as “blank” fields that would pass a nullable:true validation. The rule of thumb is that if you always want the user to supply a value, use blank:false. If you don’t mind if a user provides a value or not, use nullable:true.


3.3.2. Custom validation with regular expressions

Of course, your validation rules are different. You’ll need to customize your validations.

If your validation is a variation on a regular expression pattern, the matches constraint will probably do. Say you’re writing a student system for your local university, and all student IDs are seven numbers followed by a letter. You might implement that with a straight regular expression:

static constraints = {
userId(matches: '[0-9]{7}[A-Za-z]')
}

Regular expressions unlock a whole lot of power. But there are still situations when they aren’t powerful enough.

3.3.3. Cross-field validation tricks

Regular expressions can take you a certain distance, but there are situations when you need to do cross-field validations. For example, take the business rule that a user’s password must not match their userId. For these sorts of situations, you’ll need the validator closure constraint. It’s a little trickier to understand, but it gives you the power to do anything!

When you specify the validator constraint, you supply a closure with one or two parameters. The first is the value that the user tried to place in the field, and the second, if you supply one, references the instance of the domain class itself. The closure should return true if the data entered is valid for that field.

In our case, we need the two-argument version because we want to confirm that what the user typed in their password field doesn’t match their userId:

static constraints = {
password(size: 6..8,
validator: { passwd, user ->
return passwd != user.userId
})
homepage(url: true, nullable: true)
}

Things are getting quite tricky. When the domain class is saved, the password validators now ensure that the password is between 6 and 8 characters inclusive, and that the supplied password doesn’t match the user’s userId. You can get as creative as you like with custom validators because they give you the power to programmatically check just about anything.


Tip

Several of the constraints (such as size, maxSize, and nullable) have a direct impact on how Grails generates the fields in your database. For example, if you specify a maxSize of 8, Grails will generate a database field with a column size of 8. Check out the reference guide for specific advice on how certain constraints affect database generation.


3.4. Defining the data model—1:1, 1:m, m:n

We now know how CRUD operations work, how we can apply validations to our domain class fields, and even how to generate a quick and dirty UI. But Hubbub is going to need a lot more than a User class to get work done, so it’s time to learn about modeling relationships in the data model.

Just because you’re using an ORM, it doesn’t mean you should have to compromise on how you model your domain classes. Grails gives you the flexibility to use whatever relationships make sense for you: one-to-one (1:1), one-to-many (1:m), or many-to-many (m:n). Even better, GORM looks after creating the appropriate table structures, using sensible naming conventions.

3.4.1. One-to-one relationships

We’ll first model a one-to-one relationship. This is probably the easiest relationship to understand.

In our Hubbub example, it’s time to refactor out the user’s authentication fields (user ID, password, last login) and profile information (homepage, email, photo, and whatever else comes along). We’re moving toward our original Hubbub data model (shown in figure 3.2), which includes a Profile object. The relevant section of the data model is shown in figure 3.5.

Figure 3.5. Each User object has an optional Profile object.

We’ll start by creating a Profile domain class:

grails create-domain-class com.grailsinaction.Profile

Next, we’ll update our newly created object to handle the Profile-related features of the existing User class. We’ll pull out the homepage field, and add new entries for email and even a photo. Listing 3.8 shows the refactored Profile object.

Listing 3.8. Refactored Profile object with a 1:1 relationship with the User object

The most obvious new feature in this domain class is the belongsTo field . This field tells GORM that this object has a relationship to User. It also tells GORM how to cascade updates and deletes (as discussed in the sidebar).

In listing 3.8, belongsTo is assigned the owning class, which means the relationship is unidirectional. You can get to a Profile via a User, but there’s no link back from a Profile to a User. There’s also a form of belongsTo that lets us make a bidirectional mapping, which we’ll explore in section 3.4.2 on 1:m relationships.

We’ve introduced several new fields and constraints on the Profile object. We’ve added placeholders for full name, bio, country, and time zone. We’ve added optional fields for home page and email and used the built-in validators to make sure they’re conformant. Because they’re optional, we’ve marked them nullable right from the get-go. Jabber addresses have the same form as email addresses, so we can apply a validator to that field too.

We also want to store the user’s photo with their profile as a BLOB (binary large object). In this case, marking the field as a byte array (byte[]) tells GORM to store it as a BLOB .


BelongsTo and cascading

GORM only cascades to objects marked with belongsTo. In listing 3.8, Profile belongsTo User, so if any User is deleted, the matching Profile object will also be deleted. Without a belongsTo, the matching profile would not be deleted. This becomes increasingly important in 1:m relationships, where you want to ensure you tidy up.

belongsTo also has a special meaning in m:n relationships, where addTo*() methods can only be persisted from the owning side. But more on that later.


Now that we have the Profile class set up, it’s time to link it up from our User object. In listing 3.9, we create a field of type Profile in our User class and specify some constraints about how the relationship works.

Listing 3.9. Adding a 1:1 relationship from User to Profile

We’ve introduced a few new features to our User class in the 1:1 refactoring. First, we’ve added a Profile field to our User so Grails knows the link is 1:1 . (It would need to be a set (or list) of Profiles to be 1:m.)

We’ve also added a constraint to make the profile nullable . If you don’t specify this, Grails will force you to create a Profile instance every time you create a User object, which is overhead we can avoid for now.

Finally, we’ve added a new mapping closure to our User. The mapping block lets you customize all kind of advanced database interactions, including caching, table and field names, and loading strategies. Here we’ve set the Profile object to load eagerly , which means every time Grails loads a User it will phrase the query in a way that Profile is always returned in the same ResultSet.

You don’t always need to make the profile nullable and set it to load eagerly. In many cases you won’t need either, which makes a 1:1 association as simple as declaring an instance of the child object against the parent.


Eager and lazy fetching strategies

By default, GORM uses a lazy fetching strategy to retrieve attached collections as they’re accessed. Most of the time, that’s exactly what you want. But in the case of 1:1 mapping, if your access strategy involves accessing the linked object immediately (as we do with our Profile object), it makes sense to have Hibernate retrieve the Profile at the same time as the related User. This is an eager fetching strategy, and in these scenarios it improves performance.

If you’re using a 1:1 relationship with eager fetching, it might make sense to use Grails’ composition feature. This allows you to embed the Profile object into the same table as the User object (but still use different object references to talk to each). We’ll talk more about this in chapter 13 on advanced GORM use.


Now that we have some experience with 1:1 mappings, it’s time to turn to the more common one-to-many (1:m) modeling scenario.

3.4.2. One-to-many relationships

In our Hubbub example, each user will be capable of making many posts or entries, and each post will belong to one (and only one) user, as shown in figure 3.6. That’s a classic one-to-many (1:m) relationship.

Figure 3.6. Each User can have zero to many Post objects.

We’ll first create the relationship, and then we’ll look at how you can apply some sorting to the many side of the relationship.

Creating the one-to-many Relationship

We’ll need a new domain class for Post, so let’s create it:

grails create-domain-class com.grailsinaction.Post

Grails introduces two domain class property types to model the relationship: hasMany (on the “one” side of the relationship) and belongsTo (on the “many” side of the relationship). Let’s implement the Post object first, because it just needs a content field and the date it was created. Listing 3.10 shows the class.

Listing 3.10. The Post object models all the posts for a given User

We saw the belongsTo field in our 1:1 relationship (listing 3.8) and learned how it affects cascading operations. In particular, when the User is deleted, all their matching Post objects will be deleted too.


The two forms of belongsTo

We saw belongsTo in our Profile class (listing 3.8), where it referenced the owning class directly belongsTo = User. This creates a unidirectional relationship; you can get to a Profile via a User but the reverse isn’t true.

In listing 3.10, we use the map style of belongsTo, where we create a bidirectional link between User and Post classes. This creates a new field on Post called user that is the bidirectional mapping back to the owning User. This lets us move backwards to post.user.userId, for example. This will be handy later, when we query for posts and want to show the associated user’s ID.


We’ve told Grails that Post belongs to a User, so now we need a way of telling it that our User object should link to many Post objects. That’s done with a hasMany closure:

class User {

// existing code here

static hasMany = [ posts : Post ]

}

With hasMany and belongsTo in place, we have all the basics of the one-to-many relationship. But how do we tell Grails about adding new Posts for a given User? With some more GORM magic.

Once you have a one-to-many relationship between User and Post, Grails automatically adds two new methods to your User class: User.addToPosts() and User.removeFromPosts(). We’ll create a new integration test for Post so we can exercise these new capabilities. We start with the usual process:

grails create-integration-test com.grailsinaction.PostIntegration

With the shell of our test case in place, let’s write some code to create a user and add a bunch of new posts to their account. In listing 3.11, we’ll take full advantage of the new addToPosts() method to make our User a little more prolific.

Listing 3.11. The User.addToPosts() method makes 1:m relationships easy

Notice that we have to call save() on the User object to persist it in the database . Once the User is attached to the database, though, any additions we make to its object graph (like adding new Post objects via addToPosts() ) are automatically persisted. For this reason, we don’t need to call save() on each Post we create. If you’re feeling skeptical, rerun your test cases to make sure everything works as you expect:

grails test-app -integration

By taking advantage of some of GORM’s magic dynamic properties, we’ve added our user and a few posts. But how do we retrieve those posts when we want to do some work? A typical approach is to get a handle to the User object, and iterate through their posts. Listing 3.12 shows a test case that accesses all posts for a given user.

Listing 3.12. Accessing a User's posts by walking the object graph

In this example, we load the user via id , and then we use the Groovy collect() method to iterate through each post, retrieving the content. collect() returns a List of Post content entries that we compare to ensure they match . By default, you won’t know the ordering of 1:m collections (because they’re mapped as Sets), so for our test case we sort them alphabetically to make the comparison meaningful .

To present the user’s posting history, you’ll typically want to sort their posts by descending creation date, but sorting by hand every time is going to get old pretty quickly. In the next section, we’ll look at a way to return posts already sorted.

Keeping the Many Side Sorted

When using one-to-many relationships, you often won’t care about the ordering on the many side, such as for items on an invoice. For these situations, it makes sense to use the default Grails ordering. When you do need to apply some ordering, you can take advantage of Grails’ more sophisticated search options (like the Criteria API, which we cover later in chapter 4) to do the ordering at the time.

But sometimes you’ll want to access the many side of a relationship in a prescribed order. For example, in a blog application you’ll likely want to keep entries in descending date order (so your front page will display the most recent entries). For these situations, Grails lets you specify your own ordering mechanism using the mapping closure (which we used earlier in our Profile example in listing 3.9).

In order to implement this type of sorting, we need to let Grails know that our Posts need to be returned in a sorted order based on the date they were created. This is achieved by adding a new mapping block to our Post domain class, as shown in listing 3.13.

Listing 3.13. Sorting Posts by creation date

You can specify the sort order as either ascending or descending. In this example, all queries to the Post object will return in a descending order.

But what if you only want the posts to be sorted when accessing them via the User object (such as when iterating over user.posts.each)? For those scenarios, Grails lets you specify the sort on the relationship itself, rather than on the Post object. We could update our User class (instead of the Post class) with a mapping block like this:

static mapping = {
profile lazy:false
posts sort:'dateCreated'
}

This form of the mapping tells Grails that we only want to sort by dateCreated when accessing the posts collection via a user. This feature is presently problematic in Grails 1.1 for certain scenarios (see issue GRAILS-4089 in the Grails JIRA) but it is likely to be fixed by the time you read this.

Now that we’ve looked at sorting, it’s time to move on to the trickiest relationship of them all: many-to-many.

3.4.3. Many-to-many relationships

Where would our Web 2.0 social networking application be without tags? Tags give users the chance to group and cluster their posts, browse posts associated with particular tags, and generally categorize their posts. Let’s make some provision in our domain model for tagging.

It’s also time to consider how we might want to use tags. Let’s imagine these are our requirements:

  • Generate a tag cloud for the user on their home page
  • Provide an RSS feed for all posts with a given tag
  • See all tags for a given post

To include those requirements in our domain model, we’ll need to model two relationships:

  • A User creates many Tags, so each Tag relates to one User (1:m)
  • A Post has many Tags, and each Tag may relate to many Posts (m:n)

That’s quite a mouthful, but the model in figure 3.7 might make things a little clearer.

Figure 3.7. A tricky many-to-many scenario between Users, Posts, and Tags

The good news about many-to-many relationships is that there’s little new syntax to learn. If two objects are in a many-to-many relationship, they will both have a hasMany clause pointing to the other object. Listing 3.14 updates our Post class to add the new hasMany relationship with our Tag class.

Listing 3.14. Modeling a Post that can have many Tags

We’ve seen hasMany before in one-to-many scenarios, and this is the same beast. The [ tags : Tag ] map tells us that a Post relates to many Tag objects, and that the relationship is stored in an attribute named tags.

Let’s introduce our Tag domain model, which we can link back to our Post object. In listing 3.15, we specify that a Tag hasMany Posts.

Listing 3.15. The Tag object models relationships to both Post and User

We can see the hasMany relationship in listing 3.15, this time linking back to the Post object. The other important difference in this class is that the Tag belongsTo both User and Post. This belongsTo relationship is important in the many-to-many context: it affects how addTo*() methods work (see the sidebar for more info).


How belongsTo affects many-to-many relationships

The belongsTo field controls where the dynamic addTo*() methods can be used from. In listing 3.15, we’re able to call User.addToTags() because Tag belongsTo User. We’re also able to call Post.addToTags() because Tag belongsTo Post. But Post doesn’t belongTo Tag, so we can’t call Tag.addToPosts().


The last change that we need to make relates to the User object, which now needs to be updated to reference the Post and Tag classes. Listing 3.16 updates the has-Many clause.

Listing 3.16. User now hasMany Posts and Tags

We’ve now referenced both Post and Tag in the User class’s hasMany clause. With all the pieces of the many-to-many relationship in place, let’s write a test case to make sure that our assumptions still hold true. Listing 3.17 presents a basic test case for a post with one or more tags.

Listing 3.17. A complex many-to-many scenario for posts and tags

Because our Tag class is 1:m to User and m:n to Post, we have to add the tag to the user, and the tag to the post. Behind the scenes, Grails manages both the User and Post fields on the newly added Tag object, ensuring that all the relationships are kept bidirectional.

In listing 3.17, we have a groovyPost with one tag (“groovy”) and a bothPost with two tags (“groovy” and “grails”). By making numerous calls to post.addToTags(), you can add as many tags to each post as the user wants.

As you can see, many-to-many relationships are the trickiest of the standard relationships, so it’s worth making sure you’ve got a good handle on how the addTo*() methods work. Listing 3.17 will get you started, but we encourage you to experiment with your own use cases.


Cascading: the rules for deletes and updates

GORM does a lot of work behind the scenes to make all those 1:m and m:n relationships work smoothly. We’ve explored the addTo*() methods already, but we haven’t looked into how GORM handles the cascading.

The rules around 1:m relationships are pretty straightforward. In our Hubbub example, if you delete a User, all of the associated Post objects will also be deleted by GORM automatically.

But let’s take the trickier situation of Tags. A Post may have many Tags, and each Tag may relate to more than one Post. In this case, GORM settles things by looking at the belongsTo clause. If there’s no belongsTo clause defined on the object, no cascades will happen in either direction, and we’re on our own.


3.4.4. Self-referencing relationships

The final part of the Hubbub data model models the “follows” process—how a User can follow other Users. The data model includes it as a self-referencing relationship, as shown in figure 3.8.

Figure 3.8. Modeling the “follows” relationship

There’s nothing special about the self-referencing part. It’s a specialized version of the one-to-many relationship you’ve already seen. We’ll update the User class’s hasMany reference to model the relationship, as shown in listing 3.18.

Listing 3.18. Modeling a User following other Users
class User {

//... other code omitted

static hasMany = [ posts : Post, tags : Tag, following : User ]
}

As usual, we’ll write a test case to make sure we have a feel for how things will work. Listing 3.19 adds people the user is following.

Listing 3.19. A simple test case for adding users

As you can see, addToFollowing() works the same way for self-references as in the previous one-to-many scenario.

We have now explored all the basic relationship types in Grails, and we have a full set of unit tests to prove it. Grails has also been busy generating the tables and fields behind the scenes (including the foreign key relationships). If you look inside the Hubbub database, you’ll see that it now consists of five tables that hold all the data and relationships in our application. Figure 3.9 shows the full layout of the database, which makes a lot of sense when you match it up with the domain model fields we’ve created to date.

Figure 3.9. The final Hubbub data model after all of our changes

3.5. Summary and best practices

We’ve covered an immense amount of material in this chapter. A lot of the concepts we’ve introduced are foundational and will be reinforced in the next few chapters where we cover controllers and views.

We’ve introduced the basic domain model class, including all of the common domain model relationships. We’ve also learned about validation and how to create custom constraints.

These are some of the best practices covered in this chapter:

  • Use domain-driven design. Create your basic domain model classes as the first step in your application, and use scaffolding to get them online. This will help you stay motivated and understand your domain better.
  • Learn the basic modeling options well. You’ll spend a lot of time setting up Grails models in your future development work. Take the time to learn all the basic relationship types presented in this chapter. The test cases will give you valuable experimentation fodder.
  • Use tests to experiment. Domain model test cases provide a great way of experimenting with tricky save() scenarios and testing out your validations.
  • Don’t trust users—validate. Make use of validators to keep your domain objects in order. Custom validators aren’t hard to implement, so don’t be afraid to roll your own if the situation demands. It’s better to encapsulate the validation logic in your domain class than to use dodgy controller hacks.

Armed with those basics, we need to develop a little more UI kung fu and we’ll be all set for our first fully functional version of Hubbub, which is only a few short chapters away.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset