Mike has done some great sleuthing on how to use Grails uber-cool searchable plugin to search a domain class based on properties of a nested class.
This is exactly what I’ve been looking for! Gravl supports multiple blogs per install, and when you are viewing my blog, I want those seaches to be constrained to fulltext search of just *my* blog, not all the data in the blog db.
My data model is 1:M from Blog to BlogEntry with a bidirectional link for navigation… So to make it all searchable I’ve got…
class Blog {
static hasMany = [ blogEntries : BlogEntry, tags : Tag, blogProperties : BlogProperty ]
static searchable = true
// ...
}
class BlogEntry {
static hasMany = [ comments : Comment, tags : Tag ]
static belongsTo = [ Blog ]
// we only index title and body, but index all fields in the parent "blog"
static searchable = {
title()
body()
blog(component: true)
}
Blog blog
//...
}
So by marking blog(component: true) we’re telling Compass to store Blogs fields in the index with the BlogEntry. Why do we want to do that? So we can search BlogEntry and impose criteria related to Blog. In my case, I want to do “find me all BlogEntry objects which contain the text ‘blah’ but limit the hits to those objects that have a blog.blogid of ‘glen’”. The code is simpler than the explanation! Here’s my whole search controller code which powers my search box:
def search = {
def query = params.query
def blogid = params.blog
def results = BlogEntry.search(query + " +blogid:${blogid}", params)
return [ results: results, query: query ]
}
And you’re got yourself blog-specific searching… Note that “blog.blogid” becomes just “blogid” in terms of field searching (ie. the component objects fields are collapsed into the parent).
Thanks Mike! Great pickup!
How cool. I very much like the ‘searchable DSL’ !
Can you please describe how you made the searchable plugin work under RC4 and later? I did
grails create-app bookstore
grails install-plugin searchable
grails create-domain-class Book
added some properties and static searchable = true
grails generate-all Book
and
grails run-app
and get some weird error messages about URL controllers missing an action
which version of grails did you use to make it work?
Hi Chris.
I was using it under RC3, so I can’t speak for RC4.
The only issue I’ve had with it was a NullPointerException on startup for which you need a “patch”:http://jira.codehaus.org/browse/GRAILSPLUGINS-220 .
Other than that had no issues.
Hey Glen,
nice article
May I suggest writing the query with a builder closure like:
BlogEntry.search params, {
queryString(query)
must(term(“blogid”, blogid))
}
This way you separate the user query from your own internal query, so you can monkey with it, eg:
BlogEntry.search(params) {
queryString(query, [escape: true, defaultAndOperator: true])
must(term(“blogid”, blogid))
}
which escapes any dodgy characters and makes all terms in the query string required (this is just my example of course – it depends in your requirements as to how you treat user input!).
Second it allows you to bypass the query string parser for the blogid. Otherwise, if blogid was a word or words, and it’s put into the text query, it would be parsed and when Lucene actually does the query it might not be the query you intended, since it has stripped junk characters removed common words (and/or) etc!
I would typically map such properties as “un_tokenized”, meaning when they are saved to the index, they are not broken up into tokens (parsed) but instead are stored as complete verbatim text strings (preserving case and all). Then when you use “term” in the query builder, you can pass in the *exact* value you expect and your search will only match those exact values in the index.
See the Mapping section of the docs for how to achieve this.
As for the issues you and others have experienced here, I think there all fixed, but please raise issues if not
Awesome Maurice. Thanks for the tip. So good to have you back in the community!
Aw, thanks Glen :blush:
I’m enjoying reading your Gravl stories. It’s a great name too
Oh BTW I tried saving this comment without first entering my name and hit save and nothing happened. Then I looked in firebug and saw:
on
Mon, 3 Mar 2008 22:08
but I didn’t see the error displayed
Hi Glen,
I tried this example with two other domain classes which are
class Book {
Author author;
String name;
static searchable = {
name()
author(component: true)
}
static belongsTo = [Author];
}
class Author {
static searchable = true
String name;
String surname;
static hasMany = [books:Book]
}
and I performed following queries
Book.search(“authorid:1″);
Book.search(“authorname:Glen”);
both of these queries does not return anything.
am I doing any mistakes?
Thanks…
hi Glen
I want to use searchable in a many to many relationship
But I failed
Would you please tell me how to use that if you have any experiance about that
Thanks a lot
Hello,
I have book hasMany author and author belongsTo company
I want to search the books for the company x
Book.search {
…..
}
what i do ?