Chapter     7

Rapid Web Development with Grails

Live in fragments no longer, only connect.

—Edgar Morgan Foster

Grails takes web development to the next level of abstraction. The fact that Java EE was not written with an application level of abstraction led to the development and subsequent popularity of Java frameworks such as Spring, Hibernate, and so on. But most of the Java frameworks take a fragmented approach toward web development. You have to maintain the configuration for each layer. Grails embraces convention over configuration and wraps these powerful frameworks with a layer of abstraction via the Groovy language, thus providing a complete development platform that allows you to take full advantage of Java and the JVM.

This chapter will reach under the covers of the Grails machine and look at its parts: its wheels and gears all moving in a coordinated motion, its workability, its leading-edge engine, and its underlying form. It will take a closer look at the interactions in the Grails ecosystem. It will show how controllers handle, manage, direct, and orchestrate the logical flow of the application and how they handle requests, redirect requests, execute and delegate actions, or render views as the need arises. It will explore views and unravel how Grails uses SiteMesh, the page decoration framework, to give a consistent look to pages, as well as how views draw on Grails’ built-in tags and dynamic tags in its tag library to create well-formed markup and promote a clean separation of concerns. It’s quite a machine.

Grails Features

Grails is a request-based, MVC, open-source web development framework. More than just that, though, it is a complete development platform in which everything runs on top of the robust Java and Java EE platforms as illustrated in Figure 7-1. It leverages the existing popular Java frameworks, and it includes a web container, database, build system, and test harness that exploit the dynamism of the Groovy language.

9781430259831_Fig07-01.jpg

Figure 7-1. Grails platform

Grails provides best practices, such as convention over configuration, and unit testing using frameworks such as Spring, Hibernate, and SiteMesh, to name a few. This section will highlight some of the important best practices.

Convention over Configuration

Rather than configuration, Grails gives precedence to convention. Convention over configuration, in simple terms, means writing configuration code only when you deviate from the convention. These ingenious conventions correspond to the directory structure; Grails brings into play the name and location of the files instead of relying on explicit configuration via the wiring of XML configuration files. This means if you create a class following the Grails conventions, Grails will wire it into Spring or treat it as a Hibernate entity. If you create a new domain class called Book, Grails will automatically create a table called book in the database. By using the convention-over-configuration paradigm, Grails can envisage a component from its name and its location in the directory structure. One immediate consequence of this, other than speeding up application development, is that you have to configure a particular aspect of a component only when that configuration deviates from the standard.

Scaffolding

The Grails scaffolding generates an application’s CRUD functionality from the domain classes, at either runtime or development time. The generated application consists of the controller and GSP views associated with the domain class. The scaffolding also generates the database schema, including tables for each of the domain classes.

Object-Relational Mapping

Grails includes a powerful object-relational mapping (ORM) framework called Grails Object Relational Mapping (GORM). Like most ORM frameworks, GORM maps objects to relational databases; but unlike other ORM frameworks, GORM is based on a dynamic language. Therefore GORM can inject the CRUD methods right into the class without having to implement them or inherit them from persistent superclasses.

Note  ORM is a way to map objects from the OO world onto tables in a relational database and provides an abstraction above SQL.

Plug-Ins

Instead of providing out-of-the-box solutions for every possible requirement, Grails offers a plug-in architecture, and you can find plug-ins for a plethora of functionalities.

Unit Testing

For improving the quality of the deliverables, Grails provides unit tests, integration tests, and functional tests for automating the web interface.

Integrated Open Source

Grails integrates industry-standard and proven open source frameworks, several of which are briefly described in this section.

Table 7-1 illustrates the frameworks that Grails leverages.

Table 7-1. Frameworks That Grails Leverages

Integrated Open Source Technology

Description

Ajax frameworks

Grails ships with the jQuery library but also provides support for other frameworks such as Prototype, Dojo, Yahoo UI, and the Google Web Toolkit through the plug-in system.

Hibernate

Hibernate is an ORM framework that provides the foundation for GORM.

H2

Grails uses the in-memory H21 database and enables the H2 database console in development mode (at the URI /dbconsole) so that the in-memory database can be easily queried from the browser.

Spring

Spring Framework provides an application level of abstraction on top of the Java EE API. Grails developers can build an application that internally uses Spring and Hibernate without knowing these frameworks. Grails abstracts most of the details of these frameworks from Grails developers.

SiteMesh

SiteMesh2 is a layout-rendering framework that implements the Decorator design pattern for rendering HTML with headers, footers, and navigation elements. Grails abstracts most of the SiteMesh details from the Grails developer.

Tomcat

By default, Grails uses an embedded Tomcat container.

Installing Grails

Before installing Grails, you will need as a minimum a Java Development Kit (JDK) version 1.6 or newer. Download the appropriate JDK for your operating system, run the installer, and then set up an environment variable called JAVA_HOME pointing to the location of this installation.

Note  A JDK is required in your Grails development environment. A JRE is not sufficient.

The first step to getting up and running with Grails is to install the distribution. To do so, follow these steps:

  1. Download a binary distribution of Grails from http://grails.org/ and extract the resulting ZIP file to a location of your choice.
  2. Set the GRAILS_HOME environment variable to the location where you extracted the ZIP file.
    • On Unix/Linux-based systems, this is typically a matter of adding something like the following to your profile: export GRAILS_HOME=/path/to/grails.
    • On Windows, this is typically a matter of setting an environment variable under My Computer/Advanced/Environment Variables.
  3. Then add the bin directory to your PATH variable.
    • On Unix/Linux-based systems, this can be done by adding export PATH="$PATH:$GRAILS_HOME/bin" to your profile.
      • On Windows, this is done by modifying the PATH environment variable under My Computer/Advanced/Environment Variables.

If Grails is working correctly, you should now be able to type grails -version in the terminal window and see output similar to this:

E:>grails -version
Grails version: 2.2.4

Hello World Application

In this section, you will create your first Grails web application. To create a Grails application, you need to familiarize yourself with the usage of the grails command:

grails [command name]

Run create-app to create an application.

grails create-app helloworld

This will create a new directory called helloworld inside the current one that contains the project, in other words, your workspace. Navigate to this directory in your console:

cd helloworld

Change into the helloworld directory you just created and start the Grails interactive console by typing the grails command.

grails2-workspacehelloworld>grails

This will download several resources, and then you should see a prompt, as illustrated in Figure 7-2.

9781430259831_Fig07-02.jpg

Figure 7-2. Grails interactive console

What we want is a simple page that just prints the message “Hello World” to the browser. In Grails, whenever you want a new page, you create a new controller action for it. Since we don’t yet have a controller, let’s create one now with the create-controller command.

