data binding - Grails comando objeto de enlace de datos
data-binding groovy (4)
Grails tiene una compatibilidad muy buena para vincular los parámetros de solicitud a un objeto de dominio y sus asociaciones. Esto se basa principalmente en la detección de parámetros de solicitud que finalizan con .id
y que se cargan automáticamente desde la base de datos.
Sin embargo, no está claro cómo completar las asociaciones de un objeto de comando. Toma el siguiente ejemplo:
class ProductCommand {
String name
Collection<AttributeTypeCommand> attributeTypes
ProductTypeCommand productType
}
Este objeto tiene una asociación de ProductTypeCommand
único con ProductTypeCommand
y una asociación de muchos extremos con AttributeTypeCommand
. La lista de todos los tipos de atributos y tipos de productos está disponible desde una implementación de esta interfaz
interface ProductAdminService {
Collection<AttributeTypeCommand> listAttributeTypes();
Collection<ProductTypeCommand> getProductTypes();
}
Uso esta interfaz para rellenar las listas de selección de productos y atributos en un GSP. También intervine -inject this interface en el objeto de comando, y lo uso para "simular" attributeTypes
y productType
propiedades en el objeto de comando
class ProductCommand {
ProductAdminService productAdminService
String name
List<Integer> attributeTypeIds = []
Integer productTypeId
void setProductType(ProductTypeCommand productType) {
this.productTypeId = productType.id
}
ProductTypeCommand getProductType() {
productAdminService.productTypes.find {it.id == productTypeId}
}
Collection<AttributeTypeCommand> getAttributeTypes() {
attributeTypeIds.collect {id ->
productAdminService.getAttributeType(id)
}
}
void setAttributeTypes(Collection<AttributeTypeCommand> attributeTypes) {
this.attributeTypeIds = attributeTypes.collect {it.id}
}
}
Lo que realmente ocurre es que las propiedades attributeTypeIds
y productTypeId
están ligadas a los parámetros de solicitud relevantes y los getters / setters "simulan" las propiedades de tipo de productType
y attributeTypes
. ¿Hay una manera más simple de poblar las asociaciones de un objeto de comando?
¿De verdad necesita tener subcomandos para attributeTypes y propiedades de productType? ¿Alguna razón por la que no está utilizando el enlace PropertyEditorSupport? P.ej:
public class ProductTypeEditor extends PropertyEditorSupport
{
ProductAdminService productAdminService // inject somewhow
void setAsText(String s)
{
if (s) value = productAdminService.productTypes.find { it.id == s.toLong() }
}
public String getAsText()
{
value?.id
}
}
(y algo similar para el objeto attributeType), y registrarlos en un registrador de editor:
import java.beans.PropertyEditorSupport
public class CustomEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry reg) {
reg.registerCustomEditor(ProductType, new ProductTypeEditor())
reg.registerCustomEditor(AttributeType, new AttributeTypeEditor())
}
}
Y regístrese en sus recursos.groovy:
beans =
{
customEditorRegistrar(CustomEditorRegistrar)
}
entonces en tu Cmd solo tienes:
class ProductCommand {
String name
List<AttributeType> attributeTypes = []
ProductType productType
}
Si necesitas asociaciones de subcomandos reales, he hecho algo similar a lo que sugirió Andrew Steingress, en combinación con el enlace PropertyEditorSupport:
// parent cmd
import org.apache.commons.collections.ListUtils
import org.apache.commons.collections.FactoryUtils
public class DefineItemConstraintsCmd implements Serializable
{
List allItemConstraints = ListUtils.lazyList([], FactoryUtils.instantiateFactory(ItemConstraintsCmd))
//...
}
// sub cmd
@Validateable
class ItemConstraintsCmd implements Serializable
{
Item item // this has an ItemEditor for binding
//...
}
Espero no haber entendido mal lo que intentas lograr :)
He enfrentado el mismo problema con los objetos de comando anidados, así que hice la siguiente solución alternativa:
- Agregué explícitamente los otros objetos de dominio como parámetros a mi acción de controlador
- También, explícitamente llamado bindData () para los objetos de comando anidados (generalmente, el objeto de comando que envuelve los otros objetos de comando vinculará sus datos con éxito sin la necesidad de vincularlo explícitamente, esto depende de su convención de nomenclatura de vista)
- Luego llamé .Validate () en esos objetos de comando
- Use estos objetos para verificar si hay errores con .hasErrors ()
- Para guardar su objeto de dominio, asigne explícitamente también cada propiedad anidada con su objeto de comando correspondiente
Para ilustrar, aquí hay un pseudo código de muestra:
class CommandObjectBig{
String name
CommandObjectSmall details
static constraints = {
name (blank: false)
}
}
class CommandObjectSmall{
String address
static constraints = {
address (blank: false)
}
}
En el controlador:
.
.
.
def save = { CommandObjectBig cob, CommandObjectSmall cos ->
//assuming cob is bounded successfully by grails, and we only need to handle cos
bindData(cos, params.details)
cos.validate()
//then do you code logic depending on if cos or cob has errors
if(cob.hasErrors() || cos.hasErrors())
render(view: "create", model: [bigInstance: cob, smallInstance: cos])
}
else
{
//create the Domain object using your wrapper command object, and assign its details
//property it''s value using cos command object instance, and call the save on you
//command object and every thing should go smoothly from there
.
.
.
}
.
.
.
- Espero que las futuras versiones de Grails tengan una solución para este problema, y tal vez permitan al desarrollador agregar un parámetro opcional o un alcance de params para ser asignado a cada objeto de comando, también podría ser útil :)
Lo que he visto en algunos proyectos fue el uso de las clases de colección Lazy * de Apache Commons Collections. Utilizó un código como este para inicializar de forma lenta una asociación de comando:
class ProductCommand {
String name
String type
List<AttributeTypeCommand> attributes = org.apache.commons.collections.list.LazyList.decorate(new ArrayList(), new org.apache.commons.collections.functors.InstantiateFactory(AttributeTypeCommand.class))
}
class AttributeTypeCommand {
// ...
}
Con el ejemplo anterior, el SGP podría hacer referencia a los índices de asociación
<g:textField name="attributes[0].someProperty" ...
Esto funciona incluso para índices que no existen ya que cada llamada a get (index) en LazyList evalúa si la lista ya tiene un elemento en esa posición y si no, la lista crecerá automáticamente en tamaño y devolverá un nuevo objeto de la fábrica especificada.
Tenga en cuenta que también puede usar LazyMap para crear un código similar con mapas flojos:
http://commons.apache.org/collections/apidocs/org/apache/commons/collections/map/LazyMap.html
http://commons.apache.org/collections/apidocs/org/apache/commons/collections/list/LazyList.html
Actualizar:
Groovy 2.0 (que aún no forma parte de la distribución de Grails) vendrá con soporte incrustado para listas perezosas y entusiastas. Escribí una publicación de blog sobre este tema:
http://blog.andresteingress.com/2012/06/29/groovy-2-0-love-for-grails-command-objects/
Actualizar:
Con el lanzamiento de Grails 2.2.0, Groovy 2.0 forma parte de la distribución.
http://blog.andresteingress.com/2012/06/29/groovy-2-0-love-for-grails-command-objects/
Objeto de comando en Grails
En Grails, los objetos de comando son como clases de dominio, pero no persisten los datos. El uso de objetos de comando en Grails es una forma sencilla de realizar el enlace de datos y la validación cuando no hay necesidad de crear un objeto de dominio.
Lo primero que deberá hacer es describir su objeto de comando. Está bien hacer en el mismo archivo que contiene el controlador que lo usará. Si el objeto de comando será utilizado por más de un controlador, descríbalo en el maravilloso directorio de origen.
Declaración de objetos de comando
@Validateable
class UserProfileInfoCO {
String name
String addressLine1
String addressLine2
String city
String state
String zip
String contactNo
static constraints = {
name(nullable: false, blank: false)
addressLine1(nullable: true, blank: true)
addressLine2(nullable: true, blank: true)
city(nullable: true, blank: true)
state(nullable: true, blank: true)
zip(nullable: true, blank: true, maxSize: 6, matches: "[0-9]+")
contactNo(blank: true, nullable: true)
}
}
Uso de objetos de comando
Lo siguiente que probablemente querrá hacer es vincular los datos, que se están recibiendo por acción en su controlador al objeto de comando y validarlo.
def updateUserProfile(UserProfileInfoCO userProfileInfo) {
// data binding and validation
if (!userProfileInfo.hasErrors()) {
//do something
}
}