Show Navigation

Using Command Objects To Handle Form Data

This guide will explain how you can use command objects to validate input data in a Grails application

Authors: Matthew Moss

Grails Version: 4.0.1

1 Grails Training

Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.

2 Getting Started

In this guide you are going to use Command Objects to process form submissions with automatic type conversion, custom validation, and simple error handling.

2.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

2.2 How to complete the guide

To get started do the following:

or

The Grails guides repositories contain two folders:

  • initial Initial project. Often a simple Grails app with some additional code to give you a head-start.

  • complete A completed example. It is the result of working through the steps presented by the guide and applying those changes to the initial folder.

To complete the guide, go to the initial folder

  • cd into grails-guides/command-objects-and-forms/initial

and follow the instructions in the next sections.

You can go right to the completed example if you cd into grails-guides/command-objects-and-forms/complete

3 What are command objects?

As described in the Grails documentation,

A command object is a class that is used in conjunction with data binding, usually to allow validation of data that may not fit into an existing domain class. A class is only considered to be a command object when it is used as a parameter of an action.

While a domain class can be used as a command object, so also can non-domain classes defined in other files (or even classes defined in the same file as the controller using the class).

4 Writing the Application

This guide will lead you through the process of creating a controller with actions that use command objects. With those command objects, you will learn to handle form inputs safely and easily, with a minimum of boilerplate.

4.1 Create the controller

Before we can create any actions using command objects, we need a controller. Create a PlayerController class to work with the existing domain object Player.

$ ./grailsw create-controller demo.Player
The first time you run a grails command, application dependencies will be downloaded form the Internet. Subsequent calls will be much faster.

The output of the command will look like:

| Created grails-app/controllers/demo/PlayerController.groovy
| Created src/test/groovy/demo/PlayerControllerSpec.groovy

You will now have a new, mostly empty controller named PlayerController.groovy in the grails-app/controllers/demo directory. Update the index action and add the show action to look like this:

/grails-app/controllers/demo/PlayerController.groovy
package demo

import grails.gorm.transactions.ReadOnly
import grails.gorm.transactions.Transactional
import org.springframework.http.HttpStatus

@ReadOnly
class PlayerController {

    def index() {
        respond Player.list(params), model: [playerCount: Player.count()]
    }

    def show(Player player) {
        respond player
    }
}

The domain class, views, and some sample data are provided for you. At this point, you should be able to run the application and navigate to http://localhost:8080/player/ to see a list of players, and click on a player’s name to view that individual player. However, none of the other actions (e.g. create, save, edit, update, delete) will work yet, not until you write those actions.

At any point, you can start the application by typing ./gradlew bootRun on the command line; it is running and ready when you see:

Grails application running at http://localhost:8080 in environment: development

Stop the application by pressing Ctrl-C.

4.2 Implement the create and save actions

Next, let’s add the ability to create new players. To PlayerController, add the create action.

/grails-app/controllers/demo/PlayerController.groovy
@SuppressWarnings(['FactoryMethodName', 'GrailsMassAssignment'])
def create() {
    respond new Player(params)
}

While this action does make use of the Player class, the instance of Player is not a command object since it is not an argument to the action. This should make sense: since the intent of the action is to create a new player object, there is no form or input handling necessary.