grails> create-controller hello

The previous command will create a new controller called HelloController.groovy as illustrated in Listing 7-1, in the grails-app/controllers/helloworld directory.

Listing 7-1. HelloController.groovy

package helloworld
 
class HelloController {
 
    def index() { }
}

We now have a controller, so let’s add an action to generate the “Hello World” page. The code looks like Listing 7-2.

Listing 7-2. Modifying the Index Action

def index() { render "Hello World" } }

The action is simply a method. In this particular case, it calls a special method provided by Grails to render the page.

To see your application in action, you need to start up a server with another command called run-app.

grails> run-app

This will start an embedded server on port 8080 that hosts your application. You should now be able to access your application at the URL http://localhost:8080/helloworld/. The result will look like Figure 7-3.

9781430259831_Fig07-03.jpg

Figure 7-3. Welcome screen of Grails

This is the Grails introduction page that is rendered by the grails-app/view/index.gsp file. It detects the presence of your controllers and provides links to them. Click the HelloController link to see our custom page containing the text “Hello World.” You have your first working Grails application.

Note  In Figure 7-3 there is a link to the Dbdoc controller. Clicking this link will produce an error message, as the controller is not implemented. The purpose of the DbdocController is to generate static HTML files to view change log information. You can enable it from conf/Config.groovy by setting dbDocController.enabled = true

Bookstore Application

In this chapter, you will be learning to leverage the Grails conventions and scaffolding to create a simple but functional version of the bookstore application. This initial version of the application will not be production-ready, however; the objective of this application is to show you how, with scaffolding, you can render a CRUD web application with almost no code other than your domain class code. In addition, Grails will generate a database schema and populate a database with the schema when the application is run.

Creating the Bookstore Application

To create the bookstore application, you need to execute the create-app target using an optional project name on the command line, as shown here:

>grails create-app bookstore

The entire line in the preceding command line is a command, where create-app is a target. A target is a specific task that you want Grails to execute.

Note  Using the help command yields a list of available targets: >grails help.

If you don’t supply the project name when using create-app, you will be prompted for one.

After the create-app target has run, you have a new directory matching the name of your project. This is the root of your new project, and you must make all subsequent Grails command-line calls from within this directory. It’s a good idea to use the cd command to get into the directory now so you don’t forget. Within the new project directory, you will find a structure matching the directory structure shown in Figure 7-4.

9781430259831_Fig07-04.jpg

Figure 7-4. Directory structure of the bookstore application

Instead of creating the application from the command line, you can use the IDE of your choice. We recommend the Groovy/GrailsTool Suite (GGTS), which you can download from www.springsource.org/downloads/sts-ggts. This book uses the latest version, GGTS 3.0. GGTS provides the best Eclipse-powered development environment for building Groovy and Grails applications. GGTS provides support for the latest versions of Groovy and Grails and comes on top of the latest Eclipse releases. Figure 7-5 shows how to configure Grails in GGTS. Under Preferences, click Grails and then the Add button. In the window Configure Grails Installation, browse for Grails by clicking the Browse button, which opens the Grails Installation directory window. Select your Grails installation directory and click OK.

9781430259831_Fig07-05.jpg

Figure 7-5. Configuring GGTS with Grails

Grails is now added to the build path, as illustrated in Figure 7-6. Click OK. Now the GGTS is configured with Grails, and you can create a Grails project.

9781430259831_Fig07-06.jpg

Figure 7-6. Grails in the build path

To create a new project in GGTS, use the menu option File image New image Grails Project, as illustrated in Figure 7-7.

9781430259831_Fig07-07.jpg

Figure 7-7. Creating a new project

Since you have already created the project from the command line, you can import the created project in GGTS. Select Existing Projects into Workspace in the Import window, as illustrated in Figure 7-8.

9781430259831_Fig07-08.jpg

Figure 7-8. Importing an existing project

Click Next. Select the root directory of the project, as illustrated in Figure 7-9.

9781430259831_Fig07-09.jpg

Figure 7-9. Selecting a directory to search for existing Eclipse projects

Click Finish. Figure 7-10 illustrates the directory structure of the bookstore application in GGTS.

9781430259831_Fig07-10.jpg

Figure 7-10. Directory structure of the bookstore application

Running the Application

At this point, you have a functional application that you can run and access through a web browser. It does not do much yet, but running it now will enable you to get instant feedback as you add domain and controller classes.

To run a Grails application, execute the run-app target from your project root directory, as shown here:

> grails run-app

The output of executing the run-app target is shown here:

Server running. Browse to http://localhost:8080/bookstore

Accessing the application at the url http://localhost:8080/bookstore displays the Welcome screen as illustrated in Figure 7-11.

9781430259831_Fig07-11.jpg

Figure 7-11. The Welcome screen of the bookstore application

To run the application in GGTS, click Grails Command History, as highlighted in Figure 7-12. Type run-app in the command window, and hit Enter. Or you can also right-click the project in the IDE and select Run As image Grails (run app).

9781430259831_Fig07-12.jpg

Figure 7-12. Grails command history

Creating the Controller

A controller handles requests and creates or prepares responses. A controller can generate the response directly or delegate to a view. To create a controller class, use the Grails create-controller target. This creates a new Grails controller class in the grails-app/controllers directory, as well as the unit test for the controller class in test/unit. It also creates a grails-app/views/<controller name> directory if it doesn’t exist already.

To create the BookController class, you need to execute the create-controller target using an optional class name, as shown here:

>grails create-controller book

If you don’t supply the class name, you are prompted for one. The output of executing the create-controller target is shown here:

| Created file grails-app/controllers/bookstore/BookController.groovy
| Created file grails-app/views/book
| Created file test/unit/bookstore/BookControllerTests.groovy

Notice that when running the create-controller with the optional class name, you can leave the class name in lowercase, and Grails will automatically uppercase it for you so that it follows the standard Groovy class naming convention.

To create the controller using GGTS, click the controllers in the bookstore project hierarchy, and then use New image Controller. Type Book in the Grails Command Wizard, as shown in Figure 7-13.

9781430259831_Fig07-13.jpg

Figure 7-13. Creating a controller using GGTS

Click Finish. GGTS will generate the controller and tests, as illustrated in the following output:

Loading Grails 2.2.4
| Environment set to development.....
| Created file grails-app/controllers/bookstore/BookController.groovy
| Created file grails-app/views/book
| Compiling 1 source files.....
| Created file test/unit/bookstore/BookControllerTests.groovy

Listing 7-3 illustrates the generated controller.

Listing 7-3. BookController Generated by Grails

package bookstore
 
class BookController {
 
