data-binding grails groovy command-objects

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:

  1. Agregué explícitamente los otros objetos de dominio como parámetros a mi acción de controlador
  2. 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)
  3. Luego llamé .Validate () en esos objetos de comando
  4. Use estos objetos para verificar si hay errores con .hasErrors ()
  5. 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 } }