From the Player List (http://localhost:8080/player/index), click the New Player button to see the form to create a new player (located at http://localhost:8080/player/create). If you were to enter values into the form and click the Create button now, you would get a 404 Page Not Found error, since the save action is not defined yet. Add that now to PlayerController.

    def save(Player player) {
        // ...
    }

Here, player is a command object, because it is an argument to the action. Behind the scenes, Grails will recognize this and rewrite your action to fetch existing records (if necessary), bind input data to the command object, perform dependency injection, and validate the object. All of these tasks could be done manually, but by making use of a command object, all of this behavior is automatic and leaves your code uncluttered of so much boilerplate.

Arguably, the best feature here is Data Binding, part of the automatic behavior you get from using command objects. While on the create page, view the HTML source code (generated from the compiled create.gsp) and look for the <form> tag:

<form action="/player/save" method="post" >
    <fieldset class="form">
        <div class='fieldcontain required'>
          <label for='name'>Name<span class='required-indicator'>*</span></label>
          <input type="text" name="name" value="" required="" id="name" />
        </div>
        <div class='fieldcontain required'>
          <label for='game'>Game<span class='required-indicator'>*</span></label>
          <input type="text" name="game" value="" required="" id="game" />
        </div>
        <div class='fieldcontain'>
          <label for='region'>Region</label>
          <input type="text" name="region" value="" id="region" />
        </div>
        <div class='fieldcontain required'>
          <label for='wins'>Wins<span class='required-indicator'>*</span></label>
          <input type="number" name="wins" value="0" required="" min="0" id="wins" />
        </div>
        <div class='fieldcontain required'>
          <label for='losses'>Losses<span class='required-indicator'>*</span></label>
          <input type="number" name="losses" value="0" required="" min="0" id="losses" />
        </div>
    </fieldset>
    <fieldset class="buttons">
        <input type="submit" name="create" class="save" value="Create" id="create" />
    </fieldset>
</form>

The <input> tag names match exactly the properties of the domain class Player.

/grails-app/domain/demo/Player.groovy
package demo

class Player {
    String name
    String game
    String region
    int wins = 0
    int losses = 0

    static constraints = {
        name blank: false, unique: true
        game blank: false
        region nullable: true
        wins min: 0
        losses min: 0
    }
}

Data binding will take the form-submitted string values for name, game, and region (if set) and set those properties in your command object player. The form-submitted string values for wins and losses will also be set in player after automatic type-conversion to int. The convention of using the same name for domain class properties and input fields greatly simplifies coding and application development: view fields are pre-filled with values from domain class objects, and command objects are filled with submitted form data, requiring very little developer effort.

After binding, data validation will be done; as this is a domain class, any constraints set in the class’s constraits block will be checked. This gives us the opportunity to add simple error-checking. Near the top of PlayerController.groovy, add:

import org.springframework.http.HttpStatus

Then update the save action of PlayerController.

/grails-app/controllers/demo/PlayerController.groovy
@Transactional
def save(Player player) {
    if (player == null) {
        render status: HttpStatus.NOT_FOUND
        return
    }

    if (player.hasErrors()) {
        respond player.errors, view: 'create'
        return
    }
}

The existing, generated gsp files include Javascript to do as much client-side form validation as possible. To see the Grails server-side validation and error checking, try using curl. On the command line (while the Grails application is running), type:

$ curl --request POST --form "name=Bob Smith" --form "wins=42" --form "losses=abc" "http://localhost:8080/player/save.json"

You should receive the following response (displayed nicely here):

{"errors": [
        {"object": "demo.Player",
         "field": "losses",
         "rejected-value": "abc",
         "message": "Property losses is type-mismatched"},

        {"object": "demo.Player",
         "field": "game",
         "rejected-value": null,
         "message": "Property [game] of class [class demo.Player] cannot be null"}
]}

We are going to verify this with a Functional Test.

Functional Tests involve making HTTP requests against the running application and verifying the resultant behaviour.

We use the Micronaut HTTP Client.

Ensure you have the dependency io.micronaut:micronaut-http-client

/src/integration-test/groovy/demo/demo/PlayerControllerSpec.groovy
package demo

import grails.testing.spock.OnceBefore
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification
import grails.testing.mixin.integration.Integration

@SuppressWarnings(['LineLength', 'MethodName'])
@Integration
class PlayerControllerFuncSpec extends Specification {

    @Shared
    @AutoCleanup
    private HttpClient client

    @SuppressWarnings(['JUnitPublicNonTestMethod'])
    @OnceBefore
    void init() {
        String baseUrl = "http://localhost:$serverPort" (1)
        this.client  = HttpClient.create(baseUrl.toURL())
    }

    @SuppressWarnings(['JUnitTestMethodWithoutAssert'])
    void 'test save validation'() {
        when:
        Map<String, Object> json = [name : 'Bob Smith',
                                    wins : 42,
                                    losses : 'abc',]
        client.toBlocking().exchange(HttpRequest.POST('/player/save', json)
                .accept(MediaType.APPLICATION_JSON_TYPE), Map) (2)

        then:
        HttpClientResponseException e = thrown()
        e.status == HttpStatus.UNPROCESSABLE_ENTITY (3)
        e.response.body().errors.size() == 2
        e.response.body().errors.find { it.field == 'losses' }.message == 'Property losses is type-mismatched'
        e.response.body().errors.find { it.field == 'game' }.message ==
                'Property [game] of class [class demo.Player] cannot be null'
    }
}
1 serverPort property is automatically injected. It contains the random port where the Grails application runs during the functional test.
2 If your client accepts JSON it is always a good idea to set the Accept Http Header. You will receive a JSON response instead of the HTML page.
3 Since command object validation failed, the server returns the Http Status - 422 Unprocessable Entity

Now that data binding, validation, and error-checking is done, update the save action to actually save the object and respond to the form submission. This completes the action.

@Transactional
def save(Player player) {
    if (player == null) {
        render status: HttpStatus.NOT_FOUND
        return
    }

    if (player.hasErrors()) {
        respond player.errors, view: 'create'
        return
    }

    player.save flush: true

    request.withFormat {
        form multipartForm { redirect player }
        '*' { respond player, status: HttpStatus.CREATED }
    }
}

You should now be able to successfully create and save new players to the database.

The example controller code presented here for save and other actions in this guide is kept intentionally simple in order to focus on command objects. You will most likely want additional functionality in your actions. When building your own controllers, a good starting point is to generate your initial controller with the Grails generate-controller script:

./grailsw generate-controller demo.Player

4.3 Implement the edit and update actions

Next, let’s add the ability to update existing players. Add edit and update actions to PlayerController. You should notice that these are almost the same as the create and save actions.

    def edit(Player player) {
        respond player
    }

    def update(Player player) {
        if (player == null) {
            render status: HttpStatus.NOT_FOUND
            return
        }

        if (player.hasErrors()) {
            respond player.errors, view: 'edit'
            return
        }

        player.save flush: true

        request.withFormat {
            form multipartForm { redirect player }
            '*' { respond player, status: HttpStatus.OK }
        }
    }

The edit action starts with a Player command object; Grails fetches an existing Player object automatically, and the properties of that object are loaded as the initial values of the form in edit.gsp. The update action is the same as save, except that errors redirect back to edit rather than create, and the HTTP status for success is OK rather than CREATED.

On the assumption that a player’s win and loss counts will be managed elsewhere, the wins and losses fields have already been removed from the form in edit.gsp. While running the application, go to the player list and click on one of the player names. While you can show the win/loss count, clicking the Edit button will display a form that does not let you change the win/loss counts.

But this isn’t sufficient: removing input fields from the form does not prevent those fields from being changed. A clever user can submit the form by other means that will change those values.

$ curl --request GET "http://localhost:8080/player/show/1.json"

{"id":1, "game":"Pandemic", "losses":30, "name":"Alexis Barnett", "region":"EAST", "wins":96}

$ curl --request POST --form "id=1" --form "wins=2" --form "losses=194" "http://localhost:8080/player/update.json"

After the second curl command, player Alexis Barnett will have only two wins and 194 losses, rather than the 96 wins and 30 losses she should have.

One way to fix this would be to avoid the use of the Player command object (so as to avoid automatic filling of properties) and instead manage all of the loading, binding, and verification manually. You could customize the action’s behavior and ignore any attempts to modify the wins and losses properties.

But command objects are not required to be a domain class: use any class! Define the following class in PlayerController.groovy below the PlayerController class. Note that the property names of PlayerInfo match those of the form’s <input> tags; when the form is submitted, form values will be bound to the command object’s properties where the names match.

class PlayerInfo {
    String name
    String game
    String region
}

Change the update action to use PlayerInfo as the command object. Load the appropriate Player instance, then update and save it using the command object’s properties.

    def update(PlayerInfo info) {
        Player player = Player.get(params.id)
        if (player == null) {
            render status: HttpStatus.NOT_FOUND
            return
        }

        player.properties = info.properties
        player.save flush: true

        if (player.hasErrors()) {
            respond player.errors, view: 'edit'
            return
        }

        request.withFormat {
            form multipartForm { redirect player }
            '*' { respond player, status: HttpStatus.OK }
        }
    }

Since PlayerInfo does not contain wins or losses, those values will not be accepted from the form submission, even if values are submitted for those names.

$ curl --request GET "http://localhost:8080/player/show/4.json"

{"id":4, "name":"Catherine Newton", "game":"Scythe", "region":"WEST", "wins":66, "losses":40}

$ curl --request POST --form "id=4" --form "name=June Smith" --form "game=Chess" --form "region=NORTH" --form "wins=0" --form "losses=10" "http://localhost:8080/player/update.json"

$ curl --request GET "http://localhost:8080/player/show/4.json"

{"id":4, "name":"June Smith", "game":"Chess", "region":"NORTH", "wins":66, "losses":40}

Note that the name, game, and region properties have all updated, but the wins and losses properties have not changed despite our efforts to do so. By using the PlayerInfo command object, we’ve restricted what fields are accepted from the form submission.

Create a Unit Test to validate this behaviour

/src/test/groovy/demo/PlayerControllerSpec.groovy
package demo

import spock.lang.Specification
import grails.testing.gorm.DomainUnitTest
import grails.testing.web.controllers.ControllerUnitTest

@SuppressWarnings(['MethodName', 'DuplicateListLiteral', 'DuplicateNumberLiteral', 'LineLength'])
class PlayerControllerSpec extends Specification implements ControllerUnitTest<PlayerController>, DomainUnitTest<Player> {

    @SuppressWarnings(['JUnitPublicNonTestMethod'])
    def "test update"() {
        when:
        def  player = new Player(name: 'Sergio', game: 'XCOM: Enemy Unkown', region: 'Spain', wins: 3, losses: 2)
        player.save()
        params.id = player.id
        params.name = 'Sergio del Amo'
        params.game = 'XCOM 2'
        params.region = 'USA'
        params.wins = 4
        controller.update() (1)

        then: 'respond model has no errors'
        !model.player.hasErrors()

        and: 'player properties have been updated correctly'
        model.player.name == 'Sergio del Amo'
        model.player.game == 'XCOM 2'
        model.player.region == 'USA'

        and: 'non existing properties in the command object have not been modified'
        model.player.wins == 3
        model.player.losses == 2
    }
}
1 Call a no argument version of the update method. Put the values in the params map. Databinding will map those values to the Command Object. This emulates what happens at runtime.

The current solution is lacking, though. Because PlayerInfo is now the command object and not Player, validation is not done until the call to player.save. It would be better if validation was done earlier, as it had been done when Player was the command object.

To resolve this, add a constraints block to PlayerInfo. When you defined the command object class PlayerInfo in the PlayerController.groovy file, Grails recognized it as a related command object class and made the class able to be validated. Change PlayerInfo and add constraints.

/grails-app/controllers/demo/PlayerController.groovy
class PlayerInfo {
    String name
    String game
    String region

    static constraints = {
        name blank: false
        game blank: false
        region nullable: true
    }
}

Create a unit test to validate the constraints defined in the command object.

/src/test/groovy/demo/PlayerInfoSpec.groovy
package demo

import spock.lang.Specification

@SuppressWarnings(['MethodName', 'DuplicateListLiteral'])
class PlayerInfoSpec extends Specification {

    @SuppressWarnings(['JUnitPublicNonTestMethod'])
    def "test PlayerInfo.region can be null"() {
        expect:
        new PlayerInfo(region: null).validate(['region'])
    }

    @SuppressWarnings(['JUnitPublicNonTestMethod'])
    def "test PlayerInfo.name can be blank"() {
        when:
        def playerInfo = new PlayerInfo(name: '')

        then:
        !playerInfo.validate(['name'])
        playerInfo.errors['name'].code == 'blank'

        when:
        playerInfo = new PlayerInfo(name: null)

        then:
        !playerInfo.validate(['name'])
        playerInfo.errors['name'].code == 'nullable'
    }

    @SuppressWarnings(['JUnitPublicNonTestMethod'])
    def "test PlayerInfo.game can be blank"() {
        when:
        def playerInfo = new PlayerInfo(game: '')

        then:
        !playerInfo.validate(['game'])
        playerInfo.errors['game'].code == 'blank'

        when:
        playerInfo = new PlayerInfo(game: null)

        then:
        !playerInfo.validate(['game'])
        playerInfo.errors['game'].code == 'nullable'
    }
}

Revise the update action once more, returning error checking prior to the attempt to save.

/grails-app/controllers/demo/PlayerController.groovy
@Transactional
def update(PlayerInfo info) {
    if (info.hasErrors()) {
        respond info.errors, view: 'edit'
        return
    }
    Player player = Player.get(params.id)
    if (player == null) {
        render status: HttpStatus.NOT_FOUND
        return
    }

    player.properties = info.properties
    player.save flush: true

    request.withFormat {
        form multipartForm { redirect player }
        '*' { respond player, status: HttpStatus.OK }
    }
}

Command object classes can be defined elsewhere (for example, under the src/ folder hierarchy). They need not be in the same file as the controller using them. This might be useful to maintain a separation of concerns, or simply to make a command object class available to multiple controllers.

If your command object class is defined in the same source file as the controller using it, Grails will automatically make it Validateable, permitting the use of a static constraints block. If you define the class elsewhere and need constraints, you need to add an implements clause as displayed next:

class PlayerInfo implements grails.validation.Validateable {
    String name
    String game
    String region

    static constraints = {
        name blank: false
        game blank: false
        region nullable: true
    }
}

5 Running the Application

To run the application use the ./gradlew bootRun command which will start the application on port 8080.

6 Do you need help with Grails?

Object Computing, Inc. (OCI) sponsored the creation of this Guide. A variety of consulting and support services are available.

OCI is Home to Grails

Meet the Team