    def index() {
}
}

Now refresh the browser and you can see this controller on the Welcome screen as illustrated in the Figure 7-14.

9781430259831_Fig07-14.jpg

Figure 7-14. Welcome screen of the bookstore application

Modify the code of index(){}, as illustrated in Listing 7-4.

Listing 7-4. Modifying the Index Action

    def index() {
render "book list"
}

Now you can click the BookController link in Figure 7-14, and you will get the simple textual response illustrated in Figure 7-15.

9781430259831_Fig07-15.jpg

Figure 7-15. Simple textual response

Testing the Controller

Listing 7-5 illustrates the BookControllerTests test generated by Grails.

Listing 7-5. BookControllerTests Generated by Grails

package bookstore
 
import grails.test.mixin.*
import org.junit.*
 
/**
 * See the API for {@link grails.test.mixin.web.ControllerUnitTestMixin} for usage instructions
 */
@TestFor(BookController)
class BookControllerTests {
 
void testSomething() {
       fail "Implement me"
}
}

Modify testSomething() as shown in Listing 7-6.

Listing 7-6. Adding the Assertion

1.void testSomething() {
2.controller.index()
3.assert "book list" == response.text
4.}

Now run the test through the Run as image Grails command (test-app), as illustrated in Figure 7-16.

9781430259831_Fig07-16.jpg

Figure 7-16. The test-app command in Grails

On running the command test-app, Grails runs the test, as shown in the following output:

| Loading Grails 2.2.4
| Configuring classpath.
| Environment set to test.....
| Running 1 unit test... 1 of 1
| Completed 1 unit test, 0 failed in 3444ms
| Packaging Grails application.....
| Packaging Grails application.....
| Tests PASSED - view reports in E:ModernJavagrails2-workspaceookstore arget est-reports

You can generate the report illustrated in Figure 7-17 from the path shown in the last line of the previous output.

9781430259831_Fig07-17.jpg

Figure 7-17. Test report: passed test

Note  In this way you can augment the unit test for your Controller in the ControllerTests generated by Grails.

The test passed because in Listing 7-6 the assertion made on line 3 was correct. Now replace line 3 in Listing 7-6, as shown here:

assert "xyz" == response.text

This is an incorrect assertion, because the simple textual response shown in Figure 7-15 is “book list” and not “xyz.” So this test should fail. To see how Grails reports the failed test, fail the test by replacing testSomething() with Listing 7-7.

Listing 7-7. Replacing the Test with an Incorrect Assertion

void testSomething() {
controller.index()
assert "xyz" == response.text
}

On running the command test-app, Grails runs the test as shown in the following output:

| Loading Grails 2.2.4
| Configuring classpath.
| Environment set to test.....
| Compiling 1 source files.
| Running 1 unit test... 1 of 1
| Failure:  testSomething(bookstore.BookControllerTests)
|  Assertion failed:
 
assert "xyz" == response.text
             |  |        |
             |  |        book list
             |  org.codehaus.groovy.grails.plugins.testing.GrailsMockHttpServletResponse@14cf61d
             false
 
at bookstore.BookControllerTests.testSomething(BookControllerTests.groovy:16)
| Completed 1 unit test, 1 failed in 3210ms
| Packaging Grails application.....
| Packaging Grails application.....
| Tests FAILED  - view reports in E:ModernJavagrails2-workspaceookstore arget est-reports

You can generate the report illustrated in Figure 7-18 from the path shown in the last line of the previous output.

9781430259831_Fig07-18.jpg

Figure 7-18. Test report: failed test

Creating a Domain Class

At this point, the application we created doesn’t really do anything; it just renders a simple textual response. We will move on by creating a domain class. To create a domain class, use the Grails create-domain-class target. This creates a new Grails domain class in the grails-app/domain directory, as well as a unit test for the domain class in test/unit.

To create the Book domain class, you need to execute the create-domain-class target using an optional class name, as shown here:

> grails create­domain-class book

If you don’t supply the class name, you are prompted for one.

Notice that when running the create-domain-class target with the optional class name, you can leave the class name in lowercase, and Grails will automatically uppercase it for you so that it follows the standard Groovy class naming convention.

To create the domain class using GGTS, click “domain” in the project hierarchy and then use New image Domain Class, as illustrated in Figure 7-19.

9781430259831_Fig07-19.jpg

Figure 7-19. Creating a domain class using GGTS

When you click Domain Class in Figure 7-19, the Grails Command Wizard window is displayed, as illustrated in Figure 7-20.

9781430259831_Fig07-20.jpg

Figure 7-20. Creating a domain class using GGTS

Enter the name of the domain class in the name field and click Finish. Grails creates the Book domain class and BookTests, as shown in the following output:

Loading Grails 2.2.4
| Environment set to development.....
| Created file grails-app/domain/bookstore/Book.groovy
| Compiling 1 source files.....
| Created file test/unit/bookstore/BookTests.groovy

Listing 7-8 illustrates the Book class generated by Grails.

Listing 7-8. Book Domain Class Generated by Grails

package bookstore
 
class Book {
 
    static constraints = {
    }
}

The Book class in Listing 7-8 is empty. Now you can complete this domain class as illustrated in Listing 7-9.

Listing 7-9. Book Domain Class

1.package bookstore
2.
3.class Book {
4.String bookTitle
5.Long price
6.Long isbn
7.
8.static constraints = {
9.bookTitle(blank:false)
10.price(blank:false)
11.}
12.String toString() {
13.bookTitle
14.}
15.}

Constraints in lines 8 to 11 in Listing 7-9 provide Grails with a declarative mechanism for defining validation rules. Table 7-2 illustrates the constraints available with Grails.

Table 7-2. Constraints Available with Grails

Constraint

Description

blank

Validates that a String value is not blank

creditCard

Validates that a String value is a valid credit card number

email

Validates that a String value is a valid e-mail address

inList

Validates that a value is within a range or collection of constrained values

matches

Validates that a String value matches a given regular expression

max

Validates that a value does not exceed the given maximum value

maxSize

Validates that a value’s size does not exceed the given maximum value

min

Validates that a value does not fall below the given minimum value

minSize

Validates that a value’s size does not fall below the given minimum value

notEqual

Validates that a property is not equal to the specified value

nullable

Allows a property to be set to null; defaults to false

range

Uses a Groovy range to ensure that a property’s value occurs within a specified range

scale

Sets the desired scale for floating-point numbers (i.e., the number of digits to the right of the decimal point)

size

Uses a Groovy range to restrict the size of a collection or number or the length of a String

unique

Constrains a property as unique at the database level

url

Validates that a String value is a valid URL

validator

Adds custom validation to a field

