Build createCriteria in Grails in a dynamic and dry way?

I am working on building createCriteria dynamically. So far so good:

obj is the domain object I want to return

rulesList is a list of cards in which to search by the desired field, the operator used and the value to search by

 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 with switch statements. But what if I want to add functionality to select ANY of the rules, or ALL of them? I need to conditionally put the rules in or{} . I can not do something like

 if(groupOp == 'or'){ or{ } 

before going through the rules list and then

 if(groupOp == 'or'){ } } 

thereafter. All I can do is 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 looks pretty messy and repetitive. Suppose I want to search for a property of a property of a domain object? (For example: I want to return cars whose tires are a certain brand: vehicle.tires.brand or cars whose drivers match the name; vehicle.driver.name). Should I 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': ... } 
+6
source share
1 answer

First, you can simplify your big switch by using GString for the method name:

 case ~/^(?:eq|ne|gt|ge|lt|le)$/: "${rule['op']}"("${rule['field']}", rule['value']) break 

The same trick works for and / or:

 "${(groupOp == 'or') ? 'or' : 'and'}"() { rulesList.each { rule -> switch(rule['op']){ ... } } } 

or you can first assign closure to a variable, and then call either or(theClosure) or and(theClosure) . Finally, to search for the property β€œproperty” if you add

 createAlias('driver', 'drv') createAlias('garage', 'grg') 

to the top of criteria closing, you can request things like eq('drv.name', 'Fred') without adding intermediate driver {...} or garage {...} node.

+9
source

Source: https://habr.com/ru/post/920840/


All Articles