Chapter 3. Modeling the domain
This chapter covers
- What GORM is and how it works
- 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 explore Grails’s support for the data model portion of your applications (getting stuff into the database and querying to get it back), and if you’re worried we’ll dig deep into complex outer joins, you’ll be pleasantly surprised how straightforward Grails makes data access. We won’t write a line of SQL, and you won’t find any Hibernate XML mappings here either. We’ll take full advantage of the convention over configuration paradigm we introduced in chapter 1, which means less time configuring and more time getting work done.
We’ll spend most of our time exploring how Grails persists domain model classes to your data store of choice (be it a relational database or a shiny new NoSQL store) using GORM, mentioned briefly in chapter 2. 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 you’ll use throughout this book: Hubbub. You won’t spend much time on the 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 your sample application.
Our goal in this book is to take you to the stage where you could work as a productive Grails developer. We’ll mentor you in the skills you need to produce world-class applications in record time by showing you how to develop a real application. Our plan is that everything you learn while developing Hubbub you can apply in your workplace developing the next Facebook (or other world-beating web app).
The example we’ll use for the rest of the book is Hubbub, a simple microblogging application similar to Twitter. Think of it as a system that lets you write short 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 things out for themselves. You can follow your friends’ posts, too. Figure 3.1 shows a complete version of Hubbub in action.
Tip
No doubt you’re already well-versed in how common social network apps work, but if you need friends to follow, check out @glen_a_smith and @pledbrook on Twitter. Our tweets may be geeky, but we’re mostly harmless.
The domain model for Hubbub is simple. Figure 3.2 shows the Hubbub entity relationship (ER) model in all its glory.
The User class holds the user’s authentication details (user ID and password), but all good social networking applications let you associate bio information with each user (profile pic, email, blog, time zone, and favorite rock star, for example). We model that in a Profile class (which is an optional 1:1 relationship—each User may optionally have one Profile and each created Profile relates to one, and only one, User).
Hubbub’s purpose is to let users create posts—one-line blog entries that describe what they’re hacking on right now. A user can write many posts, and each post has a single author, so that’s a classic 1:m (one-to-many) relationship.
But what’s a social networking application without hashtags? Applications such as Twitter make great use of #hashtags to see what topics are “trending” or “so hot right now” among users. Each time a user creates a Post, he can apply hashtags to it, and 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. You can also link the Tag back to the User object, because it’s handy to see all the tags a user has available without searching all their posts.
We’ve saved the trickiest part until last: the User object has self-references. A User can follow many other Users (which we call a “following” relationship). That sounds as if it would be exhausting to implement, but it turns out to be straightforward.
Don’t worry about getting it straight in your head yet. We’ll spend plenty of time with these classes over the next few chapters. You’ll get a feel for the function of the main objects and be on your way.
If you’re as impatient as we are, you probably wonder why we’re starting with all this domain-model design stuff here. Why not something a little sexier, such as an autocompleting Ajax-powered search gizmo? Don’t worry, we’ll 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.
When we work on Grails apps, the first thing we usually do is sketch out 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 a sense of how the finished product may look. This gets us in the headspace of the application and gives us ideas about the user experience. Peter likes to do this kind of stuff on his iPad, but I’m a little more lowtech, so I’ll scratch out something on paper.
When developing any kind of web app, 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 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. In fact, that’s why this chapter is the first to cover its topic in greater depth: domain modeling is a great place to start your Grails application development journey.
During this section you’ll start defining your data model, then you’ll use Grails to generate a quick-and-dirty scaffold UI (we introduced you to these autogenerated UI artifacts in chapter 1, and we’ll look more closely at them in the next chapter). With the autogenerated UI, you’ll feel like you’re making progress because you’ll have an app that runs and persists to a database. This will motivate you to move to the app’s next level of functionality and start implementing graph paper scratchings as a real UI.
You may be more disciplined than we are and 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.
You’ve completed the rough version of your screens on paper, and you have a “napkin-level” data model to work from, so it’s time to generate the application. Let’s create the app:
We find it’s good encouragement to do a cd hubbub followed by an initial grails run-app to start a newly created application. Point your browser at http://localhost:8080/hubbub/ to see things up and running, as shown in figure 3.4.
With the shell of Hubbub in place, it’s time to put meat on the bones. The next section explains how to generate your first domain class.
Before we generate our first domain class, let’s take a quick look at the GORM implementation.
Object-relational mapping (ORM) is the process of getting objects into and out of a persistent data source (you may use a relational database, an object database, or one of the new NoSQL databases—but Grails abstracts most of these details for you). Having an ORM layer such as GORM means you can be (mostly) oblivious to the SQL/NoSQL that happens behind the scenes and get on with coding. For example, if you call user.firstName = "Glen", the ORM may create the SQL UPDATE statement to ensure that the object’s firstName field is persisted in your relational database, or it might generate the JSON to send it to a NoSQL store. In Java applications, that role is usually handled by an ORM such as Hibernate or the Java Persistence API (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, EclipseLink, or another Java ORM library, you know that configuration is required. Often, you have to write XML mapping files or add annotations to each persistent class, and you may have to configure transactional behavior, too. GORM, like most of Grails, is based on 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 your first domain model object and see things in action.
We outlined the preliminary domain model at the beginning of this chapter, and you have your application shell in place, so it’s time to define your domain model classes. One of the first things you need to define is a User object so your users can sign up and start using the system.
The first step is to ask Grails to create a skeleton of your domain class:
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). As we discussed in chapter 1, it’s good practice to store classes in packages rather than in the default scope, so you’ll keep all source in a package called com.grailsin-action. Now it’s time to think about the fields you want to define for new User accounts. You don’t want the signup process to be onerous, but you need a few basics from your users:
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 automatically sets the current timestamp value on first save to dateCreated or on every save to lastUpdated. We take advantage of dateCreated to preserve the user’s registration time.
Now you can store a user’s details. You don’t have a UI to enter anything into yet, but you do have the skeleton of a test case that Grails created, which should make it easier to begin writing tests for your code. Before you write your first real test, let’s discuss the amazing world of Grails testing.
The whole testing infrastructure (and particularly unit testing) had a massive overhaul in Grails 2.0. If you’ve been around the Grails block, you know that unit testing support in Grails 1.x was clunky, tedious, and often incomplete, creating friction when writing solid tests for your app. Unit testing was rewritten completely in Grails 2.0, so even if you’re a Grails 1.x veteran burned by Grails testing, it’s worth following along the next few sections to start your transition to Spock and Grails 2.0 and find a reason to get excited about testing again.
We first introduced you to the idea of Grails automated testing in chapter 1, when you created tests for QuoteService. Tests are useful across your application—so useful, in fact, that chapter 9 discusses testing strategies for all development lifecycle phases.
For now, though, tests give us a chance to show how GORM saves your objects to the database and how you get them back. Let’s write the first test case.
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/YourArtifactSpec.groovy. Unit tests run in isolation and rely on fairly sophisticated mocking techniques using Groovy mixins (which we introduce in the next few chapters and deep dive into in chapter 7). For most of your everyday Grails hacking, you’ll work with unit tests. Why didn’t we start there?
Given that we’re testing database-related logic, integration tests are the “right way to do it,” and you may as well learn the right way! Yes, Grails does provide mocking support for the data tier, but in this chapter we want to test the data tier, not mock it out! Grails calls this integration testing.
For integration tests, Grails bootstraps the real database and wires up all components as it would for a running application. That means you can see what happens when you create, save, and delete domain objects into a real database, and you don’t have to mess with any tricky mocking features yet. Integration tests are much slower to run, but they’re fantastic for the learning and experimenting you’ll do in chapter 9. They’re also the right way to test transactional code, because no one deploys to a mock database!
As we discussed previously, Grails creates a unit test case skeleton in /test/unit/com/grailsinaction/UserSpec.groovy. But you want an integration test, because you want to run it against your database. Recall from chapter 1 that you create integration tests with this command:
This command generates /test/integration/com/grailsinaction/UserIntegrationSpec.groovy.
Next, create and save a User object in the database (for the user joe). Then see if you can query the database to find the user based on the user ID. The following listing introduces your first saving test.
The process of creating a new domain object instance normally consists of constructing the object, then invoking the save()method . When you invoke save(), GORM generates the SQL code to insert your User object into the database. GORM returns the saved User object (or null if save() fails, which we’ll talk about later) and sets an errors object to hold any validation errors
. Once the User is saved to the database, it’s 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).
Much snazzier ways exist for querying for objects than get() and read(), and we cover them when we get to dynamic finders in the next chapter, but get()works for now.
It’s time to confirm that your test case works, so let’s ask Grails to execute your test case:
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. Normally you’d follow the colon with the particular test name you wish to run, but you can leave it blank to run all integration tests. You get brief output in the console that gives you the good news you’ve been looking for:
And you’re all green (that’s what people say when tests pass because most IDEs display passing tests with a green bar). That “PASSED” tells us your Spock assertions passed, as expected. Grails also writes a nicely formatted HTML report in target/test-reports/html/index.html. Figure 3.5 shows the output from your previous test run.
What does save() do behind the scenes?
Behind the scenes, save() uses the Hibernate session that Spring puts on the current thread, then adds your User object to that session. In Hibernate lingo, this means the User object moves from being a transient to a 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 persist 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 12.
You’ve completed your first save, so try implementing an update routine. Update is a special case of saving, so let’s try updating joe’s password programmatically.
Note
You have to create your “joe” user every time you run a test cycle, because integration tests always return the database to the way they found it. Your 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.
Start with save()and get()as in your previous test, and then you’ll modify user fields and repeat the save() and get() to make sure the update worked. The following listing takes you through the save-and-update test cycle.
You’re used to the save() and get() cycle from your previous test. But notice how executing an update is a matter of changing property values and invoking save()
to persist the change to the database. Setting the failOnError:true option to save() means Grails will throw an exception if the object fails any validation tests. An exception causes the test to fail instantly, so using this option means you don’t have to look at the errors property later in your test results. Save your updated object, then requery the database to confirm that the password change was applied
.
To confirm that your change is working as you expect, invoke another grails test-app integration:
You can now see that your two tests are running successfully with no failures! With your updates running successfully, it’s time to turn your attention to deleting users.
You now have a feel for loading and saving, but those pesky bots will soon fill your database with dodgy user registrations, so you need to delete User objects, too.
It’s time for your third and final test case. The following listing shows how to use the delete() method to remove an object from the database.
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 fetch from the database . We use the flush:true option because we want your test to delete it from the database immediately and not batch up the change.
Even though flush:true removes the object from the database, your instance handle won’t nullify, which is why you can reference foundUser.id in the later exists() call, even after foundUser is 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. Spock lets you specify "then:" block assertions as Booleans, so you don’t need the full form of User.exists(foundUser.id) == false.
Before you move on, you can confirm nothing is broken with a grails test-app integration:
You now have a good handle on saving, updating, and deleting your User objects. But although you tested that your 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 (such as not providing a value for a field that’s nonnullable, or providing an invalid email address for an email type field). Now it’s time to introduce you to the features Grails offers for validation.
You created your new User object and successfully tested saving it to the database, so you may already think a little defensively: “What keeps clients from putting all sorts of junk (including nulls and blanks) into my 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 match your validation needs, it’s not hard to customize them. Say you want to make sure that all passwords have at least six characters but not more than eight. You can apply this sort of constraint through a special constraints closure that uses a comprehensive domain-specific language (DSL) to specify constraints. You can use a validator to limit the size of fields, enforce non-nullability, or check (via patterns) whether a field contains a URL, email address, credit card number, or other data.
Let’s add basic constraints to your User object. We’ll make sure the loginId and password fields have size limits and that the homepage contains a valid URL. The following listing shows your updated domain class with the new constraints.
The size constraint makes sure the loginId field is between 3 and 20 characters (inclusive). When applied to a String field, size checks the length of the string. But if you apply it to a numeric field, it ensures 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). You also specified a unique constraint on the User to ensure that two users don’t have the same loginId.
The one true style of constraints
Grails constraints can be specified in two different styles. Optionally, you can put parentheses around the list such as:
Or drop the parentheses entirely and use the style in listing 3.4. We like using the style without the parentheses because it means less visual clutter, but you might see either style in production code. Previous versions of Grails required the parentheses, so we thought we’d warn you about it here.
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 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. You allow the homepage field to be optional (nullable), but if it’s supplied, you force it to match a URL pattern. This kind of combination gives you more power to specify 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 whether or not the constraints have been satisfied and provides an errors object that you can interrogate to see which fields failed.
The following listing augments your UserIntegrationSpec.groovy file with a new test that attempts to save an instance that doesn’t satisfy the constraints.
As we mentioned, validate() checks the constraints on the domain class to see if they’ve been satisfied, and it returns true or false. As a result, this is a common idiom you see in Grails controllers:
After you check 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 describing the type of validation that failed and a rejectedValue
containing the data the user entered. If the field has no errors, its fieldError object is null, which is the case for loginId
.
In case you want to know more about those error codes, we give you a full set of them in table 3.1. But for now, know that you can find out exactly what’s failing the validators. In chapter 7, we’ll show you how to do all these checks in a unit test, which makes things more concise.
Table 3.1. Grails validators available out of the box
Name |
Description |
Example |
Error properties |
---|---|---|---|
blank | Ensures string isn’t blank (or null). | password(blank:false) | blank |
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. | loginId(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. | loginId(size:3..20) | size.toosmall or size.toobig |
unique | Specifies whether the property must be unique. | loginId(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.invalid |
bindable | Affects whether a property will bind via automatic data binding. | See chapter 11 on security | N/A |
Now that you know how to cause an error (by violating a constraint), write a test case that repairs the damage after a bad save attempt. This isn’t something you’d typically do when processing a web request, but it helps demonstrate how these validations work. The following listing shows a test case that first attempts a save() with invalid data and then repairs the damage and performs a valid save().
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 resets
. Once the user is in a valid state, save() returns the saved object
.
You’ve now exercised constraints, and you’ve gained confidence that your database fields will persist consistently. Until now, we’ve exposed you to size and URL constraints only, but we’ll explore additional Grails validators next.
Now that you know how the basic constraint mechanism works, you may wonder what Grails validators are available out of the box. Plenty exist, 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/latest/guide. To find the names of the codes, click Constraints from the Quick Reference list, then click a specific constraint type.
Blank isn’t null?
You may have noticed in table 3.1 the 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.
What if your validation rules are different, and you need to customize them?
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 may implement that with a straight regular expression:
Regular expressions unlock power, but they may not be powerful enough in certain situations.
Regular expressions can take you a certain distance, but won’t help if you need to do cross-field validations. Take the business rule that a user’s password must not match their loginId. For these sorts of situations, you 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 parameter 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 you want to confirm that what the user typed in their password field doesn’t match their loginId:
Things are getting tricky. When you save the domain class, the password validators now ensure that the password is between six and eight characters inclusive and that the supplied password doesn’t match the user’s loginId. You can get as creative as you like with custom validators, because they give you the power to check programmatically nearly 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. If you specify a maxSize of eight, Grails generates a database field with a column size of eight. Check out the reference guide for specific advice on how certain constraints affect database generation.
Constraints are a powerful way to specify declaratively the business rules that relate to your domain objects. But what if you need to use the same set of constraints across several objects? What if you decide that passwords need to have the same rules across all objects that have a password?
New in 2.0: Sharing constraints between objects
You could copy and paste, but that violates the DRY (don’t repeat yourself) principle and gives you many points of update. Grails 2.0 introduced a constraints-sharing mechanism that lets you import constraints between objects. Suppose you want an external application to consume Hubbub API services. We’ll create an ApplicationUser domain class to model that role, but we want to preserve the same password business rules for passwords as our standard User. With that scenario in mind, let’s examine the following listing, which shares constraints between two domain objects: User and ApplicationUser.
In this example you import the rules related to the password property to the new ApplicationUser object . You use the include: style, which lets you whitelist the properties to import. Grails also supports an exclude: style, which blacklists property constraints that you don’t want to import. For ultimate flexibility, it also supports a regular expression style importer that matches wildcards on imported names. To round out the import options, it offers a fourth “no args” style that imports all property constraints from the target object that have names matching the current object. Because your User and ApplicationUser objects share the same name for their password field, you can use the more terse importFrom User version.
You now know how CRUD operations work, how to apply validations to your domain class fields, and even how to generate a quick-and-dirty UI. But Hubbub needs more than a User class to get work done, so it’s time to learn about modeling relationships in the data model.
Using an ORM doesn’t mean you have to compromise on how you model 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.
You’ll first model a one-to-one relationship. This is probably the easiest relationship to understand.
In the Hubbub example, it’s time to refactor out the user’s authentication fields (loginId, password) and profile information (homepage, email, photo, and whatever else comes along). You’re moving toward your 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.6.
Start by creating a Profile domain class:
Next, update your newly created object to handle the Profile-related features of the existing User class. You pull out the homepage field and add entries for email and even a photo. The following listing shows the refactored Profile class.
The most obvious new feature in this domain class is the addition of a user field . This field tells GORM that Profile has a relationship to the User domain class (meaning GORM stores the User’s id value against the corresponding profile in the database).
Refactoring homepage and breaking tests
With your homepage property moved from the User class to Profile, several of your tests will now fail. That’s a good thing—it’s your safety net to make sure your logic still works how you expect. To fix things, make sure you update any references to user.homepage (which is now user.profile.homepage), including User() constructors!
You introduced several new fields and constraints on the Profile object, and added placeholders for fullName (which is a required field), bio, country, and timezone. You added fields for homepage and email and used the built-in validators to make sure they conform. Because most of these fields are optional (except for email and fullName), you marked them nullable right from the get-go. Jabber addresses have the same form as email addresses, so you can apply a validator to that field, too.
You also want to store the user’s photo with their profile as a BLOB (binary large object). In this case, marking the photo field as a byte array (byte[]) tells GORM to store it as a BLOB .
Now that you set up the Profile class, it’s time to link it to your User class. The next listing shows the code to create a hasOne link to Profile in your User class and specify constraints for how the relationship works.
You introduce new features to your User class in the 1:1 refactoring. First, you added a hasOne relationship to your Profile field for the User, so Grails knows the link is 1:1 . It needs to be a set (or list) of Profiles to be 1:m.
You also added a constraint to make the profile nullable . If you don’t specify this, Grails forces you to create a Profile instance every time you create a User object, which is overhead you can avoid for now.
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 hasOne mapping, if your access strategy involves accessing the linked object immediately (as you do with your 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 Hibernate defaults to eager loading in hasOne scenarios to improve performance.
If you use a 1:1 relationship with eager fetching, it may make sense to use Grails’s composition feature instead. 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 online chapter 19 on advanced GORM use.
Now that you have experience with 1:1 mappings, it’s time to turn to the more common one-to-many (1:m) modeling scenario.
In our Hubbub example, each user is capable of making many posts or entries, and each post belongs to one (and only one) user, as shown in figure 3.7. That’s a classic one-to-many (1:m) relationship.
First, create the relationship, and then we’ll look at how you can apply sorting to the many sides of the relationship.
You need to create a new domain class for 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). Implement the Post side first, because it needs only a content field and the date it was created. The following listing shows the class.
In our Post example, you see the belongsTo property for the first time. This property is vitally important in both 1:m and m:n relationships because it tells GORM how to implement cascading operations. In particular, when the User is deleted, all their matching Post objects are deleted, too.
BelongsTo and cascading
GORM cascades only to objects marked with belongsTo. In listing 3.10, Post belongsTo User, so if any User is deleted, the matching Post object is also deleted. belongsTo has a special meaning in m:n relationships, where addTo*() methods can be persisted only from the owning side. But more on that later.
In listing 3.10, you used the map style of belongsTo, where you created a bidirectional link between User and Post classes. This creates a new field on Post called user that’s the bidirectional mapping back to the owning User. This lets you move backward to post.user.loginId, for example. This is handy later, when you query for posts and want to show the associated user’s ID.
You told Grails that Post belongs to a User, so now you need a way to tell it that your User object should link to many Post objects. That’s done with a hasMany property:
With hasMany and belongsTo in place, you have all the basics of the one-to-many relationship. But how do we tell Grails to add new Posts for a given User? With 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.remove-FromPosts(). You need to create an integration test for Post so you can exercise these new capabilities. Start with the usual process:
With the shell of our test case in place, write code to create a user and add new posts to their account. In the following listing, you’ll take full advantage of the new addToPosts() method to make your User more prolific.
Notice that you 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 you make to its object graph (such as adding new Post objects via addToPosts()
) are automatically persisted. For this reason, you don’t need to call save() on each Post you create. If you feel skeptical, rerun your test cases to make sure everything works as you expect:
By taking advantage of GORM’s magic dynamic properties, you added your user and a few posts. But how do you retrieve those posts when you want to work? A typical approach is to get a handle to the User object and iterate through their posts. The following listing shows a test case that accesses all posts for a given user.
In this example, you load the user via id , then use the Groovy collect() method
to iterate through each post, retrieving the content. The collect() returns a list of Post content, which we compare to ensure the list value matches your known values. By default, you won’t know the ordering of 1:m collections (because they’re mapped as Sets), so for this test case, we sort them alphabetically to make the comparison meaningful
.
To present the user’s posting history, you typically want to sort their posts by descending creation date, but sorting by hand every time gets old quickly. In the next section, we look at a way to return posts already 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 ordering, take advantage of Grails’s more sophisticated search options (such as Where and Criteria queries, which we cover in chapter 5) to do the ordering at the same time.
Sometimes you want to access the many side of a relationship in a prescribed order. In a blog application you likely want to keep entries in descending date order (so your front page displays the most recent entries). For these situations, Grails lets you specify your own ordering mechanism using the mapping closure (which you used in our Profile example in listing 3.10).
To implement this type of sorting, let Grails know that your Posts need to be returned in a sorted order based on the date they were created. Do this by adding a new mapping block to your Post domain class, as shown in the following listing.
You can specify the sort order as either ascending or descending. In this example, all queries to the Post object return in a descending order.
But what if you want the posts 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. You can update your User class (instead of the Post class) with a mapping block like this:
This form of the mapping tells Grails that you want to sort by dateCreated when accessing the posts collection via a user.
Now that we’ve looked at sorting, it’s time to move on to the trickiest relationship of them all: many-to-many.
Where would your 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 a provision in the domain model for tagging.
It’s also time to consider how you may want to use tags. Let’s imagine these are your 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 your domain model, you 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 a mouthful, but the model in figure 3.8 may make things clearer.
The good news about many-to-many relationships is there’s little new syntax to learn. If two objects are in a many-to-many relationship, they both have a hasMany clause pointing to the other object. The following listing updates your Post class to add the new hasMany relationship with your Tag class.
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 a property named tags.
Let’s introduce the Tag domain model, which you can link back to our Post object. In the following listing you’ll specify that a Tag hasMany Posts.
You can see the hasMany relationship in listing 3.15 this time linking back to the Post class. 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 following sidebar for more information).
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 can call User.addToTags() because Tag belongsTo User. We can also 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. The following listing updates the hasMany clause.
You 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 your assumptions still hold true. The following listing presents a test case for a post with one or more tags, which you can add to PostIntegrationSpec.
Because your Tag class is 1:m to User and m:n to Post, you have to add the tag to the user and the tag to the post. Behind the scenes, Grails manages both the users and posts properties on the newly added Tag object, ensuring that all the relationships are kept bidirectional.
In listing 3.17, you 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 you need to get a good handle on how the addTo*() methods work. Listing 3.17 gets you started, but we encourage you to experiment with your own use cases.
Cascading: the rules for deletes and updates
GORM works behind the scenes to make all those 1:m and m:n relationships work smoothly. We’ve explored the addTo*() methods, but we haven’t looked into how GORM handles the cascading.
The rules around 1:m relationships are straightforward. In our Hubbub example, if you delete a User, GORM automatically deletes all associated Post objects.
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 you’re on your own.
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.9.
There’s nothing special about the self-referencing part. It’s a specialized version of the one-to-many relationship you’ve already seen. You can update the User class’s hasMany reference to model the relationship, as shown here:
As usual, write a test case to make sure you know how things will work. The test in the following listing adds people the user is following. It goes in UserIntegrationSpec.
As you can see, addToFollowing()works the same way for self-references as in the previous one-to-many scenario.
You explored relationship types in Grails, and you have a full set of integration tests to prove it. Grails has been busy also, 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.10 shows the full layout of the database, which makes sense when you match it up with the domain model fields you created to date.
We covered an immense amount of material in this chapter. Many of the concepts we introduced are foundational and are reinforced in the next few chapters where we cover controllers and views.
We introduced the domain model class, including the common domain model relationships. You learned about validation and how to create custom constraints.
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 helps you stay motivated and understand your domain better.
- Learn the basic modeling options. You’ll spend 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 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 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, you need to develop a little more UI kung fu to be ready for your first fully functional version of Hubbub, which is only a few short chapters away.