Scaffolding

Scaffolding lets you autogenerate a whole application for a given domain class including views and controller actions for CRUD operations. Scaffolding can be either static or dynamic; both types generate the same code. The main difference is that in static scaffolding, the generated code is available to the user before compilation and thus can be easily modified if necessary. In dynamic scaffolding, however, the code is generated in memory at runtime and is not visible to the user. In the section that follows, you will learn about both dynamic and static scaffolding.

Dynamic Scaffolding

As explained earlier, the dynamic scaffolding generates controller actions and views for CRUD applications at runtime. To dynamically scaffold a domain class, you need a controller. You created a controller (BookController) in Listing 7-3. To use the dynamic scaffolding, change the index action to a scaffold property and assign it the domain class, as shown in Listing 7-10. This causes List Page, Create Page, Edit Page, and Show Page views, as well as delete functionality, to be generated for the specified domain class.

Listing 7-10. Dynamic Scaffolding–Enabled BookController

package bookstore
 
class BookController {
 
    static scaffold = Book
}

After changing BookController to look like Listing 7-10, execute the run-app target.

The output of executing the run-app target is shown here:

| Loading Grails 2.2.4
| Configuring classpath.
| Environment set to development.....
| Packaging Grails application.....
| Running Grails application
| Server running. Browse tohttp://localhost:8080/bookstore

Click the BookController link on the welcome page.

Clicking the BookController link brings you to the Book List view shown in Figure 7-21.

9781430259831_Fig07-21.jpg

Figure 7-21. Book List view

You can create or add a new book by clicking New Book. Figure 7-22 shows the screen for creating a new book. Figure 7-22 also shows validation in action for which you did not have to write any code. The code for this validation was included in the domain class Book, as illustrated in Listing 7-9 on lines 8 to 11.

9781430259831_Fig07-22.jpg

Figure 7-22. Create view with validation in action

Figure 7-23 illustrates creating a new book by fulfilling all validations.

9781430259831_Fig07-23.jpg

Figure 7-23. Create view

Figure 7-24 illustrates the newly created book.

9781430259831_Fig07-24.jpg

Figure 7-24. Show view

You can edit this newly created book, delete it, or add another book. You can edit the created book by clicking Update. Figure 7-25 illustrates the Edit view.

9781430259831_Fig07-25.jpg

Figure 7-25. Edit view

Figure 7-26 illustrates the updated book.

9781430259831_Fig07-26.jpg

Figure 7-26. Show view with updated message

You can add a new book by clicking New Book. Figure 7-27 illustrates a list of books added in this manner.

9781430259831_Fig07-27.jpg

Figure 7-27. List view with list of added books

Static Scaffolding

Static scaffoldingprovides an excellent learning tool to help you familiarize yourself with the Grails framework and how everything fits together. Now, it is the time to see static scaffolding in action as a learning tool. There is no difference in the domain class for both dynamic and static scaffolding. For quick reference, the Book class is shown in Listing 7-11.

Listing 7-11. Book Domain Class

package bookstore
 
class Book {
String bookTitle
Long price
Long isbn
 
static constraints = {
bookTitle(blank:false)
price(blank:false)
}
String toString() {
bookTitle
}
}

Static scaffolding differs from dynamic scaffolding in the way that views and controllers are generated. The domain class remains the same in both cases. However, in dynamic scaffolding, you need the controller to indicate to Grails that you need the dynamic scaffolding to generate the application for you. If you want Grails to generate the application through static scaffolding, you have to use the command in Listing 7-12.

Listing 7-12. Command for generating the application through static scaffolding

>grails generate-all bookstore.Book

Once you run this command from the command line or GGTS, Grails generates the application as shown in the following output:

Loading Grails 2.2.4
| Configuring classpath.
| Environment set to development.....
| Packaging Grails application.....
| Packaging Grails application.....
| Generating views for domain class bookstore.Book
| Generating controller for domain class bookstore.Book
| Finished generation for domain class bookstore.Book

If we run the application now, we will have a complete CRUD application. This generate-all command generates one controller (BookController) and four views for our domain class Book and generates one unit test for our controller, BookControllerTest. These files, which give us a complete CRUD application, serve as stubs to which we can add custom code. Let’s take a closer look at the code we’ve generated. We begin with the BookController illustrated in Figure 7-28.

9781430259831_Fig07-28.jpg

Figure 7-28. BookController

The first thing you will notice in the BookController code illustrated in Figure 7-28 is that a Grails controller is a plain Groovy class that does not extend any class or implement any interface. The next thing you will notice is that the BookController has eight actions.

  • Create action
  • Delete action
  • Edit action
  • Index action
  • List action
  • Save action
  • Show action
  • Update action

These actions are the closure properties of the controller. All the work in the controller is done in the action. Every closure declared in a controller is an action and can be accessed via a URL that, by default, is mapped to controller actions. The first part of the URL represents the controller name, and the second part of the URL represents the action name. In the sections that follow, you will look deeper into each of these actions, but before that, it is necessary to understand how to exit a controller action. There are three options to properly exit a controller action.

  • Calling the render() method
  • Calling the redirect() method
  • Returning a model or null

In the section that follows, you will look into these three options before probing each of the actions in the BookController shown in Figure 7-28.

Calling the render( ) Method

The first option to exit a controller action is to call the render() method to render a view or a textual response. For the sake of understanding how render() method works, Listing 7-13 shows a simple Grails controller that, when invoked, greets you.

Listing 7-13. Rendering a Textual Response

package chapter5
class HelloController {
def index() {
render 'hello'
}
defshow(){}
def someOtherAction(){}
}

As illustrated in Listing 7-13, the controller, when invoked with a request to /hello/index, will execute an index() action defined in the controller, and the index() action will render a textual response, using the render() method. The full URL to invoke the index() action in the HelloController is http://localhost:8080/chapter5/hello/index. As shown in the listing, there can be any number of actions in a controller.

If you specify the view name in the render() method, as shown in Listing 7-14, Grails assumes you mean a view at the location grails-app/views/hello/hello.gsp and renders a view called hello.

Listing 7-14. Rendering a View

class HelloController {
...
def show() {
render view: "hello"
}
...
}

Calling the redirect( ) Method

The second option to exit a controller action is to call the redirect( ) method to issue an HTTP redirect to another URL. Grails provides all controllers with a redirect( ) method that accepts a Map as an argument. The Map should contain all the information that Grails needs to carry out the redirect, including the name of the action to redirect to.

In addition, the Map can contain the name of the controller to redirect to. Listing 7-15 shows a standard redirect from the first action to the second action within the same controller.

Listing 7-15. Redirecting to an Action in the Same Controller

