Build createCriteria in Grails dynamically and in a DRY way?

Viewed 4583

I'm working on building a createCriteria dynamically. So far, so good:

obj is the domain object(s) I want back

rulesList 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':
        ...
}
1 Answers
Related