(Quick Reference)

8 Example - Reference Documentation

Authors: Noam Y. Tenne, Manuarii Stein, Stephane Maldini, Serge P. Nekoval, Marcos Carceles

Version: 0.0.4.6

8 Example

8.1 Tweets

8.1.1 The domains

class Tweet {
  static searchable = {
    message boost:2.0
  }

static belongsTo = [ user:User ]

static hasMany = [ tags:Tag ]

static constraints = { tags nullable:true, cascade:'save, update' }

String message = '' Date dateCreated = new Date() }

class User {
  static searchable = {
    except = 'password'
    lastname boost:20
    firstname boost:15, index:'not_analyzed'
    listOfThings index:'no'
    someThings index:'no'
    tweets component:true
  }

static constraints = { tweets cascade:'all' } static hasMany = [ tweets:Tweet ] static mappedBy = [ tweets:'user' ]

String lastname String firstname String password String activity = 'Evildoer' String someThings = 'something' ArrayList<String> listOfThings = ['this', 'that', 'andthis'] }

class Tag {
  static searchable = {
    except=['boostValue']
  }

String name Integer boostValue = 1 }

8.1.2 The controller

A action triggering indexation

ElasticSearchController (testCaseService is just dealing with GORM instructions):
class ElasticSearchController {
  def elasticSearchService
  def testCaseService

def postTweet = { if(!params.user?.id) { flash.notice = "No user selected." redirect(action: 'index') return } User u = User.get(params.user.id) if (!u) { flash.notice = "User not found" redirect(action: 'index') return } // Create tweet testCaseService.addTweet(params.tweet?.message, u, params.tags)

flash.notice = "Tweet posted" redirect(action: 'index') } }

With this code (considering that there are already User in the database), new Tweets will be indexed automatically, and corresponding User indexed documents will be updated since we have set the tweets association as component.

Searching for Tweets

def searchForUserTweets = {
    def tweets = Tweet.search("${params.message.search}").searchResults
    def tweetsMsg = 'Messages : '
    tweets.each {
      tweetsMsg += "<br />Tweet from ${it.user?.firstname} ${it.user?.lastname} : ${it.message} "
      tweetsMsg += "(tags : ${it.tags?.collect{t -> t.name}})"
    }
    flash.notice = tweetsMsg
    redirect(action: 'index')
}

Searching for anything

def searchAll = {
    def res = elasticSearchService.search("${params.query}").searchResults
    def resMsg = '<strong>Global search result(s):</strong><br />'
    res.each {
      switch(it){
        case Tag:
          resMsg += "<strong>Tag</strong> ${it.name}<br />"
          break
        case Tweet:
          resMsg += "<strong>Tweet</strong> "${it.message}" from ${it.user.firstname} ${it.user.lastname}<br />"
          break
        case User:
          resMsg += "<strong>User</strong> ${it.firstname} ${it.lastname}<br />"
          break
        default:
          resMsg += "<strong>Other</strong> ${it}<br />"
          break
      }

} flash.notice = resMsg redirect(action:'index') }

8.2 Geo Distance Search

A search for buildings with a geo_distance filter, ordered by distance.

8.2.1 Domains

class GeoPoint {

Double lat Double lon

static searchable = { root false } }

GeoPoint represents the geo coordinates for a building. The field namesĀ lat and lon are mandatory.

class Building {

String name GeoPoint location

static searchable = { location geoPoint: true, component: true } }

The location of the building is mapped to an ElasticSearch geo_point.

8.2.2 Service Methods

Searching for all buildings sorted by distance with 5km radius around geo location (lat=41.141, lon=11.57)

def searchForBuildings() {
    Closure filter = {
        geo_distance(
            'distance': '5km',
            'location': [lat: 48.141, lon: 11.57]
        )
    }

def sortBuilder = SortBuilders.geoDistanceSort("location"). point(48.141, 11.57). unit(DistanceUnit.KILOMETERS). order(SortOrder.ASC)

def result = elasticSearchService.search( [indices: Building, types: Building, sort: sortBuilder], null as Closure, filter)

return [results: result.searchResults, distances: result.sort] }

The calculated distances are not part of the search results themselves but are part of result.sort. sort contains all search values calculated by the ElasticSearch server as a list mapped to the id of the respective domain objects

Example

assert [1:[23, 42], 2: [24, 40]] == result.sort

8.3 Parent/Child mapping

A store with many departments
class Store {

String name String description = "A description of a store" String owner = "Shopowner"

static searchable = true

static constraints = { name blank: false description nullable: true owner nullable: false } }

class Department {

String name Long numberOfProducts Store store

static constraints = { numberOfProducts nullable: true }

static searchable = { store parent: true, component:true } }

Search for all departments which are childs of a store with the owner "Shopowner"

def result = elasticSearchService.search(
    QueryBuilders.hasParentQuery("store", QueryBuilders.matchQuery("owner", "Shopowner")),
    null as Closure,
    [indices: Department, types: Department]
)