class HelloController {
def first() {
redirect action: "second"
}
def second() {
...
}
}

If the redirect is for an action in another controller, you must specify the name of the other controller. Listing 7-16 demonstrates how to redirect to an action in another controller.

Listing 7-16. Redirecting to an Action in Another Controller

class HelloController {
def first() {
redirect action: "second", controller: "other"
}
}

In Listing 7-16, the first() action in HelloController redirects to the second() action in the Other Controller.

Returning a Model

The third option to exit a controller action is either to return a model that is a Map containing data, as illustrated in Listing 7-17.

Listing 7-17. Returning the Model

class HelloController {
def show() {
    [user: User.get(params.id)]
}
}

Grails will attempt to render a view with the same name as the action. It will look for this view in a directory named after the base name of the controller. In Listing 7-17, returning from the show() action of the HelloController will cause Grails to render the view /views/hello/show.gsp.

Now you know how the action of the controller can be called and how it can be exited. Fortified with this knowledge, take a look at Figure 7-28 and let’s start investigating each action one by one. First let’s get the allowedMethods property out of the way.

static allowedMethods = [save: "POST", update: "POST", delete: "POST"]

The allowedMethods property provides a simple declarative syntax to specify which HTTP methods are allowed for your controller actions. By default, all request methods are allowed for all controller actions. The allowedMethods property is optional and needs to be defined only if the controller has actions that need to be restricted to certain request methods. This property in the BookController specifies that only save, update, and delete can be POST methods.

Index Action

The index() action is the default action that is called when you navigate to the BookController. By default, this action just redirects to the list() action using the redirect() method explained earlier as illustrated in Listing 7-18.

Listing 7-18. The Index Action of the BookController

def index() {
        redirect(action: "list", params: params)
    }

The redirect() method issues an HTTP redirect to a URL constructed from these parameters. If the action is not specified, the index() action will be used. The params hold request parameters, if any.

List Action

Listing 7-19 illustrates the list() action of the BookController.

Listing 7-19. The List Action of the BookController

def list(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        [bookInstanceList: Book.list(params), bookInstanceTotal: Book.count()]
    }

The first line of the list closure is working with the params property, which is a Map containing all the parameters of the incoming request.

The last line returns a Map with two elements: bookInstanceList and bookInstanceTotal. The bookInstanceList is loaded with a call to Book.list( ). The list( ) is being passed the params Map, from which it will pull any parameters that it can use. The bookInstanceTotal is loaded with Book.count().The use of the bookInstanceTotal will be mentioned in the “List View” section later. The list() action renders the list view using the data in the Map that’s returned from this action.

Create Action

Listing 7-20 illustrates the create() action of the BookController.

Listing 7-20. The Create Action of the BookController

def create() {
        [bookInstance: new Book(params)]
    }

The create() action creates a new Book instance and then assigns the params to bookInstance’s property because it will be used later, as explained in the “Save Action” section discussed next in Listing 7-21. Then it returns that instance in a Map with the key of bookInstance. Finally, it renders the create view.

Save Action

Listing 7-21 illustrates the save() action of the BookController.

Listing 7-21. The Save Action of the BookController

def save() {
        def bookInstance = new Book(params)
        if (!bookInstance.save(flush: true)) {
            render(view: "create", model: [bookInstance: bookInstance])
            return
        }
 
        flash.message = message(code: 'default.created.message', args: [message(code: 'book.label', default: 'Book'), bookInstance.id])
        redirect(action: "show", id: bookInstance.id)
    }

If there are errors, the user is redirected to the create() action. For this redirect to the create() action, params was assigned to the bookInstance’s property, as mentioned in the create() action earlier in Listing 7-20. If there are no errors, the show view is rendered with the newly created instance.

Show Action

Listing 7-22 illustrates the show() action of the BookController.

Listing 7-22. The Show Action of the BookController

def show(Long id) {
        def bookInstance = Book.get(id)
        if (!bookInstance) {
            flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), id])
            redirect(action: "list")
            return
        }
 
        [bookInstance: bookInstance]
    }

The show() action expects an id parameter. The first line of the show() action calls the Book.get( ) method to retrieve the Book referred to by the id parameter. If no Book instance exists with the id passed in, an error message is stored in the flash scope, and the user is redirected to the list view.

If a Book instance is found with the id passed in, it is returned in a Map with the key of bookInstance, and the show() action will render the show view.

Edit Action

Listing 7-23 illustrates the edit() action of the BookController.

Listing 7-23. The Edit Action of the BookController

def edit(Long id) {
        def bookInstance = Book.get(id)
        if (!bookInstance) {
            flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), id])
            redirect(action: "list")
            return
        }
 
        [bookInstance: bookInstance]
    }

The edit() action loads the necessary data that will be used during editing and passes it to the edit view. The edit() action is very much the same as the show() action. The name of the edit() action, edit, is used to render the edit view.

Update Action

The update() action is called when changes from the edit view are submitted. Listing 7-24 illustrates the update() action of the BookController.

Listing 7-24. The Update Action of the BookController

def update(Long id, Long version) {
        def bookInstance = Book.get(id)
        if (!bookInstance) {
            flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), id])
            redirect(action: "list")
            return
        }
 
        if (version != null) {
            if (bookInstance.version > version) {
                bookInstance.errors.rejectValue("version", "default.optimistic.locking.failure",
                          [message(code: 'book.label', default: 'Book')] as Object[],
                          "Another user has updated this Book while you were editing")
                render(view: "edit", model: [bookInstance: bookInstance])
                return
            }
        }
 
        bookInstance.properties = params
 
        if (!bookInstance.save(flush: true)) {
            render(view: "edit", model: [bookInstance: bookInstance])
            return
        }
 
        flash.message = message(code: 'default.updated.message', args: [message(code: 'book.label', default: 'Book'), bookInstance.id])
        redirect(action: "show", id: bookInstance.id)
    }

The update() action tries to retrieve a Book instance with the id parameter. The id is provided from the edit view. If an instance is found, an optimistic concurrency check is performed. If there are no errors, all the values from the edit view are assigned to the appropriate property of the Book instance, including any necessary data conversion.

bookInstance.properties = params

If both of those steps are successful, a “success” message is stored in flash, and the user is directed to the show view. If either step fails, a “failure” message is stored in flash, and the user is directed back to the edit view.

Delete Action

The delete() action is available, by default, in the edit and show views. Listing 7-25 illustrates the delete() action of the BookController.

Listing 7-25. The Delete Action of the BookController

