class BaseDomain {
    Date dateCreated
    Date lastUpdated
    Boolean active = true
}
class Host extends BaseDomain {
    String ipAddress
    String hostName
    Site site
}
class Site extends BaseDomain {
    String name
    String description
}
Objective: user searches for matching hosts by either IP address, host name, site name, as well as filter by active/inactive field.
Defining searchable fields.
class Host extends BaseDomain {
    static searchable = {
        only: ["ipAddress", "hostName", "site", "active"]
        ipAddress boost: 2.0
        hostName boost: 2.0 
        site component: true
    }
    String ipAddress
    String hostName
    Site site
}
class Site extends BaseDomain {
    static searchable = { 
        only: ["name", "active"]
    }
    String name
    String description
}
"ipAddress" and "hostName" get a boost over "active" and Site.name. Site is defined as searchable component, meaning if a match is found on a site name, the Host that has a Site whose name matched the query is returned in the search result.
Working the query
User submitted query string should be a wildcard match on Host.ipAddress and Host.hostName fields, so if we have following data:
| ip address | 127.0.0.1 | 
| host name | localhost | 
| active | true | 
| site name | yahoo | 
queries like '127.0' or '0.0.1', 'local', 'calho' , 'yahoo', should all produce the above Host object as a match. Since there is only one search field, there is no way to know if what a user submitted is an ip address, a host name, or a site, therefore, all fields need to be checked. So, if a user submits "127.0", the query string for search should look like this:
ipAddress:*127.0* OR hostName:*127.0*
Since Site is a component, its matches are to be handled separately inside the search closure:
def wildcardQuery = "*" + params.query + "*"
def searchQuery = "ipAddress:" + wildcardQuery + " OR hostName:" + wildcardQuery 
return Host.search({
    must {
        queryString(searchQuery)
        wildcard('$/Host/site/name', wildcardQuery)
    }
}, params)
Then we add search for only active Hosts unless 'inactive' flag is set:
return Host.search({
    must {
        queryString(searchQuery)
        wildcard('$/Host/site/name', wildcardQuery)
    }
    if (!params.inactive) {
        must(term('$/Host/active', "true"))
    }
}, params) 
Now, let's add sort to the query. Technically, nothing needs to be added to the above snippet as long as there is a params.sort in the request. It works fine for fields like "ipAddress" and "hostName". However, if sorting by Site.name or 'active', actual "sort" request parameter must contain '$/Host/site/name' and '$/Host/active', which is not very convenient. It is more practical to have "site" and "active" as the value for params.sort, clone params into a separate map, and then overwrite sort param with the right value. This is how entire search closure looks:
def search = { 
    def searchParams = [:]
    params.each {
        searchParams."$it.key" = it.value
    }
    switch (params.sort) {
        case "site" :
            searchParams.sort = '$/Host/site/name'
            break
        case "active" :
            searchParams.sort = '$/Host/active'
    }
    def wildcardQuery = "*" + searchParams.query + "*"
    def searchQuery = "ipAddress:" + wildcardQuery + " OR hostName:" + wildcardQuery 
    return Host.search({
        must {
            queryString(searchQuery)
            wildcard('$/Host/site/name', wildcardQuery)
        }
        if (!params.inactive) {
            must(term('$/Host/active', "true"))
        }
    }, searchParams)
}
 
 
 Posts
Posts
 
