6.1.6 Data Binding - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith
Version: null
6.1.6 Data Binding
Data binding is the act of "binding" incoming request parameters onto the properties of an object or an entire graph of objects. Data binding should deal with all necessary type conversion since request parameters, which are typically delivered by a form submission, are always strings whilst the properties of a Groovy or Java object may well not be.Grails uses Spring's underlying data binding capability to perform data binding.Binding Request Data to the Model
There are two ways to bind request parameters onto the properties of a domain class. The first involves using a domain classes' Map constructor:def save() {
def b = new Book(params)
b.save()
}new Book(params). By passing the params object to the domain class constructor Grails automatically recognizes that you are trying to bind from request parameters. So if we had an incoming request like:/book/save?title=The%20Stand&author=Stephen%20King
title and author request parameters would automatically be set on the domain class. You can use the properties property to perform data binding onto an existing instance:def save() {
def b = Book.get(params.id)
b.properties = params
b.save()
}Data binding and Single-ended Associations
If you have aone-to-one or many-to-one association you can use Grails' data binding capability to update these relationships too. For example if you have an incoming request such as:/book/save?author.id=20
.id suffix on the request parameter and look up the Author instance for the given id when doing data binding such as:def b = new Book(params)null by passing the literal String "null". For example:/book/save?author.id=nullData Binding and Many-ended Associations
If you have a one-to-many or many-to-many association there are different techniques for data binding depending of the association type.If you have aSet based association (the default for a hasMany) then the simplest way to populate an association is to send a list of identifiers. For example consider the usage of <g:select> below:<g:select name="books"
from="${Book.list()}"
size="5" multiple="yes" optionKey="id"
value="${author?.books}" />books association.However, if you have a scenario where you want to update the properties of the associated objects the this technique won't work. Instead you use the subscript operator:<g:textField name="books[0].title" value="the Stand" /> <g:textField name="books[1].title" value="the Shining" />
Set based association it is critical that you render the mark-up in the same order that you plan to do the update in. This is because a Set has no concept of order, so although we're referring to books0 and books1 it is not guaranteed that the order of the association will be correct on the server side unless you apply some explicit sorting yourself.This is not a problem if you use List based associations, since a List has a defined order and an index you can refer to. This is also true of Map based associations.Note also that if the association you are binding to has a size of two and you refer to an element that is outside the size of association:<g:textField name="books[0].title" value="the Stand" /> <g:textField name="books[1].title" value="the Shining" /> <g:textField name="books[2].title" value="Red Madder" />
<g:textField name="books[0].title" value="the Stand" /> <g:textField name="books[1].title" value="the Shining" /> <g:textField name="books[5].title" value="Red Madder" />
List using the same .id syntax as you would use with a single-ended association. For example:<g:select name="books[0].id" from="${bookList}" value="${author?.books[0]?.id}" /><g:select name="books[1].id" from="${bookList}" value="${author?.books[1]?.id}" /><g:select name="books[2].id" from="${bookList}" value="${author?.books[2]?.id}" />
books List to be selected separately.Entries at particular indexes can be removed in the same way too. For example:<g:select name="books[0].id"
from="${Book.list()}"
value="${author?.books[0]?.id}"
noSelection="['null': '']"/>books0 if the empty option is chosen.Binding to a Map property works the same way except that the list index in the parameter name is replaced by the map key:<g:select name="images[cover].id"
from="${Image.list()}"
value="${book?.images[cover]?.id}"
noSelection="['null': '']"/>Map property images under a key of "cover".Data binding with Multiple domain classes
It is possible to bind data to multiple domain objects from the params object.For example so you have an incoming request to:/book/save?book.title=The%20Stand&author.name=Stephen%20King
author. or book. which is used to isolate which parameters belong to which type. Grails' params object is like a multi-dimensional hash and you can index into it to isolate only a subset of the parameters to bind.def b = new Book(params.book)book.title parameter to isolate only parameters below this level to bind. We could do the same with an Author domain class:def a = new Author(params.author)Data Binding and Action Arguments
Controller action arguments are subject to request parameter data binding. There are 2 categories of controller action arguments. The first category is command objects. Complex types are treated as command objects. See the Command Objects section of the user guide for details. The other category is basic object types. Supported types are the 8 primitives, their corresponding type wrappers and java.lang.String. The default behavior is to map request parameters to action arguments by name:class AccountingController { // accountNumber will be initialized with the value of params.accountNumber
// accountType will be initialized with params.accountType
def displayInvoice(String accountNumber, int accountType) {
// …
}
}params.accountType request parameter has to be converted to an int. If type conversion fails for any reason, the argument will have its default value per normal Java behavior (null for type wrapper references, false for booleans and zero for numbers) and a corresponding error will be added to the errors property of the defining controller./accounting/displayInvoice?accountNumber=B59786&accountType=bogusValue
controller.errors.hasErrors() will be true, controller.errors.errorCount will be equal to 1 and controller.errors.getFieldError('accountType') will contain the corresponding error.If the argument name does not match the name of the request parameter then the @grails.web.RequestParameter annotation may be applied to an argument to express the name of the request parameter which should be bound to that argument:import grails.web.RequestParameterclass AccountingController { // mainAccountNumber will be initialized with the value of params.accountNumber // accountType will be initialized with params.accountType def displayInvoice(@RequestParameter('accountNumber') String mainAccountNumber, int accountType) { // … } }
Data binding and type conversion errors
Sometimes when performing data binding it is not possible to convert a particular String into a particular target type. This results in a type conversion error. Grails will retain type conversion errors inside the errors property of a Grails domain class. For example:class Book {
…
URL publisherURL
}Book that uses the java.net.URL class to represent URLs. Given an incoming request such as:/book/save?publisherURL=a-bad-url
a-bad-url to the publisherURL property as a type mismatch error occurs. You can check for these like this:def b = new Book(params)if (b.hasErrors()) { println "The value ${b.errors.getFieldError('publisherURL').rejectedValue}" + " is not a valid URL!" }
grails-app/i18n/messages.properties file to use for the error. You can use a generic error message handler such as:typeMismatch.java.net.URL=The field {0} is not a valid URLtypeMismatch.Book.publisherURL=The publisher URL you specified is not a valid URL
Data Binding and Security concerns
When batch updating properties from request parameters you need to be careful not to allow clients to bind malicious data to domain classes and be persisted in the database. You can limit what properties are bound to a given domain class using the subscript operator:def p = Person.get(1)p.properties['firstName','lastName'] = params
firstName and lastName properties will be bound.Another way to do this is is to use Command Objects as the target of data binding instead of domain classes. Alternatively there is also the flexible bindData method.The bindData method allows the same data binding capability, but to arbitrary objects:def p = new Person()
bindData(p, params)bindData method also lets you exclude certain parameters that you don't want updated:def p = new Person()
bindData(p, params, [exclude: 'dateOfBirth'])def p = new Person()
bindData(p, params, [include: ['firstName', 'lastName]])