def delete(Long id) {
     def bookInstance = Book.get(id)
     if (!bookInstance) {
         flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), id])
         redirect(action: "list")
         return
     }
 
     try {
         bookInstance.delete(flush: true)
         flash.message = message(code: 'default.deleted.message', args: [message(code: 'book.label', default: 'Book'), id])
         redirect(action: "list")
     }
     catch (DataIntegrityViolationException e) {
         flash.message = message(code: 'default.not.deleted.message', args: [message(code: 'book.label', default: 'Book'), id])
         redirect(action: "show", id: id)
     }
}

The delete() action attempts to retrieve a Book instance and redirects to the list view if it can’t find one. If an instance is found, a try/catch block is entered, where deletion of the instance is tried. If the deletion is successful, a message is stored in flash and redirected to the list view. If there is an exception, a different message is stored in flash and redirected to the show view.

Now that you have seen all the actions generated in the BookController for the Book class, let’s examine the views generated by static scaffolding for the Book class.

Grails Views

Grails uses Groovy Server Pages (GSP) for its view layer. Grails also uses SiteMesh, the page decoration framework, to help with page layout. SiteMesh merges each of the .gsp files into a file called main.gsp to give a consistent look to all the pages. You will begin the generated views with main.gsp, which can be found in viewslayouts, followed by the four views generated for the Book class: list.gsp, show.gsp, create.gsp, and edit.gsp. Listing 7-26 illustrates main.gsp.

Listing 7-26. main.gsp

1.<!DOCTYPE html>
2.<!--[if lt IE 7 ]><html lang="en" class="no-js ie6"><![endif]-->
3.<!--[if IE 7 ]><html lang="en" class="no-js ie7"><![endif]-->
4.<!--[if IE 8 ]><html lang="en" class="no-js ie8"><![endif]-->
5.<!--[if IE 9 ]><html lang="en" class="no-js ie9"><![endif]-->
6.<!--[if (gt IE 9)|!(IE)]><!--><html lang="en" class="no-js"><!--<![endif]-->
7.<head>
8.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
9.<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
10.<title><g:layoutTitle default="Grails"/></title>
11.<meta name="viewport" content="width=device-width, initial-scale=1.0">
12.<link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon">
13.<link rel="apple-touch-icon" href="${resource(dir: 'images', file: 'apple-touch-icon.png')}">
14.<link rel="apple-touch-icon" sizes="114x114" href="${resource(dir: 'images', file: 'apple-touch-icon-retina.png')}">
15.<link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}" type="text/css">
16.<link rel="stylesheet" href="${resource(dir: 'css', file: 'mobile.css')}" type="text/css">
17.<g:layoutHead/>
18.<r:layoutResources />
19.</head>
20.<body>
21.<div id="grailsLogo" role="banner"><a href="http://grails.org"><imgsrc="${resource(dir: 'images', file: 'grails_logo.png')}" alt="Grails"/></a></div>
22.<g:layoutBody/>
23.<div class="footer" role="contentinfo"></div>
24.<div id="spinner" class="spinner" style="display:none;"><g:message code="spinner.alt" default="Loading&hellip;"/></div>
25.<g:javascript library="application"/>
26.<r:layoutResources />
27.</body>
28.</html>
  • Lines 1 to 6: The main.gsp page starts with <!doctype html>. This is an HTML5 doc type. Grails supports HTML5 out of the box.
  • Line 10: <g:layoutTitle> is used in layouts to render the contents of the title tag of the decorated page. The <g:layoutTitle> tag substitutes the <title> from the view that is being merged and links it in a style sheet and favicon that will be used by all views.
  • Line 17: The <g:layoutHead> tag merges in the contents of the target view’s <head> section. <g:layoutHead> is used in layouts to render the contents of the head tag of the decorated page.
  • Line 22: <g:layoutBody> is used in layouts to output the contents of the body tag of the decorated page. The <g:layoutBody> tag merges in the <body> contents of the target view.
  • Line 25: <g:javascript> includes JavaScript libraries and scripts and provides a shorthand for inline JavaScript. Specifying a library tells the Ajax tags which JavaScript provider to use.

The List View

The list View is illustrated in Listing 7-27.

Listing 7-27. list.gsp

1.<%@ page import="bookstore.Book" %>
2.<!DOCTYPE html>
3.<html>
4.<head>
5.<meta name="layout" content="main">
6.<g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
7.<title><g:message code="default.list.label" args="[entityName]" /></title>
8.</head>
9.<body>
10.<a href="#list-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
11.<div class="nav" role="navigation">
12.<ul>
13.<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
14.<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
15.</ul>
16.</div>
17.<div id="list-book" class="content scaffold-list" role="main">
18.<h1><g:message code="default.list.label" args="[entityName]" /></h1>
19.<g:if test="${flash.message}">
20.<div class="message" role="status">${flash.message}</div>
21.</g:if>
22.<table>
23.<thead>
24.<tr>
25.
26.<g:sortableColumn property="bookTitle" title="${message(code: 'book.bookTitle.label', default: 'Book Title')}" />
27.
28.<g:sortableColumn property="price" title="${message(code: 'book.price.label', default: 'Price')}" />
29.
30.<g:sortableColumn property="isbn" title="${message(code: 'book.isbn.label', default: 'Isbn')}" />
31.
32.</tr>
33.</thead>
34.<tbody>
35.<g:each in="${bookInstanceList}" status="i" var="bookInstance">
36.<tr class="${(i % 2) == 0 ? 'even' : 'odd'}">
37.
38.<td><g:link action="show" id="${bookInstance.id}">${fieldValue(bean: bookInstance, field: "bookTitle")}</g:link></td>
39.
40.<td>${fieldValue(bean: bookInstance, field: "price")}</td>
41.
42.<td>${fieldValue(bean: bookInstance, field: "isbn")}</td>
43.
44.</tr>
45.</g:each>
46.</tbody>
47.</table>
48.<div class="pagination">
49.<g:paginate total="${bookInstanceTotal}" />
50.</div>
51.</div>
52.</body>
53.</html>
  • Line 14: The <g:link> tag creates a link to the create action of the BookController.
  • Line 19: The <g:if> tag checks for the existence of flash.message that was stored in the action and, if found, displays it.
  • Lines 26 to 31: The <g:sortableColumn> tag is used to provide sorting on our list view.
  • Lines 35 to 45: The <g:each> tag iterates over the bookInstanceList. Each item in the list is assigned to the bookInstance variable. The body of the <g:each> tag fills in the table row with the properties of the bookInstance. In the line <tr class="${(i % 2) == 0 ? 'even' : 'odd'}">, a Groovy expression is used to determine the style class of the <tr>, and the fieldValue( ) method is used to render the value of each Book property.
  • Line 49: The <g:paginate> tag displays the pagination controls if there are enough elements in the list view. The bookInstanceTotal is used from Listing 7-19, as mentioned earlier.

