Grails命令对象数据绑定

时间:2011-04-15 13:38:42

标签: data-binding grails groovy command-objects

Grails有very good support用于将请求参数绑定到域对象及其关联。这主要依赖于检测以.id结尾的请求参数,并自动从数据库中加载这些参数。

但是,目前尚不清楚如何填充命令对象的关联。请看以下示例:

class ProductCommand {

    String name
    Collection<AttributeTypeCommand> attributeTypes 
    ProductTypeCommand productType
}

此对象与ProductTypeCommand具有单端关联,与AttributeTypeCommand具有多端关联。所有属性类型和产品类型的列表均可从此接口的实现中获得

interface ProductAdminService {
    Collection<AttributeTypeCommand> listAttributeTypes();
    Collection<ProductTypeCommand> getProductTypes();
}

我使用此界面填充GSP中的产品和属性类型选择列表。我还依赖 - 将此接口注入命令对象,并使用它来“模拟”命令对象上的attributeTypesproductType属性

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}
    }
}

实际发生的是attributeTypeIdsproductTypeId属性绑定到相关的请求参数,getter / setter“模拟”productTypeattributeTypes属性。是否有更简单的方法来填充命令对象的关联?

4 个答案:

答案 0 :(得分:14)

我在一些项目中看到的是使用Apache Commons Collections的Lazy *集合类。它使用这样的代码来懒惰地初始化命令关联:

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 {
  // ...
}

通过上面给出的例子,GSP可以引用关联指数

<g:textField name="attributes[0].someProperty" ...

这甚至适用于不存在的索引,因为对LazyList的每个get(index)调用都会评估列表是否已经在该位置上有一个元素,如果没有,列表将自动增大并返回指定的新对象工厂。

请注意,您也可以使用LazyMap来创建与延迟地图相似的代码:

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

<强>更新

Groovy 2.0(尚未成为Grails发行版的一部分)将附带对lazy和eager列表的嵌入式支持。我写了一篇关于这个主题的博客文章:

http://blog.andresteingress.com/2012/06/29/groovy-2-0-love-for-grails-command-objects/

<强>更新

随着Grails 2.2.0的发布,Groovy 2.0成为发行版的一部分。

http://blog.andresteingress.com/2012/06/29/groovy-2-0-love-for-grails-command-objects/

答案 1 :(得分:7)

您是否真的需要为attributeTypes和productType属性提供子命令?你没有使用PropertyEditorSupport绑定的任何原因? E.g:

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        
    }
}

(以及对于attributeType对象类似的东西),并在编辑注册器中注册这些:

import java.beans.PropertyEditorSupport
public class CustomEditorRegistrar implements PropertyEditorRegistrar {
    public void registerCustomEditors(PropertyEditorRegistry reg) {
        reg.registerCustomEditor(ProductType, new ProductTypeEditor())
        reg.registerCustomEditor(AttributeType, new AttributeTypeEditor())
    }
}

在您的resources.groovy中注册:

beans =
{
    customEditorRegistrar(CustomEditorRegistrar)
}

然后在你的Cmd中你只有:

class ProductCommand {
    String name
    List<AttributeType> attributeTypes = []
    ProductType productType
}

如果你需要实际的子命令关联,那么我已经做了类似于@Andre Steingress建议的事情,并结合了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
    //...
}

希望我没有误解你想要实现的目标:)

答案 2 :(得分:5)

我遇到了嵌套命令对象的同样问题,所以我做了以下解决方法:

  1. 我明确地将其他域对象作为参数添加到我的 控制器动作
  2. 此外,为嵌套的命令对象显式调用bindData() (通常是包装其他命令对象的命令对象 将成功绑定其数据,而无需绑定它 显式,这取决于你的视图命名约定)
  3. 然后我在这些命令对象上调用.Validate()
  4. 使用这些对象检查.hasErrors()
  5. 的错误
  6. 要保存您的域对象,还要明确指定每个嵌套对象 具有相应命令对象的属性

  7. 为了说明,这是一个伪代码示例:

    class CommandObjectBig{
    
        String name
        CommandObjectSmall details
    
        static constraints = {
          name (blank: false)
        }
    
    }
    
    
    class CommandObjectSmall{
    
        String address
    
        static constraints = {
          address (blank: false)
        }
    
    }
    

    在控制器中:

    .
    .
    .
    
    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
       .
       .
       .
    
    }
    .
    .
    .
    

    • 希望将来版本的grails可以解决这个问题,也许允许开发人员添加一个可选的params或params作用域来分配给每个命令对象,它可能很有用:)

答案 3 :(得分:1)

Grails中的命令对象

在Grails中,命令对象类似于域类,但不保留数据。在Grails中使用命令对象是在不需要创建域对象时执行数据绑定和验证的简单方法。

您需要做的第一件事就是描述您的命令对象。可以在包含将使用它的控制器的同一文件中执行。如果命令对象将由多个控制器使用,请在groovy源目录中进行描述。

声明命令对象

@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)
    }
}

使用命令对象

接下来您可能想要做的是将控制器中的操作接收的数据绑定到命令对象并验证它。

 def updateUserProfile(UserProfileInfoCO userProfileInfo) {     
  // data binding and validation
   if (!userProfileInfo.hasErrors()) {
      //do something
   } 

}