8.1.9 Command Objects - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari
Version: 3.1.1
8.1.9 Command Objects
Grails controllers support the concept of command objects. 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.Note: A class is only considered to be a command object when it is used as a parameter of an action.
Declaring Command Objects
Command object classes are defined just like any other class.class LoginCommand implements grails.validation.Validateable { String username String password static constraints = { username(blank: false, minSize: 6) password(blank: false, minSize: 6) } }
Validateable
trait. The Validateable
trait allows the definition of constraints just like in domain classes. If the command object is defined in the same source file as the controller that is using it, Grails will automatically make it Validateable
. It is not required that command object classes be validateable.By default, all Validateable
object properties are nullable: false
which matches the behavior of GORM domain objects. If you want a Validateable
that has nullable: true
properties by default, you can specify this by defining a defaultNullable
method in the class:class AuthorSearchCommand implements grails.validation.Validateable { String name Integer age static boolean defaultNullable() { true } }
name
and age
will allow null values during validation.Using Command Objects
To use command objects, controller actions may optionally specify any number of command object parameters. The parameter types must be supplied so that Grails knows what objects to create and initialize.Before the controller action is executed Grails will automatically create an instance of the command object class and populate its properties by binding the request parameters. If the command object class is marked withValidateable
then the command object will be validated. For example:class LoginController { def login(LoginCommand cmd) { if (cmd.hasErrors()) { redirect(action: 'loginForm') return } // work with the command object data } }
id
request parameter then instead of invoking the domain class constructor to create a new instance a call will be made to the static get
method on the domain class and the value of the id
parameter will be passed as an argument. Whatever is returned from that call to get
is what will be passed into the controller action. This means that if there is an id
request parameter and no corresponding record is found in the database then the value of the command object will be null
. If an error occurs retrieving the instance from the database then null
will be passed as an argument to the controller action and an error will be added the controller's errors
property. If the command object's type is a domain class and there is no id
request parameter or there is an id
request parameter and its value is empty then null
will be passed into the controller action unless the HTTP request method is "POST", in which case a new instance of the domain class will be created by invoking the domain class constructor. For all of the cases where the domain class instance is non-null, data binding is only performed if the HTTP request method is "POST", "PUT" or "PATCH".Command Objects And Request Parameter Names
Normally request parameter names will be mapped directly to property names in the command object. Nested parameter names may be used to bind down the object graph in an intuitive way. In the example below a request parameter namedname
will be bound to the name
property of the Person
instance and a request parameter named address.city
will be bound to the city
property of the address
property in the Person
.class StoreController { def buy(Person buyer) { // … } }class Person { String name Address address }class Address { String city }
class StoreController { def buy(Person buyer, Product product) { // … } }class Person { String name Address address }class Address { String city }class Product { String name }
name
it isn't clear if that should represent the name of the Product
or the name of the Person
. Another version of the problem can come up if a controller action accepts 2 command objects of the same type as shown below.class StoreController { def buy(Person buyer, Person seller, Product product) { // … } }class Person { String name Address address }class Address { String city }class Product { String name }
product.name
request parameter will be bound to the name
property in the product
argument, the buyer.name
request parameter will be bound to the name
property in the buyer
argument the seller.address.city
request parameter will be bound to the city
property of the address
property of the seller
argument, etc...Command Objects and Dependency Injection
Command objects can participate in dependency injection. This is useful if your command object has some custom validation logic which uses a Grails service:class LoginCommand implements grails.validation.Validateable { def loginService String username String password static constraints = { username validator: { val, obj -> obj.loginService.canLogin(obj.username, obj.password) } } }
loginService
bean which is injected by name from the Spring ApplicationContext
.Binding The Request Body To Command Objects
When a request is made to a controller action which accepts a command object and the request contains a body, Grails will attempt to parse the body of the request based on the request content type and use the body to do data binding on the command object. See the following example.// grails-app/controllers/bindingdemo/DemoController.groovy package bindingdemoclass DemoController { def createWidget(Widget w) { render "Name: ${w?.name}, Size: ${w?.size}" } }class Widget { String name Integer size }
$ curl -H "Content-Type: application/json" -d '{"name":"Some Widget","size":"42"}' localhost:8080/demo/createWidget Name: Some Widget, Size: 42 ~ $ $ curl -H "Content-Type: application/xml" -d '<widget><name>Some Other Widget</name><size>2112</size></widget>' localhost:8080/bodybind/demo/createWidget Name: Some Other Widget, Size: 2112 ~ $
// grails-app/controllers/bindingdemo/DemoController.groovy package bindingdemoclass DemoController { def createWidget(Widget w) { // this will fail because it requires reading the body, // which has already been read. def json = request.JSON // ... } }