The Create View

The create view is illustrated in Listing 7-28.

Listing 7-28. create.gsp

1.<%@ page import="bookstore.Book" %>
2.<!DOCTYPE html>
3.<html>
4.<head>
5.<meta name="layout" content="main">
6.<g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
7.<title><g:message code="default.create.label" args="[entityName]" /></title>
8.</head>
9.<body>
10.<a href="#create-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
11.<div class="nav" role="navigation">
12.<ul>
13.<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
14.<li><g:link class="list" action="list"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
15.</ul>
16.</div>
17.<div id="create-book" class="content scaffold-create" role="main">
18.<h1><g:message code="default.create.label" args="[entityName]" /></h1>
19.<g:if test="${flash.message}">
20.<div class="message" role="status">${flash.message}</div>
21.</g:if>
22.<g:hasErrors bean="${bookInstance}">
23.<ul class="errors" role="alert">
24.<g:eachError bean="${bookInstance}" var="error">
25.<li <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></li>
26.</g:eachError>
27.</ul>
28.</g:hasErrors>
29.<g:form action="save" >
30.<fieldset class="form">
31.<g:render template="form"/>
32.</fieldset>
33.<fieldset class="buttons">
34.<g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />
35.</fieldset>
36.</g:form>
37.</div>
38.</body>
39.</html>
  • Lines 22 to 28: The <g:hasErrors> tag examines the Book instance assigned to its bean attribute and renders its body if errors are found.
  • Line 29 to 36: The <g:form> tag sets up an HTML form. This tag has an action that will result in the URL to submit the form to.
  • Line 31: <g:render > applies a built-in or user-defined Groovy template against a model so that templates can be shared and reused. The template in this case is called form and is located in the views directory as _form.gsp. The leading underline denotes that the .gsp file is a template.

The Show View

The show view is illustrated in Listing 7-29.

Listing 7-29. show.gsp

1.<%@ page import="bookstore.Book" %>
2.<!DOCTYPE html>
3.<html>
4.<head>
5.<meta name="layout" content="main">
6.<g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
7.<title><g:message code="default.show.label" args="[entityName]" /></title>
8.</head>
9.<body>
10.<a href="#show-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
11.<div class="nav" role="navigation">
12.<ul>
13.<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
14.<li><g:link class="list" action="list"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
15.<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
16.</ul>
17.</div>
18.<div id="show-book" class="content scaffold-show" role="main">
19.<h1><g:message code="default.show.label" args="[entityName]" /></h1>
20.<g:if test="${flash.message}">
21.<div class="message" role="status">${flash.message}</div>
22.</g:if>
23.<ol class="property-list book">
24.
25.<g:if test="${bookInstance?.bookTitle}">
26.<li class="fieldcontain">
27.<span id="bookTitle-label" class="property-label"><g:message code="book.bookTitle.label" default="Book Title" /></span>
28.
29.<span class="property-value" aria-labelledby="bookTitle-label"><g:fieldValue bean="${bookInstance}" field="bookTitle"/></span>
30.
31.</li>
32.</g:if>
33.
34.<g:if test="${bookInstance?.price}">
35.<li class="fieldcontain">
36.<span id="price-label" class="property-label"><g:message code="book.price.label" default="Price" /></span>
37.
38.<span class="property-value" aria-labelledby="price-label"><g:fieldValue bean="${bookInstance}" field="price"/></span>
39.
40.</li>
41.</g:if>
42.
43.<g:if test="${bookInstance?.isbn}">
44.<li class="fieldcontain">
45.<span id="isbn-label" class="property-label"><g:message code="book.isbn.label" default="Isbn" /></span>
46.
47.<span class="property-value" aria-labelledby="isbn-label"><g:fieldValue bean="${bookInstance}" field="isbn"/></span>
48.
49.</li>
50.</g:if>
51.
52.</ol>
53.<g:form>
54.<fieldset class="buttons">
55.<g:hiddenField name="id" value="${bookInstance?.id}" />
56.<g:link class="edit" action="edit" id="${bookInstance?.id}"><g:message code="default.button.edit.label" default="Edit" /></g:link>
57.<g:actionSubmit class="delete" action="delete" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}')," />
58.</fieldset>
59.</g:form>
60.</div>
61.</body>
62.</html>
  • Lines 55 to 56: The ? after the bookInstance reference is a safe navigation operator. When this expression is evaluated and if bookInstance is null, the whole expression evaluates to null, and no exception is thrown.
  • Line 57: The <g:actionSubmit> tag generates a submit button that maps to a specific action, which lets you have multiple submit buttons in a single form. JavaScript event handlers can be added using the same parameter names as in HTML.

The Edit View

The edit view is illustrated in Listing 7-30.

Listing 7-30. edit.gsp

1.<%@ page import="bookstore.Book" %>
2.<!DOCTYPE html>
3.<html>
4.<head>
5.<meta name="layout" content="main">
6.<g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
7.<title><g:message code="default.edit.label" args="[entityName]" /></title>
8.</head>
9.<body>
10.<a href="#edit-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
11.<div class="nav" role="navigation">
12.<ul>
13.<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
14.<li><g:link class="list" action="list"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
15.<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
16.</ul>
17.</div>
18.<div id="edit-book" class="content scaffold-edit" role="main">
19.<h1><g:message code="default.edit.label" args="[entityName]" /></h1>
20.<g:if test="${flash.message}">
21.<div class="message" role="status">${flash.message}</div>
22.</g:if>
23.<g:hasErrors bean="${bookInstance}">
24.<ul class="errors" role="alert">
25.<g:eachError bean="${bookInstance}" var="error">
26.<li <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></li>
27.</g:eachError>
28.</ul>
29.</g:hasErrors>
30.<g:form method="post" >
31.<g:hiddenField name="id" value="${bookInstance?.id}" />
32.<g:hiddenField name="version" value="${bookInstance?.version}" />
33.<fieldset class="form">
34.<g:render template="form"/>
35.</fieldset>
36.<fieldset class="buttons">
37.<g:actionSubmit class="save" action="update" value="${message(code: 'default.button.update.label', default: 'Update')}" />
38.<g:actionSubmit class="delete" action="delete" value="${message(code: 'default.button.delete.label', default: 'Delete')}" formnovalidate="" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}')," />
39.</fieldset>
40.</g:form>
41.</div>
42.</body>
43.</html>
  • Lines 31 to 32: In Listing 7-24, it was mentioned that the id in the update( ) action is provided from this edit view. The id comes from the <g:hidden field> tag, as shown in the code.

