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:
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.
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
.
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):
index
, it becomes the default action.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, 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...')
// ...
}
}
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)
}
}
}
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
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.
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.
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
.
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.
Figure 4-2. Using flash scope
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}")
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.
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.
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.
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.
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.
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.
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
.
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.
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.
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 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.
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"] )
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.
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" />
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.
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.
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}" />
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
.
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.
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."
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.
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.
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:
<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.<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:
getSize()
method to allow uploads only of certain file sizes.isEmpty()
method.java.io.InputStream
using the getInputStream()
method.getContentType()
method.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.
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:
byte[]
, the file's bytes will be bound.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.
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"
}
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.
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.
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 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.
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
objectmockResponse
: An instance of the org.springframework.mock.web.MockHttpServletResponse
class that mocks the response
objectmockSession
: An instance of the org.springframework.mock.web.MockHttpSession
that provides a mock implementation of the session
objectmockParams
: A simple map that mocks the behavior of the params
objectmockFlash
: A simple map that mocks the behavior of the flash
objectAdditionally, 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.
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.
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.
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 Song
s 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.
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.
Listing 4-50. Adding a Link to the Registration Page
<div id="navPane">
<g:if test="${session.user}">
<ul>
<li><g:link controller="user" action="music">My Music</g:link></li>
<li><g:link controller="store" action="shop">The Store</g:link></a></li>
</ul>
</g:if>
<g:else>
<div id="registerPane">
Need an account?
<g:link controller="user" action="register">Signup now</g:link>
to start your own personal Music collection!
</div>
</g:else>
</div>
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).
Figure 4-3. The gTunes home page
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.
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.
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]
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:
User
instance has been placed in the session
object.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.
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
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.
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.