Object-oriented (OO) applications almost always involve a domain model representing the business entities that the application deals with. Our gTunes application will include a number of domain classes including Artist, Album
, and Song
. Each of these domain classes has properties associated with it, and you must map those properties to a database in order to persist instances of those classes.
Developers of object-oriented applications face some difficult problems in mapping objects to a relational database. This is not because relational databases are especially difficult to work with; the trouble is that you encounter an "impedance mismatch"3 between the object-oriented domain model and a relational database's table-centric view of data.
Fortunately, Grails does most of the hard work for you. Writing the domain model for a Grails application is significantly simpler than with many other frameworks. In this chapter, we are going to look at the fundamentals of a Grails domain model. In Chapter 10, we will cover more advanced features of the GORM tool.
By default, all the fields in a domain class are persisted to the database. For simple field types such as Strings
and Integers
, each field in the class will map to a column in the database. For complex properties, you might require multiple tables to persist all the data. The Song
class from Chapter 2 contains two String
properties and an Integer
property. The table in the database will contain a separate column for each of those properties.
In MySql, that database table will look something like Listing 3-1.
Listing 3-1. The Song Table
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | NULL | |
| artist | varchar(255) | NO | | NULL | |
| duration | int(11) | NO | | NULL | |
| title | varchar(255) | NO | | NULL | |
+----------+--------------+------+-----+---------+----------------+
Notice that the table includes not only a column for each of the properties in the domain class, but also an id
column and a version
column. The id
is a unique identifier for a row and Grails uses the version
column to implement optimistic locking4.
Listing 3-1 shows the default mapping. Grails provides a powerful DSL for expressing how a domain model maps to the database. Details about the mapping DSL appear later in this chapter in the "Customizing Your Database Mapping" section.
You'll probably encounter business rules that constrain the valid values of a particular property in a domain class. For example, a Person
must never have an age that is less than zero. A credit-card number must adhere to an expected pattern. Rules like these should be expressed clearly, and in only one place. Luckily, Grails provides a powerful mechanism for expressing these rules.
A Grails domain class can express domain constraints simply by defining a public static property named constraints
that has a closure as a value. Listing 3-2 shows a version of the Song
class that has several constraints defined.
Listing 3-2. The Song Domain Class
class Song {
String title
String artist
Integer duration
static constraints = {
title(blank: false)
artist(blank: false)
duration(min: 1)
}
}
The Song
class in Listing 3-2 defines constraints for each of its persistent properties. The title
and artist
properties cannot be blank. The duration
property must have a minimum value of 1
. When constraints are defined, not every property necessarily needs to be constrained. The constraints closure can include constraints for a subset of properties in the class.
The validators used in Listing 3-2 are blank
and min
. Grails ships with a lot of standard validators that cover common scenarios (see Table 3-1).
Table 3-1. Standard Validators in Grails
Name | Example | Description |
blank | login(blank:false) | Set to false if a string value cannot be blank |
creditCard | cardNumber(creditCard:true) | Set to true if the value must be a credit-card number |
homeEmail(email:true) | Set to true if the value must be an e-mail address | |
inList | login(inList:['Joe', 'Fred']) | Value must be contained within the given list |
length | login(length:5..15) | Uses a range to restrict the length of a string or array |
min | duration(min:1) | Sets the minimum value |
minLength | password(minLength:6) | Sets the minimum length of a string or array property |
minSize | children(minSize:5) | Sets the minimum size of a collection or number property |
matches | login(matches:/[a-zA-Z]/) | Matches the supplied regular expression |
max | age(max:99) | Sets the maximum value |
maxLength | login(maxLength:5) | Sets the maximum length of a string or array property |
maxSize | children(maxSize:25) | Sets the maximum size of a collection or number property |
notEqual | login(notEqual:'Bob') | Must not equal the specified value |
nullable | age(nullable:false) | Set to false if the property value cannot be null |
range | age(range:16..59) | Set to a Groovy range of valid values |
scale | salary(scale:2) | Set to the desired scale for floating-point numbers |
size | children(size:5..15) | Uses a range to restrict the size of a collection or number |
unique | login(unique:true) | Set to true if the property must be unique |
url | homePage(url:true) | Set to true if a string value is a URL address |
The constraints block in a domain class will help prevent invalid data from being saved to the database. The save()
method on a domain object will automatically validate against the constraints before the data is written to the database. Data is not written to the database if validation fails. Listing 3-3 demonstrates how code can react to the return value of the save()
method.
Listing 3-3. Validating a Song Object
// −68 is an invalid duration
def song = new Song(title:'The Rover',
artist:'Led Zeppelin',
duration:-68)
if(song.save()) {
println "Song was created!"
} else {
song.errors.allErrors.each { println it.defaultMessage }
}
An interesting aspect of Listing 3-3 is the usage of the errors
property on domain classes. This property is an instance of the Spring Framework's org.springframework. validation.Errors
interface, which allows advanced querying of validation errors. In Listing 3-3, when validation fails, the code generates a list of all the errors that occurred and prints them to stdout
.
Some of the more useful methods in the Spring Errors interface are shown in Listing 3-4.
Listing 3-4. Methods in the Spring Errors Interface
package org.springframework.validation;
interface Errors {
List getAllErrors();
int getErrorCount();
FieldError getFieldError(String fieldName);
int getFieldErrorCount();
List getFieldErrors(String fieldName);
Object getObjectName();
boolean hasErrors();
boolean hasFieldErrors(String fieldName);
// ...x remaining methods
}
Occasionally you'll find it useful to make changes to the domain model before committing to the save()
method. In this case, Grails provides a validate()
method, which returns a Boolean
value to indicate whether validation was successful. The semantics are exactly the same as in the previous example with the save()
method, except, of course, that the validate()
method doesn't perform persistent calls.
If validation does fail, the application might want to make changes to the state of the domain object and make another attempt at validation. All domain objects have a method called clearErrors()
, which will clear any errors left over from a previous validation attempt. Listing 3-5 demonstrates how code might react to the return value of the validate()
method.
Listing 3-5. Validating a Song Object, Revisited
def song = new Song(title:'The Rover',
duration:339)
if(!song.validate()) {
song.clearErrors()
song.artist = 'Led Zeppelin'
song.validate()
}
Grails provides a wide array of built-in validators to handle many common scenarios. However, it is impossible to foresee every feasible domain model and every specific kind of validation that an application might need. Fortunately, Grails provides a mechanism that allows an application to express arbitrary validation rules (see Listing 3-6).
Listing 3-2. Constraining the Password Property in the User Domain Class
class User {
static constraints = {
password(unique:true, length:5..15, validator:{val, obj ->
if(val?.equalsIgnoreCase(obj.firstName)) {
return false
}
})
}
}
The validator in Listing 3-6 will fail if the password
is equal to the firstName
property of the User
class. The validator closure should return false if validation fails; otherwise it should return true. The first argument passed to the closure is the value of the property to be validated. The second argument passed to the closure is the object being validated. This second argument is often useful if validation requires the inspection of the object's other properties, as in Listing 3-6.
In addition, when you return false from a custom validator, an error code such as user.password.validator.error
is produced. However, you can specify a custom error code by returning a String
:
if(val?.equalsIgnoreCase(obj.firstName)) {
return "password.cannot.be.firstname"
}
In this example, you can trigger a validation error simply by returning a String
with the value password.cannot.be.firstname
. You'll be learning more about error codes and how they relate to other parts of the application in later chapters. For now, let's move on to the topic of transient properties.
By default, every property in a domain class is persisted to the database. For most properties, this is the right thing to do. However, occasionally a domain class will define properties that do not need to be persisted. Grails provides a simple mechanism for specifying which properties in a domain class should not be persisted. This mechanism is to define a public static property named transients
and assign to that property a value that is a list of Strings. Those Strings represent the names of the class's properties, which should be treated as transient and not saved to the database (see Listing 3-7).
Listing 3-7. A Transient Property in the Company Domain Class
class Company {
String name
Integer numberOfEmployees
BigDecimal salaryPaidYTD
static transients = ['salaryPaidYTD']
}
In Listing 3-7, the salaryPaidYTD
property has been flagged as transient and will not be saved to the database. Notice that the default generated schema for this domain class does not contain a column for the salaryPaidYTD
property (see Listing 3-8). In other words, the company
table does not contain a column for the transient property.
Listing 3-8. The Company Table
+---------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | NULL | |
| name | varchar(255) | NO | | NULL | |
| number_of_employees | int(11) | NO | | NULL | |
+---------------------+--------------+------+-----+---------+----------------+
Not all persistent properties necessarily correspond to a field in a domain class. For example, if a domain class has a method called getName()
and a method called setName()
, then that domain class has a persistent property called name
. It doesn't matter that the class doesn't have a field called "name." Grails will handle that situation by creating the appropriate column in the database to store the value of the name
property. But you can use the transients
property to tell Grails not to do that if the property really should not be persisted, as in Listing 3-9.
Listing 3-2. A Transient Property in the Company Domain Class
class Company {
BigDecimal cash
BigDecimal receivables
BigDecimal capital
BigDecimal getNetWorth() {
cash + receivables + capital
}
static transients = ['netWorth']
}
As we have seen already, Grails does a good job of mapping your domain model to a relational database, without requiring any kind of mapping file. Many developer productivity gains that Grails offers arise from its Convention over Configuration (CoC) features. Whenever the conventions preferred by Grails are inconsistent with your requirements, Grails does a great job of providing a simple way for you to work with those scenarios. The Custom Database Mapping DSL in Grails falls in this category.
Grails provides an ORM DSL for expressing your domain mapping to help you deal with scenarios in which the Grails defaults will not work for you. A common use case for taking advantage of the ORM DSL is when a Grails application is being developed on top of an existing schema that is not entirely compatible with Grails' default domain-class mappings.
Consider a simple Person
class (see Listing 3-10).
Listing 3-10. The Person Domain Class
class Person {
String firstName
String lastName
Integer age
}
The default mapping for that class will correspond to a schema that looks like Listing 3-11.
Listing 3-12. The Default Person Table
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | NULL | |
| age | int(11) | NO | | NULL | |
| first_name | varchar(255) | NO | | NULL | |
| last_name | varchar(255) | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
That works perfectly if you have a greenfield application that doesn't need to map to an existing schema. If the application does need to map to an existing schema, the schema will probably not match up exactly to the Grails defaults. Imagine that a schema does exist, and that it looks something like Listing 3-12.
Listing 3-12. A Legacy Table Containing Person Data
+-------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+--------------+------+-----+---------+----------------+
| person_id | bigint(20) | NO | PRI | NULL | auto_increment |
| person_age | int(11) | NO | | NULL | |eee
| person_first_name | varchar(255) | NO | | NULL | |
| person_last_name | varchar(255) | NO | | NULL | |
+-------------------+--------------+------+-----+---------+----------------+
Notice that the table contains no version
column and all the column names are prefixed with person_
. You'll find it straightforward to map to a schema like that using Grails' ORM DSL. But to take advantage of the ORM DSL, your domain class must declare a public property called mapping
and assign a closure to the property (see Listing 3-13).
Listing 3-13. Custom Mapping for the Person Domain Class
class Person {
String firstName
String lastName
Integer age
static mapping = {
id column:'person_id'
firstName column:'person_first_name'
lastName column:'person_last_name'
age column:'person_age'
version false
}
}
The example in Listing 3-13 defines column names for each of the properties and turns off the version
property, which Grails uses for optimistic locking. These are just a couple of the features that the ORM DSL supports.
The default table name for persisting instances of a Grails domain class is the name of the domain class. Person
objects are stored in a person
table and Company
objects are stored in a company
table. If Person
objects need to be stored in a people
table, the ORM DSL allows for that. Listing 3-14 includes the necessary mapping code to store Person
instances in the people
table.
Listing 3-14. A Custom Table Mapping for the Person Domain Class
class Person {
String firstName
String lastName
Integer age
static mapping = {
table 'people'
}
}
We'll cover custom database mapping in more detail in Chapter 17.
Typically an application is not made up of a bunch of disconnected domain classes. More often, domain classes have relationships to one another. Of course, not every domain class has a direct relationship with every other domain class, but it is not common for a domain class to exist in total isolation with no relationship to any other domain class.
Grails provides support for several types of relationships between domain classes. In a one-to-one relationship (the simplest type), each member of the relationship has a reference to the other. The relationship represented in Listing 3-15 is a bidirectional relationship.
Listing 3-15. A One-to-One Relationship Between a Car and an Engine
class Car {
Engine engine
}
class Engine {
Car car
}
In this model, clearly a Car
has one Engine
and an Engine
has one Car
. The entities are peers in the relationship; there is no real "owner." Depending on your application requirements, this might not be exactly what you want. Often a relationship like this really does have an owning side. Perhaps an Engine
belongs to a Car
, but a Car
does not belong to an Engine
. Grails provides a mechanism for expressing a relationship like that, and Listing 3-16 demonstrates how to specify the owning side of it.
Listing 3-16. An Engine Belongs to a Car
class Car {
Engine engine
}
class Engine {
static belongsTo = [car:Car]
}
The value of the belongsTo
property in the Engine
class is a Map
. The key in this map is "car" and the value associated with that key is the Car
class. This property tells Grails that the Car
is the owning side of this relationship and that an Engine
"belongs to" its owning Car
. The key in the map can be named anything—the name does not need to be the same as the owning-class name. However, naming the key that way almost always makes sense. That key represents the name of a property that will be added to the Engine
class, as well as representing the reference back to the owner. The Engine
class in Listing 3-16 has a property called car
of type Car
.
You might encounter situations where a relationship needs an owning side but the owned side of the relationship does not need a reference back to its owner. Grails supports this type of relationship using the same belongsTo
property, except that the value is a Class
reference instead of a Map
. With the approach used in Listing 3-17, the Engine
still belongs to its owning Car
, but the Engine
has no reference back to its Car
.
Listing 3-17. An Engine Belongs to a Car But Has No Reference to Its Owner
class Engine {
static belongsTo = Car
}
One of the implications of having the belongsTo
property in place is that Grails will impose cascaded deletes. Grails knows that an Engine
"belongs to" its owning Car
, so any time a Car
is deleted from the database, its Engine
will be deleted as well.
One-to-many relationships are equally simple to represent in Grails domain classes. Our gTunes application will require several one-to-many relationships, including the relationship between an Artist
and its Albums
and between an Album
and its Songs
. You might say that an Artist
has many Albums
and an Album
has many songs. That "has many" relationship is expressed in a domain class with the hasMany
property (see Listing 3-18).
Listing 3-18. The hasMany Property
class Artist {
String name
static hasMany = [albums:Album]
}
class Album {
String title
static hasMany = [songs:Song]
static belongsTo = [artist:Artist]
}
class Song {
String title
Integer duration
static belongsTo = Album
}
In Listing 3-18, an Artist
has many Albums
and an Album
belongs to its owning Artist
. An Album
also has a reference back to its owning Artist
. An Album
has many Songs
and a Song
belongs to its owning Album
. However, a Song
does not have a reference back to its owning Album
.
The value of the hasMany
property needs to be a Map
. The keys in the map represent the names of collection properties that will be added to the domain class, and the values associated with the keys represent the types of objects that will be stored in the collection property. The Artist
class has a domain property called albums
that will be a collection of Album
objects. The default collection type that Grails will use is a java.util.Set
, which is an unordered collection. Where this is the desired behavior, you don't need to declare the property explicitly. Grails will inject the property for you. If you need the collection to be a List
or a SortedSet
, you must explicitly declare the property with the appropriate type, as shown in Listing 3-19.
Listing 3-19. The Album Class Has a SortedSet of Song Objects
class Album {
String title
static hasMany = [songs:Song]
static belongsTo = [artist:Artist]
SortedSet songs
}
Note For this to work, the Song
class must implement the Comparable
interface. This requirement isn't specific to Grails; it's how standard SortedSet
collections work in Java.
A domain class might represent the owning side of numerous one-to-many relationships. The Map
associated with the hasMany
property might have any number of entries in it, each entry representing another one-to-many-relationship. For example, if an Artist
has many Albums
but also has many Instruments
, you could represent that by adding another entry to the hasMany
property in the Artist
class, as shown in Listing 3-20.
Listing 3-20. Multiple Entries in the hasMany Map
class Artist {
String name
static hasMany = [albums:Album, instruments:Instrument]
}
Grails domain classes can extend other Grails domain classes. This inheritance tree might be arbitrarily deep, but a good domain model will seldom involve more than one or two levels of inheritance.
The syntax for declaring that a Grails domain class extends from another domain class is standard Groovy inheritance syntax, as shown in Listing 3-21.
Listing 3-21. Extending the Person Class
class Person {
String firstName
String lastName
Integer age
}
class Employee extends Person {
String employeeNumber
String companyName
}
class Player extends Person {
String teamName
}
How should these classes map to the database? Should there be separate tables for each of these domain classes? Should there be one table for all types of Person
objects? Grails provides support for both of those solutions. If all Person
objects—including Players
and Employees
— are to be stored in the same table, this approach is known as a table-per-hierarchy mapping. That is, a table will be created for each inheritance hierarchy (see Listing 3-22). Grails imposes table-per-hierarchy mapping as the default for an inheritance relationship.
Listing 3-22. The Person Table Representing a Table-Per-Hierarchy Mapping
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | NULL | |
| age | int(11) | NO | | NULL | |
| first_name | varchar(255) | NO | | NULL | |
| last_name | varchar(255) | NO | | NULL | |
| class | varchar(255) | NO | | NULL | |
| company_name | varchar(255) | YES | | NULL | |
| employee_number | varchar(255) | YES | | NULL | |
| team_name | varchar(255) | YES | | NULL | |
+-----------------+--------------+------+-----+---------+----------------+
Notice that Listing 3-22 includes columns for all the attributes in the Person
class along with columns for all the attributes in all the subclasses. In addition, the table includes a discriminator column called class
. Because this table will house all kinds of Person
objects, the discriminator column is required to represent what specific type of Person
is represented in any given row. The application should never need to interrogate this column directly, but the column is critical for Grails to do its work.
The other type of inheritance mapping is known as table-per-subclass (see Listing 3-23).
Listing 3-23. Table-Per-Subclass Mapping
class Person {
String firstName
String lastName
Integer age
static mapping = {
tablePerHierarchy false
}
}
Table-per-subclass mapping results in a separate table for each subclass in an inheritance hierarchy (see Listing 3-24). To take advantage of a table-per-subclass mapping, the parent class must use the ORM DSL to turn off the default table-per-hierarchy mapping.
Listing 3-24. The Person, Employee, and Player Tables with Table-Per-Subclass Mapping
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | NULL | |
| age | int(11) | NO | | NULL | |
| first_name | varchar(255) | NO | | NULL | |
| last_name | varchar(255) | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
+-----------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| company_name | varchar(255) | YES | | NULL | |
| employee_number | varchar(255) | YES | | NULL | |
+-----------------+--------------+------+-----+---------+-------+
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| team_name | varchar(255) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
Which of these mappings should you use? The answer depends on several factors. One of the consequences of the table-per-hierarchy approach is that none of the subclasses can have nonnullable properties, but because no joins are being executed, queries will perform better. This is because all the subclasses share a table that includes columns for all the properties in all the subclasses. When a Player
is saved to the person
table, the company_name
column would be left null because players don't have a company name. Likewise, when an Employee
is saved to the player
table, the team_name
column would be left null. One of the consequences of using the table-per-subclass approach is that you must pay a performance penalty when retrieving instances of the subclasses because database joins must be executed to pull together all the data necessary to construct an instance.
Grails lets you choose the approach that makes the most sense for your application. Consider your application requirements and typical query use cases. These should help you decide which mapping strategy is right for any particular inheritance relationship. Note that you don't need to apply the same mapping strategy across the entire application. There's nothing wrong with implementing one inheritance relationship using table-per-subclass mapping because you must support nonnullable properties, and implementing some other unrelated inheritance relationship using table-per-hierarchy mapping for performance reasons.
Grails supports the notion of composition, which you can think of as a stronger form of relationship. With that kind of relationship, it often makes sense to embed the "child" inline where the "parent" is stored. Consider a simple relationship between a Car
and an Engine
. If that relationship were implemented with composition, the Engine
would really belong to the Car
. One consequence of that: If a Car
were deleted, its Engine
would be deleted with it (see Listing 3-25).
Listing 3-25. A Composition Relationship Between the Car and Engine Domain Classes
class Car {
String make
String model
Engine engine
}
class Engine {
String manufacturer
Integer numberOfCylinders
}
Normally Car
objects and Engine
objects would be stored in separate tables, and you'd use a foreign key to relate the tables to each other (see Listings 3-26 and 3-27).
Listing 3-26. The Car Table
+-----------+--------------+------+-----+---------+---------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+---------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment|
| version | bigint(20) | NO | | NULL | |
| engine_id | bigint(20) | NO | MUL | NULL | |
| make | varchar(255) | NO | | NULL | |
| model | varchar(255) | NO | | NULL | |
+-----------+--------------+------+-----+---------+---------------+
Listing 3-27. The Engine Table
+---------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | NULL | |
| manufacturer | varchar(255) | NO | | NULL | |
| number_of_cylinders | int(11) | NO | | NULL | |
+---------------------+--------------+------+-----+---------+----------------+
To treat the relationship between those classes as composition, the Car
class must instruct Grails to "embed" the Engine
in the Car
. You do this by defining a public static property called embedded
in the Car
class and assign that property a list of strings that contains the names of all the embedded properties (see Listing 3-28).
Listing 3-28. Embedding the Engine in a Car
class Car {
String make
String model
Engine engine
static embedded = ['engine']
}
With that embedded property in place, Grails knows that the Engine
property of a Car
object should be embedded in the same table with the Car
object. The car
table will now look like Listing 3-29.
Listing 3-29. The Car Table with the Engine Attributes Embedded
+----------------------------+--------------+------+-----+---------+---------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------------+--------------+------+-----+---------+---------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment|
| version | bigint(20) | NO | | NULL | |
| engine_manufacturer | varchar(255) | NO | | NULL | |
| engine_number_of_cylinders | int(11) | NO | | NULL | |
| make | varchar(255) | NO | | NULL | |
| model | varchar(255) | NO | | NULL | |
+----------------------------+--------------+------+-----+---------+---------------+
Automated tests can be an important part of building complex applications and confirming that the system behaves as intended. In particular, testing is an important part of building complex systems with a dynamic language like Groovy. With dynamic languages, developers don't get the same kinds of feedback from the compiler that they might get if they were working with a statically typed language like Java.
For example, in Java if you make a typo in a method invocation, the compiler will let you know that you have made the mistake. The compiler cannot flag that same error when you use Groovy because of the language's dynamic nature and its runtime. With a dynamic language like Groovy, many things are not known until runtime. You must execute the code to learn whether it's correct. Executing the code from automated tests is an excellent way to help ensure that the code is doing what it is supposed to do.
Grails offers first-class support for testing many aspects of your application. In this section, we will look at testing domain classes.
Grails directly supports two kinds of tests: unit tests and integration tests. Unit tests reside at the top of the project in the test/unit/
directory, and integration tests reside in the test/ integration/
directory. You must understand the difference between unit tests and integration tests. Many dynamic things happen when a Grails application starts up. One of the things Grails does at startup is augment domain classes with a lot of dynamic methods such as validate()
and save()
. When you run integration tests, all of that dynamic behavior is available, so a test can invoke the validate()
or save()
method on a domain object even though these methods do not appear in the domain-class source code.
When you run unit tests, however, that full dynamic environment is not fired up, so methods such as validate()
and save()
are not available. Starting up the whole dynamic environment comes at a cost. For this reason, you should run tests that rely on the full Grails runtime environment only as integration tests.
That said, Grails provides advanced mocking capabilities that let you mock the behavior of these methods in a unit test. If you create a domain class using the create-domain-class
command, Grails will create a unit test automatically. If you execute grails create-domain-class Artist
(see Listing 3-30), Grails will create grails-app/domain/Artist.groovy
and test/ unit/ArtistTests.groovy
. Grails is encouraging you to do the right thing—to write tests for your domain classes. If you don't use the create-domain-class
command to create your domain class, you can create the test on your own. Make sure to put the test in the appropriate directory.
Listing 3-30. The Unit Test for the Artist Class, Generated Automatically
class ArtistTests extends grails.test.GrailsUnitTestCase {
void testSomething() {
}
}
As you can see from Listing 3-30, the default unit-test template extends from the parent class grails.test.GrailsUnitTestCase
. The GrailsTestUnitCase
class is a test harness that provides a range of utility methods to mock the behavior of a Grails application. To run the test, invoke the test-app
Grails command from the command line. The test-app
command will run all the unit tests and integration tests that are part of the project. To run a specific test, invoke the test-app
target with an argument that represents the name of the test to run. The name of the test to run should be the test-case name without the "Tests" suffix. For example, execute grails test-app Artist
to run the ArtistTests
test case.
The test-app
target will not only run the tests, but also generate a report including the status of all the tests that were run. This report is a standard JUnit test report, which Java developers know very well. An HTML version of the report will be generated under the project root at test/reports/html/index.html
.
The Song
class in the gTunes application has title
and duration
properties (see Listing 3-31).
Listing 3-31. The Song Domain Class
class Song {
String title
Integer duration
}
The application should consider a nonpositive duration to be an invalid value. The type of the property is java.lang.Integer
, whose valid values include the full range of values in a 32-bit signed int
, including zero and a lot of negative numbers. The application should include a unit test like that shown in Listing 3-32, which asserts that the system should not accept nonpositive durations.
Listing 3-32. The Song Unit Test
class SongTests extends grails.test.GrailsUnitTestCase {
void testMinimumDuration() {
// mock the behavior of the Song domain class
mockDomain(Song)
// create a Song with an invalid duration
def song = new Song(duration: 0)
// make sure that validation fails
assertFalse 'validation should have failed', song.validate()
// make sure that validation failed for the expected reason
assertEquals "min", song.errors.duration
}
}
Notice the call to the mockDomain(Class)
method in Listing 3-32 that provides a mock implementation of the validate()
method on the Song
domain class. Executing grails test-app Song
will run the test. The test should fail initially because it contains no code specifying that 0 is an invalid value for the duration
property. Starting with a failing test like this subscribes to the ideas of Test-Driven Development (TDD). The test represents required behavior, and it will "drive" the implementation to satisfy the requirement.
Adding a simple domain constraint to the Song
class as shown in Listing 3-33 should satisfy the test.
Listing 3-33. The Song Domain Class with a Constraint
class Song {
String title
Integer duration
static constraints = {
duration(min:1)
}
}
With that constraint in place, the unit test should pass. The domain class is written to satisfy the requirements expressed in the test. Specifically, the domain class considers any nonpositive value for duration
to be invalid.
This chapter covered quite a bit of ground by introducing the fundamentals of Grails domain classes. Grails provides slick solutions to common problems like validating domain classes and mapping to a relational database. The GORM technology is responsible for much of that capability. We'll explore GORM in more detail in later chapters, including Chapters 10 and 17.
3. Scott W. Ambler, "The Object-Relational Impedance Mismatch," http://www.agiledata.org/essays/impedanceMismatch.html, 2006.
4. Wikipedia, "Optimistic concurrency control," http://en.wikipedia.org/wiki/Optimistic_concurrency_control.