With this, we have completed all the views generated by static scaffolding for the Book class.

H2 Console

As discussed earlier, Grails enables the H2 database console in development mode (at the URI /dbconsole) so that the in-memory database can be easily queried from the browser. To see the dbconsole in action, browse to http://localhost:8080/bookstore/dbconsole. The default login parameters should match the default in grails-app/conf/Datasource.groovy, as illustrated in Figure 7-29.

9781430259831_Fig07-29.jpg

Figure 7-29. Login screen of H2

You can get the user name and password from Datasource.groovy. Figure 7-30 illustrates the H2 console.

9781430259831_Fig07-30.jpg

Figure 7-30. H2 console

Now, add a book in our bookstore application and then enter SELECT * from BOOK in the database console. The user we created in the application will show up, as illustrated in Figure 7-31.

9781430259831_Fig07-31.jpg

Figure 7-31. Querying the table in H2

Creating Domain Relationships

In an application domain, classes have relationships to one another. The domain relationships define how domain classes interact. Grails provides support for several types of relationships between domain classes. A one-to-many relationship is when one class, Author, has many instances of the Book class. With Grails you define such a relationship with the hasMany setting, as illustrated in Listing 7-31.

Listing 7-31. One-to-Many Relationship Between Author and Book

class Author {
    static hasMany = [books: Book]
 
String name }
 
class Book {
    String title
}

In Listing 7-31 there is a unidirectional one-to-many. Grails will, by default, map this kind of relationship with a join table. Grails will automatically inject a property of type java.util.Set into the domain class based on the hasMany setting. Grails supports many-to-many relationships by defining a hasMany on both sides of the relationship and having a belongsTo on the owned side of the relationship.

You will create an application with a many-to-many domain relationship between Author and Book. You can find the source code for this application in the books project in Chapter 7 of the source code archive that you can download from the Apress website. Listing 7-32 illustrates the Book class.

Listing 7-32. Creating a Domain Relationship Between Book and Author

1.package books
2.
3.class Book {
4.    static belongsTo = Author
5.    static hasMany = [authors:Author]
6.    String title
7.Long isbn
8.String publisher
9.static constraints = {
10.title(blank:false)
11.}
12.
13.String toString() {
14.title
15.}
16.}
  • Line 4: This line informs Grails that the Book class belongs to its owning Author.
  • Line 5: This line inform Grails that the Book class has many instances of Author.

Listing 7-33 illustrates the Author class.

Listing 7-33. Author class.

1.package books
2.
3.class Author {
4.
5.    static hasMany = [books:Book]
6.    String firstName
7.String lastName
8.static constraints = {
9.firstName(blank:false)
10.lastName(blank:false)
11.}
12.String toString() {
13."$lastName, $firstName"
14.}
15.}
  • Line 5: This line tells Grails that an Author has many instances of Book.

Grails maps a many-to-many relationship using a join table at the database level. The owning side of the relationship, Author in this application, takes responsibility for persisting the relationship because Grails uses Hibernate as ORM frameworks and, in Hibernate, only one side of a many-to-many relationship can take responsibility for managing the relationship.

With the relationship between the domain classes created, all you need to do is to create BookController and AuthorController and set their scaffold property. In the BookController you created, replace the index() action with the scaffold property set to Book, as illustrated in Listing 7-34.

Listing 7-34. BookController

class BookController {
 
    static scaffold = Book
}

In the AuthorController, set the scaffold property to Author, as illustrated in Listing 7-35.

Listing 7-35. AuthorController

class AuthorController {
 
    static scaffold = Author
}

Now you can run the application. The scaffolding will generate the application for you and point you to the URL. Figure 7-32 shows the welcome screen of the application.

9781430259831_Fig07-32.jpg

Figure 7-32. Welcome screen of book application

When you click the AuthorController, the Author List screen is displayed, as illustrated in Figure 7-33.

9781430259831_Fig07-33.jpg

Figure 7-33. Author List screen

In Figure 7-33, you can create a new author by clicking the New Author link. Figure 7-34 illustrates the list of authors thus created.

9781430259831_Fig07-34.jpg

Figure 7-34. List of authors

Now you can view the Book List by clicking the BookController link on the welcome screen, as illustrated in Figure 7-35.

9781430259831_Fig07-35.jpg

Figure 7-35. Book List screen

You can add a new book by clicking the New Book Link. When you click New Book, a Create Book screen is displayed. If you try to create a book with blank Title and Isbn fields, you will get the validation message illustrated in Figure 7-36. You did not write any constraints for the ISBN in Listing 7-32, but still there is validation on the ISBN field because Grails provides constraints by default for some fields, and ISBN is one of them (as listed in Table 7-2 of this chapter).

9781430259831_Fig07-36.jpg

Figure 7-36. Validation error

Now you can create the book by providing values for these fields. Figure 7-37 illustrates the Book List screen with two books created.

9781430259831_Fig07-37.jpg

Figure 7-37. Book List screen

Now you can go to the Author List screen and click the Edit button, as illustrated in Figure 7-38.

9781430259831_Fig07-38.jpg

Figure 7-38. Editing the author

When you click the Edit button, you will see that two books are available, as illustrated in Figure 7-39. You can select both books and click the Update button.

9781430259831_Fig07-39.jpg

Figure 7-39. Edit Author screen

In this way, you can add the books for all the authors you have created. Now when you go to the Book List screen and click one of the Book Title links, the Show Book screen is displayed with the name of all the authors for that book, as illustrated in Figure 7-40.

9781430259831_Fig07-40.jpg

Figure 7-40. Show Book screen

This concludes the chapter, which sketched a brief overview of Grails 2. For more detailed coverage, I recommend Beginning Groovy, Grails, and Griffon by Vishal Layka, Christopher M. Judd, Joseph Faisal Nusairat, and Jim Shingler (Apress, 2012), as well as The Definitive Guide to Grails 2 by Jeff Scott Brown and Graeme Rocher (Apress 2012).

Summary

In this chapter, you learned that Grails is a rapid web development framework that combines the best of Java open source, conventions, the Groovy dynamic language, and the power of the Java platform. You saw how easy it is to develop a fully functional application using Grails scaffolding to do most of the work. You used static scaffolding as a learning tool, generating the controller and views for one domain class (Book). Then you navigated through the controller, and saw that all the work in the controller is done in the actions. Then you learned the code for each action responsible for the corresponding view. Finally, you navigated through the views and saw how the views take advantage of the Grails tag libraries to promote a clean separation of concerns.

1www.h2database.com/html/main.html

2http://wiki.sitemesh.org/display/sitemesh/Home

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

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