Grails validation fails after interchanging unique attribute values
Grails validation fails after interchanging unique attribute values
Hi, I am trying to create an interface where users can create some custom enumeration with translations for different languages. For example the user can create an enumeration "Movie Genre". For this enumeration there might be an enumeration-value "Comedy" for which there might exist one ore more enumeration-value-translations for several languages.
As there must only be one translation for a specific language, I added a unique constraint to the enumeration-value-translation domain class. These are my domain classes right now:
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')
}
}
This works pretty fine so far. My problem occures when I update two enumeration-value-translations of the same enumeration-value in a way that the languages interchange. For example I have an
- enumeration-value: "Comedy"
and some translations where the language is "accidentally" mixed up
- translations for "Comedy"
- language: german, value: "Comedy"
- language: english, value "Komödie"
if 开发者_StackOverflow中文版the user recognizes that he mixed up the language, he might want to swap the languages and save the enumeration again. And this is where my error occures, because after swapping the languages the enumeration-value-translations unique constraint validates to false.
To debug this i simply tryed to print out the error causing translations before and after i processed the params, so:
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...
}
}
}
wich results to:
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
at this state i havend deleted, or saved (or flushed in any way) anything - this is just the result after altering the objects. And as you can see, there really is no inconsistency in the actual data and the validation should'nt fail.
Might there be a mistake in the way i change the translations? I just fetched them by ID and simply updated the language - i tryed that out in a minimalistic example and it worked there... It also works if i just create a deep copy of all enumeration-values and enumeration-value-translations and store that instead (which means that the validation really should'nt fail), but i think this is really not the way it should be done...
Another strange thing is, that the validation only fails if I iterate through the data. If i dont touch the data at all, no error occures, but the data isn't saved too, meaning that the folowing lines are causing the validations to be evaluated at all:
enumeration.enumerationValues.each() { ev ->
ev.enumerationValueTranslations.each() { evt ->
}
}
thats why i strongly believe that there must be some non-trivial problem... please let me know if there is anything else you need to know.
thanks for any help
Let me take another try :)
I'm looking at UniqueConstraint.processValidate()
, and can suppose that its logic does not consider the exchange case.
Particularly, the code
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;
}
should iterate the obtained results
and verify that the field value STILL violates uniqueness. In case of exchange, the other instance should be picked from a cache and have a new field value.
So I'd suggest you create an own descendant of UniqueConstraint
and use it, unless anyone's going to patch Grails.
精彩评论