Skip to content


Add text search to your grails application

Whenever you see the words full text search in the java world, the first thing you will run into is Apache Lucene. The powerful full-featured text search engine for java. To be honest, I have never used, it before since most of the applications I’ve worked on relied mostly on hand-crafted SQL queries that were enough to get the job done. Nevertheless, I’ve always wanted to learn how to use it, but never got the chance to get past the basic tutorials on how to setup a simple application and give it a spin.

Having played around with grails for a while now, I came across the Grails Searchable plugin, which brings the rich search features of Compass and Lucene to grails in a very easy to use way which made me decide to finally give the full text search experiment a go. Since I already have my sample phone book grails application up and running, it would be really cool if I could add a search feature (I mean what’s the use of a phone book if you can’t search!!)

DISCLAIMER: For the record, I have never really used Lucene before this experiment, and before running into this plugin I did not even know what Compass was, so this is my basic attempt at hacking away with this plugin; having no previous knowledge of the APIs it’s built upon; and making search work (which was really easy). This should be good news for anyone trying this out for the first time, but I wouldn’t say that what I have done here is in any way complete or correct, but it works! Here’s a teaser of the end result

search for john

I will start by defining what the search function will serve in my application. Basically I want to be able to search freely for contacts by either their first name, last name, nickname and/or any of their phone numbers. Doing this with SQL queries is not very easy since I only want to have one search box that the user can type freely into and not some advanced search where each text field corresponds to a domain class property.

Ok so lets get to it. First things first, if you want to follow along you can grab the source code. I will be continuing on the phone book application from the last post.

Installing the plugin

In order to install the searchable plugin, cd to your application’s root directory and issue the following command

grails install-plugin searchable

If you are on JDK 1.4 (Why are you still on 1.4 anyways!) you need to use this command

grails install-plugin searchable14

This will download the Searchable plugin and install it for use in your application. (As of this writing, version 0.5.5.1 is the current release). Out of the box, the plugin will introduce a SearchableService that allows for easy cross domain searching and index management as well as a basic search page (view) and a controller that will allow you to do quick searches without the need to create your own view and controller which is really nice for our first basic testing. But before trying to search for anything, we need to define how our domain model will be indexed and which properties are to be included.

Defining the mappings

Enabling the domain classes to be search-able is  as simple as adding the following to your domain class

static searchable = true

This is enough to enable searching on all simple properties in your domain class, I bet this is much easier than you thought. In any case, my search requirements are a little bit more complex. I need to be able to find contacts by their phone numbers as well, so I will have to do a little more mapping to get what I want.

It’s important to note that setting search-able to true on each domain class, will create a separate index (in this case if I set the Contact to be search-able as well as the Phone I will get 2 separate indexes for each domain class) and I don’t want that since finding a phone instance on it’s own doesn’t make sense, what I want is to get the actual contact when I search for his/her phone number. In order to achieve this behavior, I need to define the phones list on my contact as a Searchable Component, what this means is that the actual phones data is stored within the contact’s search data/index (the owning object). The mapping I put together looks something like this in the Contact.groovy file

static searchable = {
     phones component: true
}

Simple right? Well there is also one more mapping that needs to be done, since I want to search the phone numbers, the Phone domain class should be declared search-able as well, but it should not be defined as root, since I want the Contact object to be the first class citizen of this search (i.e. I don’t want to find a phone object, but I want to find a contact object containing the phone I am searching for).  The search mapping in Phone.groovy should look like this.

static searchable = {
     root false
}

What this does is define the Phone domain class as search-able, but the root:false part indicates that I will be finding Phone objects through the owning object (Contact).

Note:If you are curious about where all the indexed data is stored, by default this data is stored as binary (?) file/s located in your home directory under the .grails folder (e.g. on my machine, /home/omarji/.grails/projects/phone-book/searchable-index/development)

Search Away

That’s it, seriously, we are done. At this point we can run the application and point the browser to the prepackaged search page that comes with the plugin (http://localhost:8080/phone-book/searchable) and try out some search queries.

searchable view

Pretty cool. But we need to integrate this search into our phone book list page so that we can see the complete contact card when searching. Doing this is very easy as well, all we have to do is add a search form to the phonebook.gsp page.

<g:form action="search">
    <div class="search">
        Search Contacts
        <input type="text" name="q" value="${params.q}" />
        <input type="submit" value="Search" />
    </div>
</g:form>

And add a new ‘search’ action to the ContactController.groovy which will contain our search logic. In order to do that we use grails’ convention over configuration to inject the SearchableService into our controller and use this service to do our searches like so

	def searchableService //inject the service (make sure the name is correct)

	def search = {
		def query = params.q
		if(query){
			def srchResults = searchableService.search(query)
			render(view: "phonebook",
                   model: [contactInstanceList: srchResults.results,
                         contactInstanceTotal:srchResults.total])
		}else{
			redirect(action: "phonebook")
		}
	}

As you can see, searching is as simple as calling the search method on the searchableService, off-course you can pass all sorts of different options to the search method in order to do pagination and whatnot, but I am using the simplest form here, and rendering the phone book view by passing it the list of Contact objects found by the search service. Now we are ready to test the phone book page with the new search box.

Here are some cool things to try out when searching your contacts (check the different query syntax options that Lucene provides for more option)

  • firstName: John : Searches the first name property of our contacts which equal John
  • +414* : Searches all properties (including phone numbers) for the string that starts with +414
  • Daivd~ : Performs a fuzzy search (matches anything with a similar spelling to Daivd) on all properties

Here are some sample screenshots in action, starting with the sample list of contacts we will be searching

initial contact list


Searching for a contact with phone number “4148769234″

search-phonenumber


Searching for all contacts that live in the Wisconsin area code (+414)

search wisconsin area code


Searching for misspelled “Daivd” instead of “David” on all properties

misspelled david


As you can see, adding the search functionality was very easy, and it exposed a lot of flexible search options with just a few lines of code, but what I have shown here is just part of a huge topic, ranging from suggested spelling fixes (ala Google’s Did you mean: ), to analyzers, managing indexes, converters, configuration options, etc… but I still did not dive into trying to understand everything. The plugin page has all sorts of help topics to cover all those areas, also the documentation provided by Lucene and Compass is another place to find more information.
On a final note, I am not sure what the status of the plugin is at the moment, and whether it is still being actively developed or not, but I sure hope that it will still be taken care off.

Posted in Programming.

Tagged with , , , , .


0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.



Some HTML is OK

or, reply to this post via trackback.