I'm working on building a createCriteria
dynamically. So far, so good:
obj
is the domain object(s) I want backrulesList
is a list of maps which hold the field to be searched on, the operator to use, and the value to search against
def c = obj.createCriteria()
l = c.list (max: irows, offset: offset) {
switch(obj){ //constrain results to those relevant to the user
case Vehicle:
eq("garage", usersGarage)
break
case Garage:
users {
idEq(user.id)
}
break
}
rulesList.each { rule ->
switch(rule['op']){
case 'eq':
eq("${rule['field']}", rule['value'])
break
case 'ne':
ne("${rule['field']}", rule['value'])
break
case 'gt':
gt("${rule['field']}", rule['value'])
break;
case 'ge':
ge("${rule['field']}", rule['value'])
break
case 'lt':
lt("${rule['field']}", rule['value'])
break
case 'le':
le("${rule['field']}", rule['value'])
break
case 'bw':
ilike("${rule['field']}", "${rule['value']}%")
break
case 'bn':
not{ilike("${rule['field']}", "${rule['value']}%")}
break
case 'ew':
ilike("${rule['field']}", "%${rule['value']}")
break
case 'en':
not{ilike("${rule['field']}", "%${rule['value']}")}
break
case 'cn':
ilike("${rule['field']}", "%${rule['value']}%")
break
case 'nc':
not{ilike("${rule['field']}", "%${rule['value']}%")}
break
}
}
}
}
The above code works fine and is only a little verbose-looking with the switch statements. But what if I want to add functionality to choose to match ANY of the rules or ALL of them? I would need to conditionally put the rules in an or{}
. I can't do something like
if(groupOp == 'or'){
or{
}
before I go through the rulesList and then
if(groupOp == 'or'){
}
}
afterward. All I can think to do is to repeat the code for each condition:
if(groupOp == 'or'){
or{
rulesList.each { rule ->
switch(rule['op']){
...
}
}
}
}
else{
rulesList.each { rule ->
switch(rule['op']){
...
}
}
Now the code is looking quite sloppy and repetitive. Suppose I want to search on a property of a property of the domain object? (Ex: I want to return vehicles whose tires are a certain brand; vehicle.tires.brand, or vehicles whose drivers match a name; vehicle.driver.name). Would I have to do something like:
switch(rule['op']){
case 'eq':
switch(thePropertiesProperty){
case Garage:
garage{
eq("${rule['field']}", rule['value'])
}
break
case Driver:
driver{
eq("${rule['field']}", rule['value'])
}
break
}
break
case 'ne':
...
}
First off, you can simplify your big switch by using a GString for the method name:
case ~/^(?:eq|ne|gt|ge|lt|le)$/:
"${rule['op']}"("${rule['field']}", rule['value'])
break
The same trick works for the and/or:
"${(groupOp == 'or') ? 'or' : 'and'}"() {
rulesList.each { rule ->
switch(rule['op']){
...
}
}
}
or you could assign the closure to a variable first and then call either or(theClosure)
or and(theClosure)
as appropriate. Finally, for the "property of a property" search, if you add
createAlias('driver', 'drv')
createAlias('garage', 'grg')
to the top of the criteria closure then you can query on things like eq('drv.name', 'Fred')
without having to add the intervening driver {...}
or garage {...}
node.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With