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)
}