在Grails插件中定义全局约束

时间:2013-10-20 09:48:51

标签: grails groovy

在Grails中,您可以在项目的Config.groovy文件中定义全局约束,如下所示

grails.gorm.default.constraints = {
    myShared(nullable: false, blank: false)
}

并在域内使用它们

static constraints = {
    name(shared: "myShared")
}

由于我们的域类在几个Grails项目中被重用,因此它们被拆分为插件。插件的Config.groovy文件被排除在外,因此定义全局约束是行不通的。因此,我创建了一个Constraints.groovy文件,该文件将合并到包含域类的插件的插件描述符中的应用程序配置中。这有效,但我仍然得到运行主项目的以下异常(grails run-app):

Caused by GrailsConfigurationException: Property [test.plugin.TestDomain.name] references shared constraint [myShared:null], which doesn't exist!

在Grails核心进行一些调试之后,我发现在运行插件描述符之前,域类已经使用共享约束进行了初始化。

public DefaultGrailsDomainClass(Class<?> clazz, Map<String, Object> defaultConstraints)

构造函数中的映射包含共享约束。如果我将全局约束放在主项目的Config.groovy文件中,它包含已定义的约束,一切正常。但是如果我在插件描述符中合并它们,那么这个映射是空的,并且会抛出异常。

我的问题是,是否有可能以某种方式在Grails插件中定义全局约束?我可能错过了什么吗? 将全局约束复制到每个Grails项目中不应该是解决方案。另外,不使用其他插件来定义约束的解决方案也是首选。

我们正在使用Grails 2.2.4。

2 个答案:

答案 0 :(得分:1)

由于Grails初始化doWithSpring闭包中的约束,我认为你不能使用配置文件来完成它。

但是如果你看DomainClassGrailsPlugin,你就可以访问配置对象了。

def doWithSpring = {
  def config = application.config
  def defaultConstraintsMap = getDefaultConstraints(config)
  ...
}

所以我认为你可以做一些像(未经测试)

的事情
def loadBefore = ['domainClass']

def doWithSpring = {
  def config = application.config
  config.grails.gorm.default.constraints = {
    myShared(nullable: false, blank: false)
  }
}

答案 1 :(得分:1)

我对Sérgio的回答进行了进一步的调试,并提出了一个可能适用于某些项目的解决方案,而有些则不会。不幸的是,我们的Grails项目工作属于后一组项目...但首先要做的事情。这就是我所做的。

我设置了一个完全空的Grails项目和Grails插件项目。该插件包含在内,以反映我们在Grails项目中的工作情况。

我在插件的conf目录中创建了一个文件Constraints.groovy,如问题所述。

grails.gorm.default.constraints = {
    myShared(nullable: false, blank: false)
}

为了让这些共享约束可用于在插件中进行测试,我将此文件添加到Config.groovy中的配置位置。

grails.config.locations = [Constraints]

现在是为主项目提供这些约束的部分。这是通过向插件描述符添加一些行来实现的。

def loadBefore = ['domainClass']

Sérgio建议我更改了加载顺序。虽然我不确定这是否真的有必要。关于为什么我不确定这一点的更多细节。

def doWithSpring = {
    ConstraintEvalUtils.clearDefaultConstraints()

    mergeConfig(application)
}

protected mergeConfig(application) {
    application.config.merge(loadConfig(application))
}

protected loadConfig(application) {
    new ConfigSlurper(Environment.current.name).parse(application.classLoader.loadClass("Constraints"))
}

这两种方法负责将Constraints.groovy的内容加载和合并到应用程序配置中。但实际上诀窍是在合并之前调用ConstraintEvalUtils.clearDefaultConstraints()

/**
 * Looks up the default configured constraints from the given configuration
 */
public static Map<String, Object> getDefaultConstraints(ConfigObject config) {
    def cid = System.identityHashCode(config)
    if (defaultConstraintsMap == null || configId != cid) {
        configId = cid
        def constraints = config?.grails?.gorm?.default?.constraints
        if (constraints instanceof Closure) {
            defaultConstraintsMap = new ClosureToMapPopulator().populate((Closure<?>) constraints);
        }
        else {
            defaultConstraintsMap = Collections.emptyMap()
        }
    }
    return defaultConstraintsMap
}

此方法(也在ConstraintEvalUtils中)被调用以加载共享约束。正如您所看到的,结果会缓存在defaultConstraintsMap中。因此,如果在第一次调用此方法时共享约束为空,则它始终返回空映射。所以我调试了这个方法,以找出实际调用此方法的时间。它总是返回一张空地图。即使我在插件描述符中将loadBefore设置为core,该方法也会在插件描述符之前调用(不止一次)。正如我已经提到的ConstraintEvalUtils.clearDefaultConstraints()工作,它清理缓存的约束,默认约束可以从我将约束合并到的配置中新加载。

这就是它。实际上不需要添加很多行。它适用于我的测试项目,也可能适用于其他项目,但不适用于我们的Grails项目。如果我在那里工作,我会更新我的答案。