CHAPTER 4

Understanding Controllers

A Grails controller is a class that is responsible for handling requests coming in to the application. The controller receives the request, potentially does some work with the request, and finally decides what should happen next. What happens next might include the following:

  • Execute another controller action (possibly, but not necessarily, in the same controller)
  • Render a view
  • Render information directly to the response

A controller is prototyped, meaning that a new instance is created per request. So developers don't need to be as cautious about maintaining thread-safe code in a singleton controller.

You can think of controllers as the orchestrators of a Grails application. They provide the main entry point for any Grails application by coordinating incoming requests, delegating to services or domain classes for business logic, and rendering views.

Let's look at the basics of how to create a controller before moving on to meatier subjects such as data binding and command objects.

Defining Controllers

A controller is a class defined under the grails-app/controllers directory. The class name must end with "Controller" by convention. Controllers do not need to extend any special base class or implement any special interfaces.

Listing 4-1 shows a typical controller, residing at the location grails-app/controllers/SampleController.groovy, that defines an action called index. The index action renders a simple textual response.

Listing 4-1. The SampleController Class

class SampleController {
  def index = {
    render 'You accessed the Sample controller...'
  }
}

With this controller in place, a request to /sample/index will result in the String "You accessed the Sample controller ... " being rendered back to the browser. You can see that actions, like the index action, are defined as fields. Each field is assigned a block of code using a Groovy closure. A controller can define any number of actions, as shown in Listing 4-2.

Listing 4-2. Defining Multiple Actions

class SampleController {
  def first = { ... }
  def second = { ... }
  def third = { ... }
  def fourth = { ... }
}

In Chapter 6, you will learn about the powerful URL-mapping support that Grails provides. By default, URLs are mapped to controller actions by way of a convention. The first part of the URL represents which controller to access, and the second part of the URL represents which action should be executed. For example, /sample/first will execute the first action in the SampleController. Likewise, /sample/second will execute the second action in the SampleController.

Setting the Default Action

You don't necessarily need to specify the action to execute in the URL. If no action is specified, Grails will execute the default action in the specified controller. You can identify the default action using the following rules (see Listing 4-3):

  • If the controller defines only one action, it becomes the default action.
  • If the controller defines an action called index, it becomes the default action.
  • If the controller defines a property called defaultAction, its value is the name of the default action.

Listing 4-3. The Default Action

// Here the 'list' action is the default as there is only one action defined
class SampleController {
  def list = {}
}
// In this example 'index' is the default by convention
class SampleController {
  def list = {}
  def index = {}
}
// Here 'list' is explicitly set as the default
class SampleController {
  def defaultAction = 'list'
  def list = {}
  def index = {}
}

Logging

Logging, an important aspect of any application, allows the application to report textual information about what is going on inside it. Various logging solutions exist on the Java platform, including third-party logging solutions as well as the standard logging API introduced in Java 1.4. You face a certain amount of complexity in configuring logging for an application.

Often, application developers will avoid this complexity by avoiding logging altogether and opt instead for simply printing messages using System.out.println and System.err.println. For a variety of reasons, this is really not a good idea.

Fortunately, Grails tackles much of the complexity involved with setting up logging. A log property, which is injected into every controller, is an instance of org.apache.commons.logging.Log. You don't need to write any code to initialize the log property because the framework handles that. Listing 4-4 documents the org.apache.commons.logging.Log API.

Listing 4-4. The org.apache.commons.logging.Log Interface

public interface Log {
  public void debug(Object msg);
  public void debug(Object msg, Throwable t);
  public void error(Object msg);
  public void error(Object msg, Throwable t);
  public void fatal(Object msg);
  public void fatal(Object msg, Throwable t);
  public void info(Object msg);
  public void info(Object msg, Throwable t);
  public void trace(Object msg);
  public void trace(Object msg, Throwable t);
  public void warn(Object msg);
  public void warn(Object msg, Throwable t);
  public boolean isDebugEnabled();
  public boolean isErrorEnabled();
  public boolean isFatalEnabled();
  public boolean isInfoEnabled();
  public boolean isTraceEnabled();
  public boolean isWarnEnabled();
}

The log property that is injected into a controller can be used from any controller action or any method within the controller (see Listing 4-5).

Listing 4-5. Using the log Property

class SampleController {
  def index = {
    log.info('In the index action...')
    // ...
  }
}

Logging Exceptions

Groovy translates all exceptions into runtime exceptions, so Groovy code is never forced to catch an exception. This differs from what Java developers are used to. In any case, even though an application is never forced to catch an exception, it makes sense to catch an exception in a lot of scenarios. In Groovy, the details for how to catch an exception are exactly the same as they are in Java. There is no special Groovy syntax for handling exceptions.

When an exception is caught in a controller, you'll almost always want to log details about the exception using the log property (see Listing 4-6).

Listing 4-6. Logging an Exception

class SampleController {
  def index = {
    try {
      // do something that might throw an exception
    } catch (Exception e) {
      log.error ('some message goes here', e)
    }
  }
}

Accessing Request Attributes

Java servlet developers will recognize components such as HttpServletRequest, HttpServletResponse, HttpSession, ServletContext, and others. These are all standard players in the servlet space. The Grails framework differs greatly from your standard servlet-based web frameworks, of course. However, Grails is built on top of those same servlet APIs. Table 4-1 contains a list of standard attributes that are automatically injected into Grails controllers.

Table 4-1. Standard Request Attributes

Attribute Description
actionName The name of the currently executing action
actionUri The relative URI of the executing action
controllerName The name of the currently executing controller
controllerUri The URI of executing controller
flash The object for working with flash scope
log An org.apache.commons.logging.Log instance
params A map of request parameters
request The HttpServletRequest object
response The HttpServletResponse object
session The HttpSession object
servletContext The ServletContext object

Many of the previously listed attributes are standard servlet API objects, whose documentation you can find on Sun's Java technology web site at http://java.sun.com/. It is, however, interesting to observe how working with a Grails controller differs from working with these objects.

A common way to interact with the request, for example, is to retrieve or set a request attribute. The session and servlet context also have attributes that you can set or retrieve. Grails unifies these by overriding the dot dereference and subscript operators. Table 4-2 shows the difference between accessing request, session, and servlet context attributes in regular Java servlets compared to accessing them in Grails controllers.

Table 4-2. Differences Between Request Attributes in Java Servlets and Grails Controllers

