Monday 7 February 2011

Add search to your grails application using the searchable plugin

If you're building a web application, you're most certainly going to need some 'search' capabilities in your application. The searchable plugin is an amazing plugin that is built using Compass search engine and lucene. This plugin enables you to make your domain classes without writing almost any code. In this article we will be discussing how you can install and use this plugin, and also discuss some issues around it.

We will be creating a simple grails application, a couple of domain classes, and then we will explore the 'searchable' plugin. (find the source code of the sample application at the end of the article)


Let's get started!



STEP 1:  Create a grails app and install the searchable plugin
We will create a very simple basic application. To learn how to install and get started with grails please take a look at the blog article 'Build your first grails application'.
Open a command prompt and create your sample application:
grails create-app empsys
cd empsys
grails install-plugin searchable
STEP 2: Create your domain classes
Now let's create some domain classes which we would make 'searchable'. To create the domain class use the command:
grails create-domain-class Employee
grails create-domain-class Department
Let's add some fields to our domain classes.
Employee.groovy
package empsys

class Employee {

    static searchable = true
    static embedded = ['dept']

    Department dept
    String empCode
    String firstName
    String lastName
    String emailAddress
    String phoneNumber
    String designation
    Date birthDate
    Date joiningDate
   
    static constraints = {
        empCode(blank: false, unique: true)
        firstName(blank: false)
        lastName(blank: false)
        emailAddress(blank: false, email:true)
    }
}

Department.groovy
package empsys

class Department {

    static searchable = true
       
    String deptCode
    String deptName
    String description

    static constraints = {
    }
}
Note the statement 'static searchable = true'. This is necessary if you want to be able to search the objects of the domain class. (we will shortly discuss the 'static embedded' statement)

STEP 3: Create controllers and views for the domain classes
To create the controller and views for your class use this command:
grails generate-all empsys.Department
grails generate-all empsys.Employee

At this point, you can run the application using the command: grails run-app

You can now test the plugin, run the application, create some test data i.e. add some departments and employees to your system. Browse to : http://localhost:8080/empsys/searchable and try searching for the employees ( and departments) you just added to your system. How cool is that!

Tip: you may want to change your datasource.groovy to save into an hsqldb file instead of the default memdb or you'll loose all your test data. (url = "jdbc:hsqldb:file:devDb")

STEP 5: Customize the controller and the views
In addition to the 'searchable' controller which searches all the 'searchable' domain classes, you'd typically need a search box and a button on the '/employee/list' page to search for employees. You can easily do this, by using the views and controllers generated by the plugin. The controllers and views generated by the plugin can be found at:
%USER_HOME%\.grails\1.3.4\projects\empsys\plugins\searchable-0.5.5.1\grails-app
Copy the content of SearchableController.java, change the function name from 'index' to say 'search' and copy it to your EmployeeController.java.
/* add this import statement for the searchable plugin to work */
import org.compass.core.engine.SearchEngineQueryParseException

class EmployeeController {
static String WILDCARD = "*"
def searchableService

......... 
def search = {
        if (!params.q?.trim()) {
            return [:]
        }
        try {
            String searchTerm = WILDCARD+ params.q + WILDCARD
            return [searchResult: Employee.search(searchTerm, params)]
        } catch (SearchEngineQueryParseException ex) {
            return [parseException: true]
        }
    }
  def indexAll = {
        Thread.start {
            searchableService.index()
        }
        render("bulk index started in a background thread")
    }

    def unindexAll = {
        searchableService.unindex()
        render("unindexAll done")
    }
......
}
[Note: We've made a small change to the default search functionality provided here. We are appending the search term with a wildcard '*' so that even if the user enters part of the employee's first name or last name or any other field, the employee is featured in the search results. ]

 Let's add a search box and a button on the employee/list.gsp page. Add the following code to \views\employee\list.gsp file after the navigation buttons:
<div class="nav">
            <span class="menuButton"><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></span>
            <span class="menuButton"><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></span>
        </div>
        <div class="body">
        <g:form action="search" method="get">
            <g:textField name="q" value="${params.q}"/>
            <g:submitButton name="search"/>
        </g:form> ......
We also need to create a new gsp view to display the results of the search. This page takes most of its code from the %USER_HOME%\.grails\1.3.4\projects\empsys\plugins\searchable-0.5.5.1\grails-app\views\searchable\index.gsp file and the views\employee\list.gsp page. I am not copying the code here since it's pretty long and would make the article look ugly :) But you can find the source attached at the bottom of the post.

Once you have fixed your views, you should be able to see something like this:
And the search result page would look like this:

It's time to now go back to the Employee.groovy file
We had added a line  'static embedded = ['dept']' to our Employee domain class. Here we are making use of 'composition'. Since the Department class is also 'searchable' and we have used composition, you will be able to search for the all employees belonging to a certain category using the above screen. i.e. If your employee belongs to a department with department name 'Finance', on searching the text 'Finance' the employee will show up in the results.
You can read more about the searchable-reference and searchable-components here.

The plugin provides loads of options to customize the search to suit your requirements for eg: query mechanisms, builders, mappings etc. There are a couple of mappings you can add to the 'static searchable' closure. eg: if you want to exclude some fields from the search or you want to boost a certain field while searching, update your closure as follows:
static searchable = {     
        /* we don't wish the id and version to be 'searched'
              or added to the index */
            except = ['id', 'version']
        empCode boost:2.0
        }
In this article, we've implemented a very basic search functionality for our domain classes. There's a lot of stuff in this plugin that you can explore and use in your application.

Hope you find this article useful.

Happy coding !

Download the source code of the sample search application : empsys.rar


7 comments:

  1. Hi
    I have multiple search fields on my view, which represent properties in different domains. I want to use searchable controller and search based on multiple search terms. How to do that, Any suggestions please

    ReplyDelete
  2. Hi Suurya,

    By 'multiple search fields on my view', if you mean fields across domain classes, you just need to add 'static searchable = true' to all the necessary domain classes.
    Then use the searchableService.searchI) method.

    For further details : http://www.grails.org/Searchable+Plugin+-+Methods+-+search

    I hope this answers your query.
    Good luck

    ReplyDelete
  3. here you do not mention how to install compass plugin only you import the compass.can you tell me how to install compass plugin

    ReplyDelete
  4. Every time i try to use static embedded in my class i get this error
    Invocation of init method failed; nested exception is org.hibernate.MappingException: Foreign key (FK317B13250F8F50:item [])) must have same number of columns as the referenced primary key (item [id])

    Can you help me out?

    Regards

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. There aren't many tutorials for the searchable plugin.

    Thank you very much, it was very helpful in the understanding of the plugin and also helpful in the making of a nice gsp for a newbie like me in Grails

    ReplyDelete
  7. Hi,

    Please help me, i am new to grails how to install plugins in grails 3.2.m1 manually in windows.
    Thanks in advance

    ReplyDelete