在Grails中向域对象添加动态字段

时间:2013-02-25 12:48:32

标签: grails gorm

我正在尝试找到一种向grails域类添加动态字段的方法。我确实找到了基于Burt文章的动态域类插件,但这对我们的需求来说太过分了。

假设我们有一个人类的域类:

class Person extends DynamicExtendableDomainObject {
    String firstName
    String lastName

    static constraints = {
        firstName(nullable: false, blank: false, maxSize: 50)
        lastName(nullable: false, blank: false)
    }
}

现在,客户 a 希望在此处拥有生日字段。通过使用某种管理工具,他在数据库中添加了这个额外的字段。

客户 b 希望还有一个字段中间名,因此他将字段中间名添加到此人。

现在我们实现了一个DynamicExtendableDomainObject类,Person类继承自该类。这会为每个继承此类的Domain类添加一个自定义字段,以将动态属性存储为JSON(类似于Perl中的KiokuDB存储它们)。

现在当Person被实例化时,我们想将这些动态属性添加到Person类,以便能够使用标准的Grails getter和setter以及Templating函数。

所以在客户 a 上我们可以使用脚手架,而人们会输出firstName,lastName,birthDate,在客户b上,脚手架会输出firstName,lastName,middleName。

将使用saveinterceptor实现属性的存储,将这些属性序列化为JSON并将它们存储在特殊字段中。

但是我们还没有找到一种在运行时将这些JSON属性动态添加到域类的方法。有没有一个好方法来处理这个?如果是这样,如何最好地实现这个?

1 个答案:

答案 0 :(得分:2)

您可以尝试通过在metaClass中展开getProperty(),setProperty(),setProperties()然后使用beforeUpdate(),beforeInsert()和afterLoad,在运行时将属性添加到类型DynamicExtendableDomainObject的DomainClass中()挂钩持久性。

例如在Bootstrap(或服务)中:

def yourDynamicFieldDefinitionService

for(GrailsClass c in grailsApplication.getDomainClasses()){
    if(DynamicExtendableDomainObject.isAssignableFrom(c.clazz)){
        Set extendedFields = yourDynamicFieldDefinitionService.getFieldsFor(c.clazz)

        //getProperty()
        c.clazz.metaClass.getProperty = { String propertyName ->
            def result
            if(extendedFields.contains(propertyName)){
                result = delegate.getExtendedField(propertyName)
            } else {
                def metaProperty = c.clazz.metaClass.getMetaProperty(propertyName)
                if(metaProperty) result = metaProperty.getProperty(delegate)
            }
            result
        }

        //setProperty()
        c.clazz.metaClass.setProperty = { propertyName , propertyValue ->
                    if(extendedFields.contains(propertyName)){
                        delegate.setExtendedField(propertyName, propertyValue)
                        delegate.blobVersionNumber += 1
                    } else {
                        def metaProperty = c.clazz.metaClass.getMetaProperty(propertyName)
                        if(metaProperty) metaProperty.setProperty(delegate, propertyValue)
                    }
                }

        //setProperties()
                def origSetProperties = c.clazz.metaClass.getMetaMethod('setProperties',List)
                c.clazz.metaClass.setProperties = { def properties ->
                    for(String fieldName in extendedFields){
                        if(properties."${fieldName}"){
                            delegate."${fieldName}" = properties."${fieldName}"
                        }
                    }
                    origSetProperties.invoke(delegate,properties)
                }
    }
}

abstract DynamicExtendableDomainObject {
    String yourBlobField
    Long blobVersionNumber //field to signal hibernate that the instance is 'dirty'

    Object getExtendedField(String fieldName){
        ...
    }

    void setExtendedField(String fieldName, Object value){
        ...
    }

    def afterLoad(){
        //fill your transient storage to support getExtendedField + setExtendedField 
    }

    def beforeUpdate(){
        //serialize your transient storage to yourBlobField
    }

    def beforeInsert(){
        //serialize your transient storage to yourBlobField
    }
}