Java Servlet Grails Controller
request.getAttribute("myAttr"); request.myAttr
request.setAttribute("myAttr", "myValue"); request.myAttr = "myValue"
session.getAttribute("mAttr"); session.myAttr
session.setAttribute("myAttr", "myValue""); session.myAttr = "myValue"
servletContext.getAttribute("mAttr"); servletContext.myAttr
servletContext.setAttribute("myAttr", "myValue""); servletContext.myAttr = "myValue"

Of course, if you are accustomed to writing code like that in the left column of the table, you can continue to do so; Grails just makes it a little bit easier.

Using Controller Scopes

You can choose from a number of scopes when developing controllers. The following list defines all the scopes available in order of their longevity:

  • request: Objects placed into the request are kept for the duration of the currently executing request.
  • flash: Objects placed into flash are kept for the duration of the current request and the next request only.
  • session: Objects placed into the session are kept until the user session is invalidated, either manually or through expiration.
  • servletContext: Objects placed into the servletContext are shared across the entire application and kept for the lifetime of the application.

As you can see, each scope is unique, and provides very different semantics. In an ideal world, sticking to request scope allows you to maintain a completely stateless application. In terms of scalability, this has significant advantages, as you do not need to consider issues such as replication of session state and session affinity.

However, you can certainly scale stateful applications that use flash and session scope using container-provided replication services or distributed data grids. The advantage of session scope is that it allows you to associate data on the server with individual clients. This typically works using cookies to associate individual users with their sessions.

Finally, the servletContext is a rarely used scope that allows you to share state across the entire application. Although this can prove useful, you should exercise caution when using the servletContext because objects placed within it will not be garbage-collected unless the application explicitly removes them. Also, access to the servletContext object is not synchronized, so you need to do manual synchronization if you plan to read and write objects from the servletContext object, as shown in Listing 4-7.

Listing 4-7. Synchronized Access to the ServletContext

def index = {
     synchronized(servletContext) {
             def myValue = servletContext.myAttr
             servletContext.myAttr = "changed"
             render myValue
     }
}

Of course, writing code like this will result in a serious bottleneck in your application, which leads us to the best-practice usage of the servletContext object: in general, if you really need to use the servletContext, you should prepopulate it with any values you need at startup and then read those values only at runtime. This allows you to access the servletContext in an unsynchronized manner.

Understanding Flash Scope

The flash object is a map accessible in the same way as the params object, the fundamental difference being that key/value pairs stored in the flash object are stored in flash scope. What is flash scope? It's best explained with the problem it solves.

A common usage pattern in web applications is to do some processing and then redirect the request to another controller, servlet, or whatever. This is not an issue in itself, except, What happens when the request is redirected? Redirecting the request essentially creates a brand-new request, wiping out all previous data that might have resided in the request attributes. The target of the redirect often needs this data, but unfortunately, the target action is out of luck. Some have worked around this issue by storing this information in the session instead.

This is all fine and good, but the problem with the session is that developers often forget to clear out this temporarily stored data, which places the burden on the developer to explicitly manage this state. Figure 4-1 illustrates this problem in action.

The first request that comes in sets an attribute on the request called "message." It then redirects the request by sending a redirect response back to the client. This creates a brand-new request instance, which is sent to the controller. Sadly, the message attribute is lost and evaluates to null.

image

Figure 4-1. Request attributes and redirects

To get around this little annoyance, the flash object stores its values for the next request and the next request only, after which they automatically vanish. This feature manages the burden of this kind of use case for you. It's another small but significant feature that allows you to focus on the problem at hand instead of the surrounding issues.

One of the more common use cases for flash scope is to store a message that will display when some form of validation fails. Listing 4-8 demonstrates how to store a hypothetical message in the flash object so it's available for the next request.

Listing 4-8. Storing a Message in Flash Scope

flash.message = 'I am available next time you request me!'

Remember that the flash object implements java.util.Map, so all the regular methods of this class are also available. Figure 4-2 shows how flash scope solves the aforementioned problem. Here, on the first request, you store a message variable to the flash object and then redirect the request. When the new request comes in, you can access this message, no problem. The message variable will then automatically be removed for the next request that comes in.


Note The flash object does still use the HttpSession instance internally to store itself, so if you require any kind of session affinity or clustering, remember that it applies to the flash object, too.


image

Figure 4-2. Using flash scope

Accessing Request Parameters

A controller action is often given input that will affect the behavior of the controller. For example, if a user submits a form that he or she has filled out, all the form's field names and values will be available to the controller in the form of request parameters. The standard servlet API provides an API for accessing request parameters. Listing 4-9 shows how a controller might retrieve the userName request parameter.

Listing 4-9. Request Parameters via Standard Servlet API

def userName = request.getParameter('userName')
log.info("User Name: ${userName}")

One of the dynamic properties injected into a Grails controller is a property called params. This params property is a map of request parameters. Listing 4-10 shows how a controller might retrieve the userName request parameter using the params property.

Listing 4-10. Request Parameters via params Property

def userName = params.userName
log.info("User Name: ${userName}")

Rendering Text

In its most basic form, you can use the render method from a controller to output text to the response. Listing 4-11 demonstrates how to render a simple String to the response.

Listing 4-11. Rendering a Simple String

render 'this text will be rendered back as part of the response'

Optionally, you can specify the contentType:

render text:'<album>Revolver</album>', contentType:'text/xml'

The most common use of the render method is to render a GSP view or a GSP template. We'll cover GSP in detail in Chapter 5.

Redirecting a Request

Often a controller action will need to redirect control to another controller action. This is a common thing for a controller action to do, so Grails provides a simple technique to manage redirecting to another controller action.

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.

Specifying the controller is required only if the request is being redirected to an action defined in a controller other than the current controller. Listing 4-12 shows a standard redirect from the first action to the second action within the sample controller.

Listing 4-12. A Simple Redirect

class SampleController {
  def first = {
    // redirect to the "second" action...
    redirect(action: "second")
  }
  def second = {
    // ...
  }
}

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

Listing 4-13. Redirecting to an Action in Another Controller

class SampleController {
  def first = {
    // redirect to the 'list' action in the 'store' controller...
    redirect(action: "list", controller: "store")
  }
}

Although the previous examples are pretty trivial, the redirect method is pretty flexible. Table 4-3 shows the different arguments that the redirect method accepts.

Table 4-3. Redirect Arguments

Argument Name Description
action The name of or a reference to the action to redirect to
controller The name of the controller to redirect to
id The id parameter to pass in the redirect
params A map of parameters to pass
uri A relative URI to redirect to
url An absolute URL to redirect to

As you can see, the redirect method allows you to effectively pass control from one action to the next. However, often you simply want to formulate some data to be rendered by a view. In the next couple of sections, we'll take a look at how to achieve this.

Creating a Model

One of the most fundamental activities carried out in a controller is gathering data that will be rendered in the view. A controller can gather data directly or delegate to Grails services or other components to gather the data. However the controller gathers the data, the data is typically made available to the view in the form of a map that the controller action returns. When a controller action returns a map, that map represents data that the view can reference. Listing 4-14 displays the show action of the SongController.

Listing 4-14. Returning a Map of Data to be Rendered by the View

class SongController {
  def show = {
    [ song: Song.get(params.id) ]
  }
}

Remember that return statements are optional in Groovy. Because the last expression evaluated in the show action is a map, the map is the return value from this action. This map contains data that will be passed in to the view to be rendered. In Listing 4-14, the sole key in the map is song and the value associated with that key is a Song object retrieved from the database based on the id request parameter.

Now the view can reference this song. Whereas this map contains only a single entry, a map returned from a controller action can include as many entries as is appropriate. Every entry represents an object that the view can reference.

A controller action does not have to return a model, so what happens if no model is returned? The simple answer is that it depends on what the action does. If the action writes directly to the response output, there is no model; conversely, if a controller action simply delegates to the view with no model returned, the controller's properties automatically become the model. This allows you to write code like that shown in Listing 4-15 as an alternative to the show action you have already seen.

Listing 4-15. The Controller as the Model

class SongController {
  Song song
  def show = {
    this.song = Song.get(params.id)
  }
}

The technique you choose to use is as much up to personal preference as anything else. You'll often find greater clarity in returning an explicitly defined map. Use whatever makes the most sense in your case, and keep in mind that consistency can be as important as anything else in terms of writing code that is easy to understand and maintain.

Rendering a View

The subject of views in Grails is important, and we'll dedicate an entire chapter to it (see Chapter 5). But for now, you need to understand how Grails goes about view selection from a controller's point of view. First, let's look at the default view-selection strategy.

Finding the Default View

As you saw in Listing 4-15, the SongController has a single action called show. The show action returns a model containing a single key, called song, which references an instance of the Song domain class. However, nowhere in the code can you see any reference to the view that will be used to deal with the rendering part of this action.

That's perfectly understandable because we haven't explicitly told Grails what view to render. To mitigate this problem, Grails makes that decision for you based on the conventions in the application. In the case of the show action, Grails will look for a view at the location grails-app/views/song/show.gsp. The name of the view is taken from the name of the action, while the name of the parent directory is taken from the controller name. Simple, really.

But what if you want to display a completely different view? The ever-flexible render method comes to the rescue again.

Selecting a Custom View

To tell Grails to render a custom view, you can use the render method's view argument as shown in Listing 4-16.

Listing 4-16. Rendering a Custom View

class SongController {
  def show = {
     render(view:"display",model:[ song: Song.get(params.id) ])
  }
}

Notice how you can use the model argument to pass in the model, rather than using the return value of the action. In the example in Listing 4-16, we're asking Grails to render a view called "display." In this case, Grails assumes you mean a view at the location grails-app/views/song/display.gsp. Notice how the view is automatically scoped with the grails-app/views/song directory.

If the view you want to render is in another, possibly shared, directory, you can specify an absolute path to the view:

render(view:"/common/song", model:[song: Song.get(params.id) ])

By starting with a / character, you can reference any view within the grails-app/views directory. In the previous example, Grails will try to render a view at the location grails-app/ views/common/song.gsp.

Rendering Templates

In addition to views, Grails supports the notion of templates—small snippets of view code that other views can include. We'll be covering templates in more detail in Chapter 5, but for now, just know that you can render a template from a controller using the render method:

render(template:"/common/song", model:[song: Song.get(params.id) ] )

In this case, Grails will try to render a template at the location grails-app/views/common/_song.gsp. Notice how, unlike views, the name of the template starts with an underscore by convention.

Performing Data Binding

Often a controller action will need to create new domain objects and populate the properties of the instance with values received as request parameters. Consider the Album domain class, which has properties such as genre and title. If a request is made to the save action in the AlbumController, the controller action could create a new Album and save it using a technique like that shown in Listing 4-17.

Listing 4-17. Populating an Album with Request Parameters

class AlbumController {
  def save = {
    def album = new Album()
    album.genre = params.genre
    album.title = params.title
    album.save()
  }
}

The approach in Listing 4-17 assigns values to domain properties based on corresponding request parameters. It might work for simple models with a small number of properties, but as your domain model grows in complexity, this code gets longer and more tedious. Fortunately, the Grails framework provides some slick options for binding request parameters to a domain object.

Remember that the params object in your controller is a map of name/value pairs. You can pass maps to a domain class's constructor to initialize all the domain class's properties with the corresponding request parameters. Listing 4-18 shows a better approach to creating and populating an Album object.

Listing 4-18. Populating an Album by Passing params to the Constructor

class AlbumController {
  def save = {
    def album = new Album(params)
    album.save()
  }
}

Caution The features detailed so far can leave your web application open to URL attacks due to the automatic setting of properties from request parameters. This is a common issue among frameworks that perform such conversion (including Ruby on Rails, Spring MVC, WebWork, and so on). If you are developing a web application with heightened security in mind, you should use fine-grained control over data binding through the bindData method (described later) and stricter validation.


As you can see, this is a much cleaner approach and scales better as the number of properties in a domain class grows.

Occasionally, setting properties on a domain object that has already been constructed can prove useful. For example, you retrieve a domain object from the database and then need to update it with values passed to the controller as request parameters. In a case like this, passing a map of parameters to the domain-class constructor will not help because the object already exists. Grails provides yet another slick solution here. You can use a domain class's properties property in conjunction with request parameters to update an existing object, as shown in Listing 4-19.

Listing 4-19. Updating an Existing Object with Request Parameters

class AlbumController {
  def update = {
    def album = Album.get(params.id)
    album.properties = params
    album.save()
  }
}

Whenever your application accepts user input, there is a chance that said input might not be what your application requires. You've already seen in Chapter 3 how to define custom-validation constraints on domain classes; now you'll begin to understand how you can use data binding in combination with these constraints to validate incoming data.

Validating Incoming Data

The mechanics of Grails' data-binding and data-validation process has two phases. First, let's revisit the following line of code:

album.properties = params

At this point, Grails will attempt to bind any incoming request parameters onto the properties of the instance. Groovy is a strongly typed language and all parameters arrive as strings, so some type conversion might be necessary.

Underneath the surface, Grails uses Spring's data-binding capabilities to convert request parameters to the target type if necessary. During this process, a type-conversion error can occur if, for example, converting the String representation of a number to a java.lang.Integer is impossible. If an error occurs, Grails automatically sets the persistent instance to read-only so it cannot be persisted unless you explicitly persist it yourself (refer to Chapter 10 for more information on automatic dirty checking).

If all is well, the second phase of validation commences. At this point, you validate the persistent instance against its defined constraints using either the validate() method or the save() method as described in Chapter 3:

album.validate()

Grails will validate each property of the Album instance and populate the Errors object with any validation errors that might have occurred. This brings us nicely into a discussion of the Errors API.

The Errors API and Controllers

The mechanics of Grails' validation mechanism is built entirely on Spring's org.springframework.validation package. As discussed in Chapter 3, whenever you validate a domain instance, a Spring org.springspringframework.validation.Errors object is created and associated with the instance.

From a controller's point of view, when you have a domain object in an invalid state— typically due to invalid user input that changes an instance using data binding—you need to use branching logic to handle the validation error.

Listing 4-20 shows an example of how you could use data binding to update an Album instance and validate its state.

Listing 4-20. Dealing with Validation Errors

def save = {
       def album = Album.get(params.id)
       album.properties = params
       if(album.save()) {
             redirect(action: "show", id:album.id)
       }
       else {
             render(view: "edit", model:[album:album])
       }
}

Notice how in Listing 4-20 you can call the save() method, which triggers validation, and send the user back to the edit view if a validation error occurs. When a user enters invalid data, the errors property on the Album will be an Errors object containing one or more validation errors.

You can programmatically decipher these errors by iterating over them:

album.errors.allErrors.each { println it.code }

If you merely want to check if an instance has any errors, you can call the hasErrors() method on the instance:

if(album.hasErrors()) println "Something went wrong!"

In the view, you can render these using the <g:renderErrors> tag:

<g:renderErrors bean="${album}" />

You'll be learning more about handling errors in the view through the course of the book, but as you can see, it's frequently the controller's job to coordinate errors that occur and ensure the user enters valid data.

Data Binding to Multiple Domain Objects

In the examples of data binding you've seen so far, the assumption has been that you wish to bind parameters to a single domain instance. However, you might encounter a scenario in which you must create several domain instances.

Consider, for example, the creation of Artist instances in the gTunes application. The application might require that an Artist can exist only if he or she has released at least one Album. In this case, it makes sense to create both the Artist and the first Album simultaneously.

To understand data binding when dealing with multiple domain instances, you first need to understand how parameters are submitted from forms. Consider, for example, the case of updating an Album and the line:

album.properties = params

In this case, the expectation is that parameters are not namespaced in any way. In other words, to update the title property of the Album instance, you can provide an HTML input such as the following:

<input type="text" name="title" />

Notice how the name of the <input> matches the property name. This clearly would not work in the case of multiple domain classes because you might have two different domain classes that have a property called title. You can get around this problem namespacing any parameters passed using a dot:

<input type="text" name="album.title" />
<input type="text" name="artist.name" />
...

Now create and bind both Album and Artist instances by referring to them within the params object by their respective namespaces:

def album = new Album( params["album"] )
def artist = new Artist( params["artist"] )

Data Binding with the bindData Method

The data-binding techniques you have seen so far are automatic and handled implicitly by Grails. However, in some circumstances you might need to exercise greater control over the data-binding process or to bind data to objects other than domain classes. To help tackle this issue, Grails provides a bindData method that takes the object to bind the data to and a java.util.Map.

The map should contain keys that match the property names of the target properties within the passed object. As an example, if you wanted to ensure only the title property was bound to an Album instance, you could use the code shown in Listing 4-21.

Listing 4-21. Using the bindData Method

class AlbumController {
     def save = {
               def album = Album.get(params.id)
                bindData(album, params, [include:"title"])
              ...
     }
}

Notice how in Listing 4-21 you can pass the Album instance as the first argument, and the parameters to bind to the instance as the second argument. The final argument is a map specifying that you wish to include only the title property in the data-binding process. You could change the key within the map to exclude if you wished to bind all properties except the title property.

Finally, as you saw in the previous section, you can bind to multiple domain instances using Grails' default data-binding mechanism. You can do this with the bindData method too, using the last argument that specifies the prefix to filter by:

bindData(album, params, [include:"title"], "album")

In this example, the prefix "album" is passed as the last argument, making the bindData method bind all parameters that begin with the album. prefix.

Data Binding and Associations

The final topic to consider when doing data binding is how it relates to associations. The easiest case to understand is many-to-one and one-to-one associations. For example, consider the artist property of the Album class, which is a many-to-one association, as shown in Listing 4-22.

Listing 4-22. The artist Association of the Album Class

class Album {
       Artist artist
       ...

}

You need to consider two cases when working with a many-to-one association like this. The first involves creating new instances. Suppose you create a new Album instance using this code:

def album = new Album(params)

In this case, if any parameters reference the artist association, such as artist.name, a new Artist instance will be automatically instantiated and assigned to the Album instance. The names of the properties to set are taken from the value of the right side of the dot in the request-parameter name. With artist.name, the property to set is name. To further clarify, the following <input> tag shows an example of a form field that will populate the artist association of the Album class:

<input type="text" name="artist.name" />

The second scenario occurs when you are assigning an existing instance of an association (an existing Artist, for example) or modifying an association. To do this, you need to pass the association's identifier using a request parameter with the .id suffix. For example, you can use the following <input> to specify the Artist that should be associated with an existing or new Album:

<input type="text" name="artist.id" value="1" />

With single-ended associations out of the way, let's consider associations that contain multiple objects. For example, an Album has many Song instances in its songs associations. What if you wanted to provide a form that enabled you to create an Album and its associated songs? To enable this, you can use subscript-style references to populate or update multiple Song instances:

<input type="text" name="songs[0].title" value="The Bucket" />
<input type="text" name="songs[1].title" value="Milk" />

Note that the default collection type for association in Grails is a java.util.Set, so unless you change the default to java.util.List, the order of entries will not be retained because Set types have no concept of order. If you want to create a new Album instance and populate the songs association with an existing collection of songs, then you can just specify their identifiers using the .id suffix:

<input type="text" name="songs[0].id" value="23" />
<input type="text" name="songs[1].id" value="47" />

Working with Command Objects

Sometimes a particular action doesn't require the involvement of a domain class, but still requires the validation of user input. In this case, you might want to consider using a command object. A command object is a class that has all the data-binding and data-validation capabilities of a domain class, but is not persistent. In other words, you can define constraints of a command object and validate them just like a domain class.

Defining Command Objects

A command object requires the definition of class, just like any other object. You can define command classes in the grails-app/controllers directory or even in the same file as a controller. Unlike Java, Groovy supports the notion of multiple class definitions per file, which is quite handy if you plan to use a particular command object only for the controller you're working with.

For example, you could define an AlbumCreateCommand that encapsulates the validation and creation of new Album instances before they are saved. Listing 4-23 presents such an example.

Listing 4-23. An Example Command Object Definition

class AlbumCreateCommand {
    String artist
    String title
    List songs = []
    List durations = []
    static constraints = {
        artist blank:false
        title blank:false
        songs minSize:1, validator:{ val, obj ->
              if(val.size() != obj.durations.size())
                     return "songs.durations.not.equal.size"
        }
    }
    Album createAlbum() {
          def artist = Artist.findByName(artist) ?: new Artist(name:artist)
          def album = new Album(title:title)
          songs.eachWithIndex { songTitle, i ->
             album.addToSongs(title:songTitle, duration:durations[i])                }
         return album
    }
}

In Listing 4-23, you can see a command-object definition that is designed to capture everything necessary to subsequently create a valid Album instance. Notice how you can define constraints on a command object just like in a domain class. The createAlbum() method, which is optional, is interesting because it shows how you can use command objects as factories that take a valid set of data and construct your domain instances. In the next section, you'll see how to take advantage of the command object in Listing 4-23.

Using Command Objects

In order to use a command object, you need to specify the command as the first argument in a controller action. For example, to use AlbumCreateCommand, you need to have a save action such as the one shown in Listing 4-24.

Listing 4-24. Using a Command Object

class AlbumController {
...
      def save = { AlbumCreateCommand cmd ->
              ...

       }
}

As you can see from the code highlighted in bold, you need to explicitly define the command object using its type definition as the first argument to the action. Here's what happens next: when a request comes in, Grails will automatically create a new instance, bind the incoming request parameters to the properties of the instance, and pass it to you as the first argument.

Providing the request parameters to a command like this is pretty trivial. Listing 4-25 shows an example form.

Listing 4-25. Providing a Form to Populate the Data

<g:form url="[controller: 'album', action: 'save'] ">
       Title: <input type="text" name="title" /> <br>
       Artist: <input type="text" name="artist" /> <br>
       Song 1: <input type="text" name="songs[0]" /> <br>
       Song 2: <input type="text" name="songs[1]" /> <br>
       ...
</g:form>

You'll probably want to make the input of the songs dynamic using some JavaScript, but nevertheless you can see the concept in Listing 4-25. Once you've given the user the ability to enter data and you're capturing said data using the command object, all you need to do is validate it. Listing 4-26 shows how the save action's logic might look with the command object in use.

Listing 4-26. Using the Command Object for Validation

def save = { AlbumCreateCommand cmd ->
      if(cmd.validate()) {
            def album = cmd.createAlbum()
            album.save()
            redirect(action:"show", id:album.id)
       }
       else {
            render(view:"create", model:[cmd:cmd])
        }
}

As you can see, it's now the command object that is ensuring the validity of the request, and we're using it as a factory to construct a perfectly valid Album instance. As with domain classes, command objects have an Errors object, so you can use the <g:renderErrors> tag to display validation errors to the user:

<g:renderErrors bean="{cmd}" />

Imposing HTTP Method Restrictions

Often a web application needs to impose restrictions on which HTTP request methods are allowed for a specific controller action. For example, it is generally considered a bad idea for a controller action to carry out any destructive operation in response to an HTTP GET. Such operations should be limited to HTTP POST or DELETE.

Implementing an Imperative Solution

One approach to dealing with this concern is for a controller action to inspect the request method and prevent certain actions from being carried out in response to an inappropriate HTTP request method. Listing 4-27 shows a simple imperative approach to the problem.

Listing 4-27. Inspecting the HTTP Request Method in a Controller Action

class SongController {
  def delete = {
    if(request.method == "GET") {
      // do not delete in response to a GET request
      // redirect to the list action
      redirect(action: "list")
    } else {
      // carry out the delete here...
    }
  }
}

While this approach is fairly straightforward and does get the job done, it's a tedious solution to the problem. In a real-world application, this same logic would appear in many controller actions.

Taking Advantage of a Declarative Syntax

A better solution to limiting actions to certain HTTP request methods is to take advantage of a simple declarative syntax that expresses which HTTP request methods are valid for a particular controller action. Grails supports an approach like this through the optional allowedMethods property in a controller.

The allowedMethods property expresses which HTTP request methods are valid for any particular controller action. By default, all HTTP request methods are considered valid for any particular controller action. If you want an action to be accessible through specific request methods only, then you should include the action in the allowedMethods property.

You should assign the allowedMethods property a value that is a map. The keys in the map should be the names of actions that you want restricted. The value(s) associated with the keys should be a String representing a specific request method or a list of Strings representing all allowed methods for that particular action. Listing 4-28 shows an example.

Listing 4-28. Restricting Access to Controller Actions Using the allowedMethods Property

class SomeController {
// action1 may be invoked via a POST
  // action2 has no restrictions
  // action3 may be invoked via a POST or DELETE
  def allowedMethods = [action1:'POST', action3:['POST', 'DELETE']]
  def action1 = { ... }
  def action2 = { ... }
  def action3 = { ... }
}

If the rules expressed in the allowedMethods property are violated, the framework will deny the request and return a 405 error code, which the HTTP specification defines as "Method Not Allowed."

Controller IO

As you've learned so far, controllers can control request flow through redirects and rendering views. In addition to this, controllers might need to read and write binary input to and from the client. In this section, we'll look at how to read data, including file uploads, and how to write binary responses to the client.

Handling File Uploads

One of the more common use cases when developing web applications is to allow the user to upload a local file to the server using a multipart request. This is where Grails' solid foundation of Spring MVC starts to shine through.

Spring has excellent support for handling file uploads via an extension to the servlet API's HttpServletRequest interface called org.springframework.web.multipart.MultipartHttpServletRequest, the definition of which is in Listing 4-29.

Listing 4-29. The org.springframework.web.multipart.MultipartHttpServletRequest Interface

interface MultipartHttpServletRequest extends HttpServletRequest {
         public MultipartFile getFile(String name);
         public Map getFileMap();
         public Iterator getFileNames();
}

As you can see, the MultipartHttpServletRequest interface simply extends the default HttpServletRequest interface to provide useful methods to work with files in the request.

Working with Multipart Requests

Essentially, whenever a multipart request is detected, a request object that implements the MultipartHttpServletRequest interface is present in the controller instance. This provides access to the methods seen in Listing 4-30 to access files uploaded in a multipart request. Listing 4-30 also shows how you can define a multipart form using the <g:uploadForm> tag.

Listing 4-30. An Example Upload Form

<g:uploadForm action="upload">       <input type="file" name="myFile" />
      <input type="submit" value="Upload! " />
</g:uploadForm>

The important bits are highlighted in bold, but an upload form essentially requires two things:

  • A <form> tag with the enctype attribute set to the value multipart/form-data. The <g:uploadForm> in Listing 4-30 does this for you automatically.
  • An <input> tag whose type attribute is set to the value file.

In the previous case, the name of the file input is myFile; this is crucial because it's the named reference that you work with when using the getFile method of the MultipartHttpServletRequest interface. For example, the code within an upload action will retrieve the uploaded file from the request (see Listing 4-31).

Listing 4-31. Retrieving the Uploaded File

def upload = {
        def file = request.getFile('myFile')
        // do something with the file
}

Note that the getFile method does not return a java.io.File, but instead returns an instance of org.springframework.web.multipart.MultipartFile, the interface detailed in Listing 4-32. If the file is not found in the request, the getFile method will return null.

Listing 4-32. The org.springframework.web.multipart.MultipartFile Interface

interface MultipartFile {
     public byte[] getBytes();
     public String getContentType();
     public java.io.InputStream getInputStream();
     public String getName();
     public String getOriginalFilename();
     public long getSize();
     public boolean isEmpty();
     public void transferTo(java.io.File dest);
}

Many useful methods are defined in the MultipartFile interface. Potential use cases include the following:

  • Use the getSize() method to allow uploads only of certain file sizes.
  • Reject empty files using the isEmpty() method.
  • Read the file as a java.io.InputStream using the getInputStream() method.
  • Allow only certain file types to be uploaded using the getContentType() method.
  • Transfer the file onto the server using the transferTo(dest) method.

As an example, the code in Listing 4-33 will upload a file to the server if it's not empty and if it's fewer than 1,024 bytes in size.

Listing 4-33. File Uploads in Action

def upload = {
     def file = request.getFile('myFile')
     if(file && file.empty && file.size < 1024) {
         file.transferTo( new java.io.File( "/local/server/path/${file.name}" ) )
     }
}

Working directly with a MultipartHttpServletRequest instance is one way to manage file uploads, but frequently you need to read the contents of a file. In the next section, we'll look at how Grails makes this easier through data binding.

Uploads and Data Binding

In the "Performing Data Binding" section, you saw how Grails handles automatic type conversion from strings to other common Java types. What we didn't discuss is how this capability extends to file uploads. Grails, through Spring MVC, will automatically bind files uploaded to properties of domain-class instances based on the following rules:

  • If the target property is a byte[], the file's bytes will be bound.
  • If the target property is a String, the file's contents as a string will be bound.

Suppose you want to allow users of the gTunes application to upload album art for each album. By adding a new property to the Album domain class called art of type byte[], you automatically have the capability to save the image data to the database, as shown in Listing 4-34.

Listing 4-34. Adding the Picture Property

class Album{
      byte[] art
      ...
}

To bind an uploaded file, you simply need to add an art upload field that matches the art property name to a <g:uploadForm> tag:

<input type="file" name="art" />

The following line automatically handles binding the file to the s:

def user = new Album( params )

Grails will automatically recognize the request as being multipart, retrieve the file, and bind the bytes that make up the file to the art byte array property of the Album class. This capability also extends to usage in conjunction with the properties property and bindData method discussed previously.

Reading the Request InputStream

The way in which you read the body of an incoming request depends very much on the content type of the request. For example, if the incoming request is an XML request, the parsing is handled automatically for you. We'll cover this subject further in Chapter 15.

However, if you just want to get the text contained within the request body, you can use the inputStream property of the request object as shown in Listing 4-35.

Listing 4-35. Reading the Request Body

def readText = {
    def text = request.inputStream.text
    render "You sent $text"
}

Writing a Binary Response

You can send a binary response to the client using standard servlet API calls such as the example in Listing 4-36, which uses the HttpServletResponse object to output binary data to the response in the form of a ZIP file.

Listing 4-36. Writing Binary Data to the Response

def createZip = {
  byte[] zip = ... // create the zip from some source
  response.contentType = "application/octet-stream"
  response.outputStream << zip
  response.outputSream.flush()
}

The code uses the response object's outputStream property in conjunction with Groovy's overloaded left shift << operator, which is present in a number of objects that output or append to something such as java.io.Writer and java.lang.StringBuffer, to name just a couple.

Using Simple Interceptors

Frequently, it is useful to catch the flow of method execution by intercepting calls to certain methods. This concept is the foundation of Aspect-Oriented Programming (AOP), which allows the definition of "pointcuts" (execution points) to be intercepted. You can then modify the intercepted execution through the use of before, after, and around "advice."

As the names suggest, before advice in AOP is code that can be executed before an intercepted method call; after advice is code that can be executed after an intercepted method call. Around advice is code that can replace the method call entirely. AOP's great strength is providing support for implementing cross-cutting concerns.

The example frequently used for this concept is the logging of method calls. Although Grails' interception mechanism by no means provides the same power and flexibility in terms of what pointcuts can be intercepted, it does fulfill the basic need of intercepting calls to actions on controllers.

Additionally, interceptors are useful if they apply only to a single controller. If your requirement spans multiple controllers, you're better off having a look at Filters (a topic covered in Chapter 14). With interceptors you can either intercept all actions or provide more fine-grained control by specifying which actions should be intercepted. Let's look at a few examples, starting with before interceptors.

Before Advice

Luckily, as with the rest of Grails, there is no hefty XML configuration or annotation trickery required, thanks to Convention over Configuration. All it takes to define a before interceptor is to create a closure property named beforeInterceptor within the target controller, as shown in Listing 4-37.

Listing 4-37. A beforeInterceptor

def beforeInterceptor = {
        log.trace("Executing action $actionName with params $params")
}

Listing 4-37 uses the log object to output tracing information before any action within the defining controller is executed. This example applies to every action defined in the controller. However, you can apply more fine-grained control using interception conditions.

As an example, say you wanted to trace each time a user views an Album and each user's country of residence. You could define a beforeInterceptor as shown in Listing 4-38.

Listing 4-38. Using Interception Conditions

class AlbumController {
        private trackCountry = {
                def country = request.locale.country
                def album = Album.get(params.id)
                 new AlbumVisit(country:country, album:album).save()
         }
def beforeInterceptor = [action:trackCountry, only: "show"]          ...
}

As you can see from Listing 4-38, you can define a beforeInterceptor using a map literal. The action key defines the code that should execute. In this case, we're using an only condition, which means that the interceptor applies only to the show action. You could change this to an except condition, in which case the interceptor would apply to all actions except the show action.

Finally, a beforeInterceptor can also halt execution of an action by returning false. For example, if you want to allow only U.S. visitors to your site, you could send a 403 forbidden HTTP code if the user hails from outside the U.S. (see Listing 4-39).

Listing 4-39. Halting Execution with a beforeInterceptor

class AlbumController {
        def beforeInterceptor = {
if(request.locale != Locale.US) {
                response.sendError 403
                return false
          }
        }
}

After Advice

After advice is defined using the unsurprisingly named afterInterceptor property that again takes a closure. The first argument passed to the closure is the resulting model from the action, as shown in Listing 4-40.

Listing 4-40. An afterInterceptor Example

def afterInterceptor = { model ->
      log.trace("Executed action $actionName which resulted in model: $model")
}

Again, in this rather trivial example, the logging mechanism traces any action that executes.

Testing Controllers

Grails provides a special ControllerUnitTestCase class that you can use to test controllers. Tests that extend from ControllerUnitTestCase are provided with mock implementations of the various Servlet API objects, such as the HttpServletRequest, as well as mock implementations of key methods such as render and redirect.

As an example, the AlbumController class as it stands has no test coverage. To create a test for this controller, you need create a new test class that follows the naming convention for the controller under test. For example, you can create a test for the AlbumController with the create-unit-test command:

grails create-unit-test com.g2one.gtunes.AlbumController

This will create a new unit test called AlbumControllerTests at the location test/unit/com/g2one/gtunes/AlbumControllerTests.groovy. Now you need to modify the test class to extend from the ControllerUnitTestCase test harness, as shown in Listing 4-41.

Listing 4-41. Using ControllerUnitTestCase

class AlbumControllerTests extends grails.test.ControllerUnitTestCase {
    ...
}

The ControllerUnitTestCase class extends from the parent GrailsUnitTestCase, which contains general mocking capabilities plus utility methods that enable you to mock the behavior of domain classes and controllers. For example, to test the list action of the AlbumController, you can write a trivial test that takes advantage of the mockDomain method (see Listing 4-42).

Listing 4-42. Mocking a Simple Action That Returns a Model

void testList() {
        mockDomain(Album, [ new Album(title: "Odelay"),
                            new Album(title: "Abbey Road"] )
        def model = controller.list()
        assertEquals 2, model.albumList.size()
}

In Listing 4-42, we're testing the returned model, but some controller actions write directly to the response or issue a redirect rather than return a value. To test an action that writes to the response, you can use the response object of the controller, which is an instance of the org.springframework.mock.web.MockHttpServletResponse class.

Several useful methods in the MockHttpServletResponse class allow you to inspect the state of the current response. In particular, the getContentAsString() method provides access to what is currently written into the response as a String. For example, if you have an action that renders some text to the response, you could test it as shown in Listing 4-43.

Listing 4-33. Testing the Contents of the Response

void testIndex() {
       controller.index()
       assertEquals "Welcome to the gTunes store!",
                     controller.response.contentAsString
}

For more complex usages of the render method, such as rendering a view and so on, you can use the renderArgs property of the ControllerUnitTestCase class, which provides a map of the named parameters given to the render method that executed last. For example, say you have a render method that renders a view with a model such as:

render(view: "show", model:[album:Album.get(params.id)])

You can test this code using the renderArgs property and mock the domain as shown in Listing 4-44.

Listing 4-44. Testing the render Method

void testShow() {
         mockDomain(Album, new Album(id:1, title: "Aha Shake Heartbreak"))
         mockParams.id = 1
         controller.show()
         assertEquals "show", renderArgs.view
         assertEquals 1, renderArgs.model.album.id
         assertEquals "Aha Shake Heartbreak", renderArgs.model.album.title
}

Notice the usage in Listing 4-44 of the ControllerUnitTestCase class's mockParams property. This property provides a mock implementation of the params object that you can populate with values before calling the controller. In addition to a mock implementation of the params object, the ControllerUnitTestCase class provides the following properties that mock various aspects of the controller API:

  • mockRequest: An instance of the org.springframework.mock.web.MockHttpServletRequest class that mocks the request object
  • mockResponse: An instance of the org.springframework.mock.web.MockHttpServletResponse class that mocks the response object
  • mockSession: An instance of the org.springframework.mock.web.MockHttpSession that provides a mock implementation of the session object
  • mockParams: A simple map that mocks the behavior of the params object
  • mockFlash: A simple map that mocks the behavior of the flash object

Additionally, you can test the redirect method as you test the render method, using the provided redirectArgs property of the ControllerUnitTestCase class. You'll see more examples of testing as we progress through the book, but in the meantime, let's exercise your new knowledge of controllers by implementing the gTunes application's first bit of real functionality.

Controllers in Action

In this section, you'll learn how to build a simple login and registration system using Grails controllers. In Chapter 14, we'll be refactoring this system to use one of Grails' more generic security plugins, but for the moment it will serve as a useful starting point.

One of the first things to consider when developing any site is the point of entry into the site. At the moment, you've just created a bunch of scaffolded pages, but now it's time to think about the real application for the first time, starting with the home page.

Creating the gTunes Home Page

The gTunes application is a music store where users can log in, browse the available music, and purchase music that they can then play. First, you need to establish a home page. You already have a StoreController, so you can use that as the controller that deals with the home page. To make sure visitors get routed to this controller, you can modify the grails-app/conf/UrlMappings.groovy file to map visitors to the root of the application to this controller (see Listing 4-45).

Listing 4-45. Routing Users to the Root of the Application to the StoreController

class UrlMappings {
    static mappings = {
                    "/"(controller:"store")
    }
}

Notice how you can use a forward slash to tell Grails to map any request to the root of the application to the StoreController. As you can see from the mapping, it is not mapping onto any particular action in StoreController, which will trigger the default action. The default action is the index action, which currently writes out a simple-text response. You need to change the index action so view delegation kicks in:

def index = {}

Now instead of returning a textual response, the index action delegates to the grails-app/views/store/index.gsp view, which you can use to render the home page. We'll start with something simple that just shows a welcome message; we can expand on this later. Listing 4-46 shows the markup code involved.

Listing 4-46. The gTunes Home Page

<html>
    <head>
        <meta http-equiv="Content-type" content="text/html; charset=utf-8">
        <meta name="layout" content="main">
        <title>gTunes Store</title>
    </head>
    <body id="body">
        <h1>Your online music store and storage service!</h1>
        <p>Manage your own library, browse music and purchase new tracks as they
           become available</p>
    </body>
</html>

The next step is to consider how to enable users to register, log in, and log out. Before you can do that, you need to define the notion of a user within the gTunes application. Let's do that in the next section.

Adding the User Domain Class

To model users, you'll need to create a User domain class that contains personal information such as first name and last name, as well as the login and password for each user. To do so, you can use the create-domain-class command:

grails create-domain-class com.g2one.gtunes.User

This will create a new domain class at the location grails-app/domain/com/g2one/gtunes/User.groovy. With that done, you need to populate the User domain class with a few properties, as shown in Listing 4-47.

Listing 4-47. The User Domain Class

package com.g2one.gtunes
class User {
          String login
          String password
          String firstName
          String lastName
          static hasMany = [purchasedSongs:Song]
}

As you can see, the code in Listing 4-47 captures only the basics about users, but you could easily expand this information to include an address, contact number, and so on. One property to note is the purchasedSongs association, which will hold references to all the Songs a User buys once you have implemented music purchasing.

However, before we get too far ahead of ourselves, let's add a few constraints to ensure domain instances stay in a valid state (see Listing 4-48).

Listing 4-48. Applying Constraints to the User Class

class User {
    ...
    static constraints = {
        login blank:false, size:5..15,matches:/[S]+/, unique:true
        password blank:false, size:5..15,matches:/[S]+/
        firstName blank:false
        lastName blank:false
    }
}

With these constraints in place, you can ensure that a user cannot enter blank values or values that don't fall within the necessary size constraints. Also, note the usage of the unique constraint, which ensures that the login property is unique to each User. We'll revisit this in more detail later; for now, let's focus on login and registration.

Adding a Login Form

Because you already have a home page, it might make sense to add the login form there. But further down the line, you'll want to allow users to browse the gTunes music catalog anonymously, so users should be able to log in from anywhere. With this in mind, you need to add a login form to the grails-app/views/layouts/main.gsp layout so that it's available on every page.

Listing 4-49 shows the GSP code to do so. Note how you can check whether a User already exists in the session object and display a welcome box or login form, accordingly.

Listing 4-49. Adding the Login Form Everywhere

<div id="loginBox" class="loginBox">
      <g:if test="${session?.user}">
            <div style="margin-top:20px">
      <div style="float:right;">
           <a href="#">Profile</a> |
                       <g:link controller="user" action="logout">Logout</g:link><br>
      </div>
      Welcome back
      <span id="userFirstName">${session?.user?.firstName}!</span>
      <br><br>
      You have purchased (${session.user.purchasedSongs?.size() ?: 0}) songs.<br>
              </div>
         </g:if>
         <g:else>
      <g:form
          name="loginForm"
          url="[controller:'user',action:'login']">
          <div>Username:</div>
          <g:textField name="login" ></g:textField>
          <div>Password:</div>
          <g:passwordField name="password" />
                                  <input type="submit" value="Login" />
      </g:form>
      <g:renderErrors bean="${loginCmd}"></g:renderErrors>
      </g:else>
</div>

In addition to providing a login box, you need to provide a link that allows a User to register. Once logged in, the user will be able to click through the store to browse and click a "My Music" link to view music already purchased. These links won't display when the user isn't logged in, so instead you can use the screen real estate for a prominent link to the registration page. Listing 4-50 shows the registration link added to the main.gsp layout.

After getting the web designers involved and making a few Cascading Style Sheets (CSS) tweaks, the home page has gone from zero to something a little more respectable (see Figure 4-3).

image

Figure 4-3. The gTunes home page

Implementing Registration

Before users can actually log in, they need to register with the site. You'll need to run the create-controller command to create a controller that will handle the site's login and registration logic:

grails create-controller com.g2one.gtunes.User

Once complete, the command will create a controller at the location grails-app/controllers/com/g2one/gtunes/UserController.groovy. Open up this controller and add a register action, as shown in Listing 4-51.

Listing 4-51. Adding a register Action

class UserController {
      def register = {}
}

As you can see from the example, the register action currently does nothing beyond delegating to a view. Nevertheless, it gives you the opportunity to craft a registration form. Listing 4-52 shows the shortened code from the grails-app/views/user/register.gsp view that will render the form.

Listing 4-52. The register View

<body id="body">
       <h1>Registration</h1>
    <p>Complete the form below to create an account!</p>
    <g:hasErrors bean="${user}">
                        <div class="errors">
       <g:renderErrors bean="${user}"></g:renderErrors>
                        </div>
    </g:hasErrors>
    <g:form action="register" name="registerForm">
       <div class="formField">
           <label for="login">Login:</label>
           <g:textField name="login" value="${user?.login}" />
       </div>
       <div class="formField">
           <label for="password">Password:</label>
           <g:passwordField name="password"
                            value="${user?.password}"/>
       </div>
       ...
       <g:submitButton class="formButton"
                       name="register"
                       value="Register" />
    </g:form>
</body>

The rendered registration form will look like the screenshot in Figure 4-4.

As you can see from Figure 4-4, you can also provide a confirm-password field to prevent users from entering their passwords incorrectly. With that done, let's consider the controller logic. To implement registration, you can take advantage of Grails' data-binding capabilities to bind incoming request parameters to a new User instance. At this point, validation takes over and the rest comes down to a little branching logic. Listing 4-53 shows the completed register action.

image

Figure 4-4. The Registration screen

Listing 4-53. Implementing the register Action

1 def register = {
2    if(request.method == 'POST') {
3        def u = new User(params)
4        if(u.password != params.confirm) {
5            u.errors.rejectValue("password", "user.password.dontmatch")
6            return [user:u]
7        }
8        else if(u.save()) {
9            session.user = u
10            redirect(controller:"store")
11        }
12        else {
13            return [user:u]
14        }
15   }
16 }

Many of the key concepts you've learned throughout the course of this chapter have been put to use in Listing 4-53, including a few new ones. Let's step through the code to see what's going on. First, on line 2, the code checks that the incoming request is a POST request because doing all this processing is pointless unless a form is submitted:

2    if(request.method == 'POST') {

Then on line 3, data binding takes over as it binds the incoming request parameters to the User instance:

3         def u = new User(params)

On lines 4 though 7, the code confirms whether the user has entered the correct password twice. If not, the password is rejected altogether:

4         if(u.password != params.confirm) {
5             u.errors.rejectValue("password", "user.password.dontmatch")
6             return [user:u]
7         }

Note how calling the rejectValue method of the org.springframework.validation.Errors interface accomplishes this. The rejectValue method accepts two arguments: the name of the field to reject and an error code to use. The code in Listing 4-53 uses the String user.password.dontmatch as the error code, which will appear when the <g:renderErrors> tag kicks in to display the errors. If you want to provide a better error message, you can open up the grails-app/i18n/messages.properties file and add a message like this:

user.password.dontmatch=The passwords specified don't match

Here's one final thing to note: directly after the call to rejectValue, a model from the controller action is returned, which triggers the rendering register.gsp so it can display the error.

Moving on to lines 8 through 11, you'll notice that the code attempts to persist the User by calling the save() method. If the attempt is successful, the User is redirected back to the StoreController:

8        else if(u.save()) {
9            session.user = u
10            redirect(controller:"store")
11        }

Finally, if a validation error does occur as a result of calling save(), then on line 13 a simple model is returned from the register action so that the register view can render the errors:

13             return [user:u]

Testing the Registration Code

Now let's consider how to test the action using the ControllerUnitTestCase class you learned about earlier. When you ran the create-controller command, a new unit test for the UserController was created for you in the test/unit directory.

You'll notice that the UserControllerTests class extends from a super class called ControllerUnitTestCase:

class UserControllerTests extends grails.test.ControllerUnitTestCase {

Now write a test for the case in which a user enters passwords that don't match. Listing 4-54 shows the testPasswordsDontMatch case that checks whether a password mismatch triggers a validation error.

Listing 4-54. The testPasswordsMatch Test Case

void testPasswordsMatch() {
    mockRequest.method = 'POST'
    mockDomain(User)
    mockParams.login = "joebloggs"
    mockParams.password = "password"
    mockParams.confirm = "different"
    mockParams.firstName = "Joe"
    mockParams.lastName = "Blogs"
    def model = controller.register()
    assert model?.user
    def user = model.user
    assert user.hasErrors()
    assertEquals "user.password.dontmatch", user.errors.password
}

Notice how the testPasswordsMatch test case populates the mockParams object with two passwords that differ. Then you have a call to the register action, which should reject the new User instance with a user.password.dontmatch error code. The last line of the test asserts that this is the case by inspecting the errors object on the User instance:

assertEquals "user.password.dontmatch", user.errors.password

The next scenario to consider is when a user enters invalid data into the registration form. You might need multiple tests that check for different kinds of data entered. Remember, you can never write too many tests! As an example of one potential scenario, Listing 4-55 shows a test that checks whether the user enters blank data or no data.

Listing 4-55. The testRegistrationFailed Test

void testRegistrationFailed() {
    mockRequest.method = 'POST'
    mockDomain(User)
    mockParams.login = ""
    def model = controller.register()
    assertNull mockSession.user
    assert model
    def user = model.user
    assert user.hasErrors()
    assertEquals "blank", user.errors.login
    assertEquals "nullable", user.errors.password
    assertEquals "nullable",
                 user.errors.firstName
    assertEquals "nullable", user.errors.firstName
}

Once again, you can see the use of the errors object to inspect that the appropriate constraints have been violated. Finally, you need to ensure two things to test a successful registration:

  • The User instance has been placed in the session object.
  • The request has been redirected appropriately.

Listing 4-56 shows an example of a test case that tests a successful user registration.

Listing 4-56. Testing Successful Registration

void testRegistrationSuccess() {
    mockRequest.method = 'POST'
    mockDomain(User)

    mockParams.login = "joebloggs"
    mockParams.password = "password"
    mockParams.confirm = "password"
    mockParams.firstName = "Joe"
    mockParams.lastName = "Blogs"

    def model = controller.register()
    assertEquals 'store',redirectArgs.controller
    assertNotNull mockSession.user
}

With the tests written, let's now consider how to allow users to log in to the gTunes application.

Allowing Users to Log In

Since you've already added the login form, all you need to do is implement the controller logic. A login process is a good candidate for a command object because it involves capturing information—the login and password—without needing to actually persist the data.

In this example you're going to create a LoginCommand that encapsulates the login logic, leaving the controller action to do the simple stuff. Listing 4-57 shows the code for the LoginCommand class, which is defined in the same file as the UserController class.

Listing 4-57. The LoginCommand

class LoginCommand {
    String login
    String password
    private u
    User getUser() {
        if(!u && login)
            u = User.findByLogin(login, [fetch:[purchasedSongs:'join']])
        return u
    }
    static constraints = {
        login blank:false, validator:{ val, cmd ->
            if(!cmd.user)
                return "user.not.found"
        }
        password blank:false, validator:{ val, cmd ->
            if(cmd.user && cmd.user.password != val)
                return "user.password.invalid"
        }
    }
}

The LoginCommand defines two properties that capture request parameters called login and password. The main logic of the code, however, is in the constraints definition. First, the blank constraint ensures that the login and/or password cannot be left blank. Second, a custom validator on the login parameter checks whether the user exists:

login blank:false, validator:{ val, cmd ->
    if(!cmd.user)
        return "user.not.found"
}

The custom validator constraint takes a closure that receives two arguments: the value and the LoginCommand instance. The code within the closure calls the getUser() method of the LoginCommand to check if the User exists. If the User doesn't exist, the code returns an error code—"user.not.found"—that signifies an error has occurred.

On the password parameter, another custom validator constraint checks whether the User has specified the correct password:

password blank:false, validator:{ val, cmd ->
    if(cmd.user && cmd.user.password != val)
        return "user.password.invalid"
}

Here the validator again uses the getUser() method of the LoginCommand to compare the password of the actual User instance with the value of the password property held by the LoginCommand. If the password is not correct, an error code is returned, triggering an error. You can add appropriate messages for each of the custom errors returned by the LoginCommand by adding them to the grails-app/i18n/messages.properties file:

user.not.found=User not found
user.password.invalid=Incorrect password

With that done, it's time to put the LoginCommand to use by implementing the login action in the UserController. Listing 4-58 shows the code for the login action.

Listing 4-58. The login Action

def login = { LoginCommand cmd ->
    if(request.method == 'POST') {
        if(!cmd.hasErrors()) {
            session.user = cmd.getUser()
            redirect(controller:'store')
        }
        else {
            render(view:'/store/index', model:[loginCmd:cmd])
        }
    }
    else {
        render(view:'/store/index')
    }
}

With the command object in place, the controller simply needs to do is what it does best: issue redirects and render views. Again, like the register action, login processing kicks in only when a POST request is received. Then if the command object has no errors, the user is placed into the session and the request is redirected to the StoreController.

Testing the Login Process

Testing the login action differs slightly from testing the register action due to the involvement of the command object. Let's look at a few scenarios that need to be tested. First, you need to test the case when a user is not found (see Listing 4-59).

Listing 4-59. The testLoginUserNotFound Test Case

void testLoginUserNotFound() {
    mockRequest.method = 'POST'
    mockDomain(User)
    MockUtils.prepareForConstraintsTests(LoginCommand)
    def cmd = new LoginCommand(login:"fred", password:"letmein")
    cmd.validate()
    controller.login(cmd)     assertTrue cmd.hasErrors()
    assertEquals "user.not.found", cmd.errors.login
    assertEquals "/store/index", renderArgs.view
}

As you can see from Listing 4-59, when testing command objects you have to explicitly create the command and call the validate() method on it. Notice also how you can use the prepareForConstraintsTests method of the grails.test.MockUtils class to mock the validation behavior of a command object:

MockUtils.prepareForConstraintsTests(LoginCommand)

You can the inspect the command for errors as demonstrated by the following two lines from Listing 4-59:

assertTrue cmd.hasErrors()
assertEquals "user.not.found", cmd.errors.login

The next scenario to test is when a user enters an incorrect password. Listing 4-60 shows the testLoginPasswordInvalid test case that demonstrates how to do this.

Listing 4-60. The testLoginPasswordInvalid Test Case

void testLoginPasswordInvalid() {
    mockRequest.method = 'POST'
    mockDomain(User, [new User(login:"fred", password:"realpassword")])
    MockUtils.prepareForConstraintsTests(LoginCommand)
    def cmd = new LoginCommand(login:"fred", password:"letmein")
    cmd.validate()
    controller.login(cmd)
    assertTrue cmd.hasErrors()
    assertEquals "user.password.invalid", cmd.errors.password
    assertEquals "/store/index", renderArgs.view
}

Unlike the example in Listing 4-59, the testLoginPasswordInvalid test case actually provides mock data using the mockDomain method:

mockDomain(User, [new User(login:"fred", password:"realpassword")])

The second argument of the mockDomain method provides the data that all the query methods should operate on. In this case, the code specifies a mock User instance that has a password with the value of "realpassword." Then you can use the LoginCommand to simulate the entry of an incorrect password:

def cmd = new LoginCommand(login:"fred", password:"letmein")

The remainder of the test is largely similar to Listing 4-59.

The last test to write is one that tests a successful login. Listing 4-61 shows how to do this.

Listing 4-61. The testLoginSuccess Test Case

void testLoginSuccess() {
    mockRequest.method = 'POST'
    mockDomain(User, [new User(login:"fred", password:"letmein")])
    MockUtils.prepareForConstraintsTests(LoginCommand)
    def cmd = new LoginCommand(login:"fred", password:"letmein")
    cmd.validate()
    controller.login(cmd)
    assertFalse cmd.hasErrors()
    assertNotNull mockSession.user
    assertEquals "store", redirectArgs.controller
}

The testLoginSuccess test case again uses the mockDomain method to set up the domain model, and then uses an appropriate LoginCommand to simulate a valid login. As you can see from the last two assertions, you can use the mockSession object to check whether the User instance has been placed in the session and inspect redirectArgs to ensure that an appropriate redirect has occurred.

Summary

And with that, you've implemented the login and registration process for the gTunes application. We'll present throughout the book many more examples of using controllers, but in this chapter you've obtained a strong grounding in the core concepts that apply to controllers.

From data binding and validation to command objects, Grails' controller mechanism offers you a lot of tools. To fully see how everything fits together, you'll need a strong understanding of Grails' view technology—Groovy Server Pages (GSP). In the next chapter, we'll take a much closer look at GSP and what it has to offer, with its dynamic tag libraries and templating mechanisms.

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

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