2

交换唯一属性值后 Grails 验证失败

嗨,我正在尝试创建一个界面,用户可以在其中创建一些带有不同语言翻译的自定义枚举。例如,用户可以创建枚举“Movie Genre”。对于这个枚举,可能存在一个枚举值“喜剧”,其中可能存在一个或多个针对多种语言的枚举值翻译。

由于特定语言只能有一个翻译,因此我为枚举-值-翻译域类添加了一个唯一约束。这些是我现在的域类:

class Enumeration {
    String label
    List<EnumerationValue> enumerationValues = new ArrayList<EnumerationValue>()

    static hasMany = [ enumerationValues: EnumerationValue ]
    static constraints = {
        label(nullable: false, blank: false)
        enumerationValues(nullable: true)
    }
}


class EnumerationValue {
    String label
    List<EnumerationValueTranslation> enumerationValueTranslations = new ArrayList<EnumerationValueTranslation>()

    static belongsTo = [ enumeration: Enumeration ]
    static hasMany = [ enumerationValueTranslations: EnumerationValueTranslation ]

    static constraints = {
        label(nullable: false, blank: false, unique: 'enumeration')
        enumeration(nullable: false) 
        enumerationValueTranslations(nullable: false)
    }
}


class EnumerationValueTranslation {
    String value
    Language language

    static belongsTo = [ enumerationValue: EnumerationValue ]

    static constraints = {
        value(nullable: false, blank: false)
        language(nullable: true, unique: 'enumerationValue')
        enumerationValue(nullable: false)

        /* unique constraint as mentioned in description text */
        language(unique: 'enumerationValue')
    }
}

到目前为止,这工作得很好。当我以语言交换的方式更新相同枚举值的两个枚举值翻译时,就会出现我的问题。例如我有一个

  • 枚举值:“喜剧”

以及一些语言“意外”混淆的翻译

  • “喜剧”的翻译
    • 语言:德语,价值:“喜剧”
    • 语言:英语,值“Komödie”

如果用户认识到他混淆了语言,他可能想要交换语言并再次保存枚举。这就是我的错误发生的地方,因为在交换语言后,枚举值翻译唯一约束验证为假。

为了调试这个,我只是尝试在处理参数之前和之后打印出导致翻译的错误,所以:

Enumeration enumeration = Enumeration.get(params['id']);

println "before:"
enumeration.enumerationValues.each() { enumValue ->
    enumValue.enumerationValueTranslations.each() { enumValueTr ->
        println enumValueTr;
        if(!enumValueTr.validate()) {
            // print errors...
        }
    }
}

// swap languages:
// (this are the lines of codes that are actually executed, and cause the 
// error. The actual processing of params looks different of course...)

// sets the language of "Comedy" to English
EnumerationValueTranslation.get(5).language = Language.get(1);
// sets the language of "Komödie" to German
EnumerationValueTranslation.get(6).language = Language.get(2);


println "after:"
enumeration.enumerationValues.each() { enumValue ->
    enumValue.enumerationValueTranslations.each() { enumValueTr ->
        println enumValueTr;
        if(!enumValueTr.validate()) {
            // print errors...
        }
    }
}

结果:

before:
EnumerationValueTranslation(value: Fantasy, language: en_US, enumerationValue: Fantasy)
EnumerationValueTranslation(value: Phantasie, language: de_DE, enumerationValue: Fantasy) 

EnumerationValueTranslation(value: Comedy, language: de_DE, enumerationValue: Comedy)
EnumerationValueTranslation(value: Komödie, language: en_US, enumerationValue: Comedy)


after:
EnumerationValueTranslation(value: Fantasy, language: en_US, enumerationValue: Fantasy)
EnumerationValueTranslation(value: Phantasie, language: de_DE, enumerationValue: Fantasy)

EnumerationValueTranslation(value: Comedy, language: en_US, enumerationValue: Comedy)
    validation fails: Property [language] of class [Translation] with value [Language(code: en_US)] must be unique
EnumerationValueTranslation(value: Komödie, language: de_DE, enumerationValue: Comedy)
    validation fails: Property [language] of class [Translation] with value [Language(code: de_DE)] must be unique

在这种状态下,我还没有删除或保存(或以任何方式刷新)任何东西——这只是改变对象后的结果。正如您所看到的,实际数据中确实没有不一致,并且验证不应该失败。

我更改翻译的方式可能有错误吗?我只是通过 ID 获取它们并简单地更新了语言 - 我在一个简约的示例中尝试了它并且它在那里工作......如果我只是创建所有枚举值和枚举值翻译的深层副本并存储它也可以工作相反(这意味着验证真的不应该失败),但我认为这真的不是它应该做的方式......

另一个奇怪的事情是,只有在我遍历数据时验证才会失败。如果我根本不接触数据,则不会发生错误,但也不会保存数据,这意味着以下几行会导致对验证进行评估:

enumeration.enumerationValues.each() { ev ->
    ev.enumerationValueTranslations.each() { evt ->

    }
}

这就是为什么我坚信一定有一些不平凡的问题......请让我知道您是否需要知道其他任何事情。

谢谢你的帮助

4

1 回答 1

3

让我再试一次:)

我在看UniqueConstraint.processValidate(),并且可以假设它的逻辑不考虑交换案例。

特别是代码

    boolean reject = false;
    if (id != null) {
        Object existing = results.get(0);
        Object existingId = null;
        try {
            existingId = InvokerHelper.invokeMethod(existing, "ident", null);
        }
        catch (Exception e) {
            // result is not a domain class
        }
        if (!id.equals(existingId)) {
            reject = true;
        }
    }
    else {
        reject = true;
    }

应该迭代获得results并验证字段值仍然违反唯一性。在交换的情况下,应该从缓存中挑选另一个实例并具有新的字段值。

所以我建议你创建一个自己的后代UniqueConstraint并使用它,除非有人要修补 Grails。

于 2011-06-01T08:03:53.203 回答