6

My Grails app is using the searchable plugin, which builds on Compass and Lucene to provide search functionality. I have two searchable classes, say Author and Book. I have mapped these classes to the search index, so that only certain fields can be searched.

To perform a search across both classes I simply call

def results = searchableService.search(query)

One of the nice features of doing the search across both class simultaneously, is that the results object includes metadata about number of results included, number of results available, pagination details etc.

I recently added a boolean approved flag to the Book class and I never want unapproved books to appear in the search results. One option is to replace the call above with:

def bookResults = Book.search(query + " approved:1")
def authorResults = Author.search(query)

However, I now need to figure out how to combine the metadata for both results, which is likely to be tricky (particularly pagination).

Is there a way to search across Book and Author with a single query, but only return approved books?

Dónal
  • 185,044
  • 174
  • 569
  • 824

2 Answers2

6

Do you want to be able to find authors or do you want to find books with a given author?

If you want to find books with a given author, you can configure your domain classes in the following way:

class Author {
    String name
    ...

    static searchable = {
        root false
    }    
}

this will result in excluding the Author from the searchableService.search(query)-result and you'll find field names like $/Book/Author/name in your index. (use luke to examine your index: http://code.google.com/p/luke/).

You can change the name of those fields by configuring a better prefix in your Book-class:

class Book {
    String name
    Author author
    ...

    static searchable = {
        author component: [prefix: 'author']
    }    
}

this will change the name of the field in the index to bookauthor.

If you now search with searchableService.search(query), you'll find all books where the name of the book or the name of the author contains the search term. You can even restrict the search to a given author by using the authorname:xyz syntax.


If you really would like to mix the search results, I only know the solution you already mentioned: mixing both results with your own code, but I guess it will be hard to mix the scoring of the hits in a good way.

Update to your response: Here's my pagination code...

.gsp:

<div class="pagination">
  <g:paginate total="${resultsTotal}" params="[q: params.q]"/>
</div>

controller:

result = searchableService.search(params.q, params)
[
  resultList: result.results, 
  resultsTotal: result.total
]

So if you just merge the results of your two searches and add the result.totals, this could work for you.

rdmueller
  • 10,742
  • 10
  • 69
  • 126
  • I want to be able to find books and authors with a single query, but never include unapproved books in the results. The problem with doing 2 queries is that it's difficult to combine the 2 sets of metadata into a single set. For example if I have 2 pages of book results and 2 pages of author results, that doesn't necessarily mean I have 4 pages of results in total – Dónal Jan 13 '12 at 00:31
  • Sorry, I guess I miss something here. Isn't is the case that if you have 15 book results and 17 author results, you'll always have 32 results together -> 4 page (with 10 results each) – rdmueller Jan 13 '12 at 05:57
  • Yes, but if I have 11 book results (2 pages) and 11 author results (2 pages), then if I combine the results I can fit them into 3 pages. – Dónal Jan 13 '12 at 10:38
  • A better example of where combining the queries is difficult is navigating to the next page of results. If I perform a single Query, the plugin will generate URLs for viewing the next/previous page of results. If I perform 2 separate queries, it will only generate URLs for viewing the next/previous page of results for each subquery. – Dónal Jan 13 '12 at 10:41
  • I see. I don't use the page information from the result. I use the result size together with the default grails pagination instead. – rdmueller Jan 13 '12 at 12:34
  • @Don: did you solve your problem? Or is there anything left where I maybe could help? – rdmueller Jan 19 '12 at 11:54
  • I haven't solved my issue insofar as I still don't know how to search across multiple classes but only use the predicate `approved:1` with one of them – Dónal Jan 19 '12 at 14:01
  • so a merge of the results is not a valid solution? – rdmueller Jan 19 '12 at 16:23
4

I've created a test app and came to the following solution. maybe it helps...

if the property approved only has the states 0 and 1, the following query will work:

def results = searchableService.search(
    "(${query} AND approved:1) OR (${query} -approved:0 -approved:1)"
)

I guess this can be reformulated in a better way if you don't use the QueryParser but the BooleanQueryBuilder.

BTW: if you add a method like

String getType() { "Book" }

and

String getType() { "Author" }

To your domains, you can even configure your search to do it like this

def results = searchableService.search(
    "(${query} AND approved:1) OR (${query} AND type:Author)"
)
rdmueller
  • 10,742
  • 10
  • 69
  • 126