【问题标题】:Grails command object data bindingGrails 命令对象数据绑定
【发布时间】:2011-08-06 08:44:45
【问题描述】:

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 属性。有没有更简单的方法来填充命令对象的关联?

【问题讨论】:

  • 我不能回答你的问题,但我个人认为这是一个很好的插件材料,甚至是未来 Grails 版本中的一个功能。

标签: data-binding grails groovy command-objects


【解决方案1】:

我在一些项目中看到的是使用 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 发行版的一部分)将嵌入对惰性列表和渴望列表的支持。我写了一篇关于这个主题的博文:

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/

【讨论】:

  • "对 LazyList 的每个 get(index) 调用都会评估列表是否已经在该位置有一个元素" 当然,您想要的是列表在添加项目时自动增长 到列表中,因为当请求参数绑定到命令对象时会发生这种情况?
  • 例如,如果引用了“attriutes[0]”,但属性列表为空,则 InstantiateFactory 将使用给定类的默认构造函数并创建一个新实例并将其添加到索引 0 . 此实例用于绑定请求参数与“attributes[0].xxx”
  • 但在我看来,Grails 会尝试做的第一件事是将项目设置到列表中,而LazyList 的文档清楚地表明列表会增长仅当从列表中检索到项目时
  • Grails 必须首先执行“attributes[0]”,这会导致 List.get(index) 调用,在 LazyList/InstantiateFactory 的情况下,它将创建指定类的新实例.如果您使用对象初始化列表,则此机制不适用。
【解决方案2】:

你真的需要attributeTypes 和productType 属性的子命令吗?您不使用 PropertyEditorSupport 绑定的任何原因?例如:

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

(以及类似的属性类型对象),并在编辑器注册器中注册它们:

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

希望我没有误解你想要达到的目标:)

【讨论】:

  • 是的,您的 gsp 中将有一个与该属性相对应的字段。例如,对于一个隐藏输入: 用于单个关联,并且对于 1-M 一个,您将有多个具有相同名称的输入。在我的子命令版本中,输入将类似于 其中数组表示法将其映射到惰性列表中的位置(我不'但认为你不需要这样做)。
【解决方案3】:

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

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

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

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 的未来版本能够修复此问题,并可能允许开发人员添加一个可选的参数或参数范围以分配给每个命令对象,这可能会很有用:)

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-12-12
      • 1970-01-01
      • 2012-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多