Wednesday, April 2, 2008

Searchable Plugin: Just use it!

Warning: If you are one of the clever folks who has already discovered the searchable plugin then there's nothing new for you here. You might want to move along to Glen Smith's latest post about hit highlighting with Searchable. If you haven't looked at Searchable yet, you need to. So, either go directly to grails.org/Searchable+Plugin or read on if you need more convincing.

I've been working with Grails for over a year, all the while hearing great things about the Searchable plugin. So far, I have only had the need for simple searches so I just never bothered to look at Searchable. That was really dumb! Like, "why bother learning how to use a hammer, this rock is working fine".


So, just the other day, I had a search that was going from two potential fields to three or four. So, I decided to take a look at the Searchable plugin and see how "hard" it would be to get it integrated. Well, as with most things in the Groovy / Grails world, I had it up an running almost before I had finished reading the wiki page. It is so easy and so powerful that I can't see myself building any kind of search without it.

Here's a simple scenario for an example: We have a Company class that has half a dozen fields that users might want to search on and they want the search form to be at the top of the list page.

First we install the plugin like so:

> grails install-plugin searchable

Then add the following line to Company.groovy:
    static searchable = true
It doesn't matter where you add this line to your Groovy class, but I like to put all static references at the top.

Next we'll add a search action to our controller:


def search = {
def companyList
if (params.query)
companyList = Company.search(params.query).results
else
companyList = Company.list()
render(view:'list', model:[companyList : companyList])
}
Just like GORM adds list(), find(), save(), etc. to our domain class, Searchable added search(), which takes a query string and optionally a map where you can add options like max, offset, sort, etc. It returns a searchResult which contains a results property which is an ArrayList. So, our companyList can be assigned either our search results or the result of a call to list().
There are other methods added to our domain and many more things that can be done with Searchable. You can read more about it at: grails.org/Searchable+Plugin+-+Searching

Finally we add a search bar to our list.gsp:

< div class="nav">
< g:form action="search" method="post">
Search: < input type="text" name="query">
< input value="Search" type="submit">
< /g:form>
< /div>


Now our users can search for companies by any of the properties using one or more search words and the result will show up in our standard list page. Searchable also supports complex queries which you can read all about at grails.org/Searchable+Plugin+-+Searching#SearchablePlugin-Searching-StringQueries

There's more that we could do here such as pagination, but I'll leave that as an exercise for the reader. How hard could it be?

6 comments:

Shawn Hartsock said...

I'm going go run out and try it right now!

Gordon Dickens said...

Did you have to setup a domainclass.cpm.xml file? If so, can you post an example?

Dave Klein said...

@Gordon - No, I didn't have to use any external files.

Lissandro Dittmar said...

Hello! I have a problem. I want to search only for lines where the fields 'finish' are true and 'locked' are false. How can I do this? My code is:

def search = {
def helloList
def c = Hello.createCriteria()

params.suggestQuery = true

if (params.query)
helloList = Hello.search(params.query, params)
else
helloList = c.list{
and{
eq('locked', false)
eq('finish', true)
}
}

render(view:'list', model:[helloList : helloList.results, suggestQuery : helloList.suggestedQuery])
}

Dave Klein said...

@Lissandro - I haven't tested this but a query string like this should work:

+finish:true +locked:false

Since the Searchable plugin uses Lucene under the hood, you can find more info about query strings in the lucene docs:
http://lucene.apache.org/java/2_4_0/queryparsersyntax.html

Hope that helps,
Dave

Lissandro Dittmar said...

Thx... it works with:

params.query = params.query + " +finish:true +locked:false"