My Grails app is using the searchable plugin, which builds on Compass and Lucene to provide search functionality. I have two searchable classes, say Author and Book. I have mapped these classes to the search index, so that only certain fields can be searched.
To perform a search across both classes I simply call
def results = searchableService.search(query)
One of the nice features of doing the search across both class simultaneously, is that the results object includes metadata about number of results included, number of results available, pagination details etc.
I recently added a boolean approved flag to the Book class and I never want unapproved books to appear in the search results. One option is to replace the call above with:
def bookResults = Book.search(query + " approved:1")
def authorResults = Author.search(query)
However, I now need to figure out how to combine the metadata for both results, which is likely to be tricky (particularly pagination).
Is there a way to search across Book and Author with a single query, but only return approved books?
Do you want to be able to find authors or do you want to find books with a given author?
If you want to find books with a given author, you can configure your domain classes in the following way:
class Author {
String name
...
static searchable = {
root false
}
}
this will result in excluding the Author from the searchableService.search(query)-result and you'll find field names like $/Book/Author/name in your index. (use luke to examine your index: http://code.google.com/p/luke/).
You can change the name of those fields by configuring a better prefix in your Book-class:
class Book {
String name
Author author
...
static searchable = {
author component: [prefix: 'author']
}
}
this will change the name of the field in the index to bookauthor.
If you now search with searchableService.search(query), you'll find all books where the name of the book or the name of the author contains the search term. You can even restrict the search to a given author by using the authorname:xyz syntax.
If you really would like to mix the search results, I only know the solution you already mentioned: mixing both results with your own code, but I guess it will be hard to mix the scoring of the hits in a good way.
Update to your response: Here's my pagination code...
.gsp:
<div class="pagination">
<g:paginate total="${resultsTotal}" params="[q: params.q]"/>
</div>
controller:
result = searchableService.search(params.q, params)
[
resultList: result.results,
resultsTotal: result.total
]
So if you just merge the results of your two searches and add the result.totals, this could work for you.
I've created a test app and came to the following solution. maybe it helps...
if the property approved only has the states 0 and 1, the following query will work:
def results = searchableService.search(
"(${query} AND approved:1) OR (${query} -approved:0 -approved:1)"
)
I guess this can be reformulated in a better way if you don't use the QueryParser but the BooleanQueryBuilder.
BTW: if you add a method like
String getType() { "Book" }
and
String getType() { "Author" }
To your domains, you can even configure your search to do it like this
def results = searchableService.search(
"(${query} AND approved:1) OR (${query} AND type:Author)"
)
Related
I know that have to be really easy, but I'm new in grails and I don't find any clear answer. That I want to do is read and get with a criteria query the data that I have in my domain do a search for each parameter.
This is my domain Person:
String name
String surname
String address
String village
String country
This is that I'm trying to do:
def getData = Person.createCriteria()
I can see in the log that I have an object (com.mypackagename.Person: 1.), but not the data that I have in the database. example (myname, mysurname, myaddress, myvillage, mycountry)
I only have one row of data in my database and I want to get the data of every column and do a search for each parameter
Thanks in advance.
Let me show you the code first, then I'll explain it:
class SomeController {
def search() {
def people = Person.withCriteria {
params
.findAll { name, value -> name in ['name', 'surname', 'address', 'village', 'country'] }
.each { name, value -> eq(name, value) }
}
// Do as you please with 'people'; a list of Person instances.
}
}
Ok, so let's say you have a controller method (ex. search()) which receives the query parameters from the user. The parameters would be in the params Map. For example, if the user searches for the name John and the country USA, params would look like this: [name: 'John', country: 'USA']. Even though there are other search parameters available, we won't use them because the user did not specify them.
Within the criteria query, first search for the param key/value pairs which you care about; the searchable properties of Person. Then, for each of those pairs call eq(String propertyName, Object value) to set up the query criteria (the WHERE clause).
Using the example data, Hibernate will generate SQL that looks something like this:
SELECT name, surname, address, village, country
FROM person
WHERE name = 'john' AND country = 'USA'
And that's it!
Note: You will see the same output in the log (ex. com.mypackagename.Person: 1). That's because you're logging personInstance.toString(). So if you want the log entry to look differently, you'll need to override Person.toString() A very easy way to remedy this is to use Groovy's #ToString AST.
For more about creating criteria queries, take a look at my series of articles. I cover criteria queries using SQL terminology.
Try to use:
def persons = Person.createCriteria().list{}
or if you want just one result:
def persons = Person.createCriteria().list {
maxResults 1
}
Moreover please read about using Criteria and Querying with GORM
I have a filter, for example lets assume the name John Doe is who a user is searching for.
I have the following domain object structure: Company--->Employee--->Name. Where employee is an attribute of company, and name is an attribute of employee. Now, the filter is for the name attribute, but I need to keep a reference to the Company. So for example I did:
companyList.events.each {
if(it.employee!=null){
if(it.employee.name.toString().toLowerCase().contains(filter)){
filterSet.add(it)
}
}
Unfortunately, this operation is very slow for 1500 entries. However if I do something like,
def searchResults = Company.findAll{
employee.name == filter
}
It is very fast, but I need the filter to get matched with partial names (i.e. saying Joh would still match John Doe. I know there is an operation called like but I have been unsuccessful using it.
Any help would be really appreciated.
I suggest you look at using criteria. So in this case you could do this:
def filter = 'joh'
def searchResults = Company.createCriteria().list() {
employee {
ilike('name', "%${filter}%")
}
}
Try this:
Company.findAll ("from Company c where c.employee.name like '%'| |:filterName| |'%'", [filterName: yourFilterName])
This query has to be fast.
I need to search member's by first name and last name, which I have done successfully.
Next thing which I have to do is that member's connection should come first in the list (sorting by connection.), like in Facebook, friends come first in the list and than other users of the community.
I am using grails plugin Searchable. One simple way to do this is to sort the searchListFromSearchable w.r.t. connection's list.
Following is the domain structure.
class Member extends {
String firstName
String lastName
static searchable = {
analyzer "simple"
only = ['firstName', 'lastName']
firstName boost: 5.0
}
static hasMany = [connections: Connection]
}
And Connection class is as follow
class Connection {
String uuid
Member connectedMember
static belongsTo = [member: Member]
}
Is there any lucene way to do this ?
I think you can add the sort process in the collect step or score step in Lucene. I think you get the relationship first, and when search the member, you can check whether the member is in the relationship or not. If the member is in the relationship, you can add score of this doc, such as write your own collector which extend TopFieldDocCollector and add score *= 10f before super.collect() in the collect method .
On of the solution to add friends ids to index. In this case, your search query should have followed form +name:firstName +name:lastName friend:userId^10. The friendId has a bigger boost and will cause friends to a high rank in your query.
I have an object from which I must filter certain attributes, some of which could also be "null". I have a Filter object and a Product object.
In the Filter object I have certain attributes reflecting the Product object which can be filled out or be left blank. Here a shortened view on the classes.
Product: String name, Boolean isEmpty, ...., belongsTo [Producer, Distributor]...
Filter: Boolean isEmpty, ... belongsTo [Producer, Distributor]...
With this filter I can search for all Products having certain attributes (empty, Producer, Distributor).
I have an export functionality where I can select the filter and it outputs the information based on that selection for the Products.
As all of these attributes can be null, but also contain a value, I first of thought to construct an own search query (combining strings etc) to construct an SQL-string and then using Product.findAll(string_query, string_params). But as this is quite tedious, I changed it now to someting like this:
if(filter.producer)
prods = Product.findAllWhere(producer:filter.producer)
if(filter.distributor)
prods = prods.findAll {it.distributor == filter.distributor}
if(filter.isEmpty!=null) //as it could be NULL but also false/true
prods = prods.findAll {it.isEmpty == filter.isEmpty}
But this becomes quite a larger task if I have 10-15 attributes to be filtered. I'm not very experienced with Grails or Groovy but I guess this can be solved easier, right?
I believe you'll find Grails Criteria queries to be a very nice way to accomplish tasks like this. See:
http://grails.org/doc/latest/guide/single.html#criteria
http://viaboxxsystems.de/the-grails-hibernatecriteriabuilder
Your sample might look something like this when expressed as a criteria query:
def prods = Product.createCriteria().list {
if(filter.producer) eq("producer", filter.producer)
if(filter.distributor) eq("distributor", filter.distributor)
if(filter.isEmpty != null) eq("isEmpty", filter.isEmpty)
}
My problem simply put in Grails: I want a plugin OR example for translations of Domain Classes.
Description: I would like to have translatable entites with a nice DB scheme behind it. For example
Domain Class 1: Book (id, author_id, number_pages)
Translations for Book: (book_id, language, title, description)
Domain Class 2: Author (id, birthday)
Translations for Author (author_id, language, first_name, last_name) (for example, the same author is known under different names in different countries)
What I want to do: knowing the Language, I want to get The book with an ID, and EAGER fetching its author (of course book and author with the right translations). Or for example to search all books by an author, known by a first_name in a country/language.
How would you do manually that with GORM?
Is there a plugin for that or something to hide the "business logic"?
Or how could I simply get all translations for a book?
Whab about just using GORM as is - i.e. Book and BookTranslations would be two domain objects with a one to many translation between Book and BookTranslations, and then you could do BookTranslations.findAllByBook, criteria queries, etc.
The reason I suggest this is that there is an entity relationship in your description (i.e. one to many), so it's not like a parent/child/embedded style class heirarchy would do anything for you. This also gives you complete control over the fetching strategy[1]
I think the only 'secret sauce' here is that you could standardize on the way you represented languages. For example, if you actually had some unicode text in those rows or mappings to a standard messages files, then you could use the ISO standard language names for the language column in the BookTranslation table (e.g. en-US for US English). This would allow you to take direct advantage of the Grails internationalizatino features, where locales and message files can be automatically resolved, plus there are taglibs and other goodies to make internationalization less painful.
See [2]
A simple example would be something like the following (not tested, so pardon any compile errors):
class Book {
String author
int numPages
static hasMany [ bookTranslations: BookTranslations]
}
class BookTranslations {
String language
String title
String description
static belongsTo = Book
}
def book = new Book(author:"test", numPages:20)
book.save()
def translation1 = new BookTranslations(language:"en-us", title:"How to Book", description:"A wonderful book")
book.addToBookTranslations(translation1)
def translation2 = new BookTranslations(language:"en-us", title:"Second How to Book", description:"A glorious book")
book.addToBookTranslations(translation2)
def results = BookTranslations.findAllByBook(book)
// or ..
results = book.bookTranslations
As luck would have it, the example in the GORM chapter in the Grails Documentation is all about authors and books. See [3]
[1] http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html#5.5.2.8%20Eager%20and%20Lazy%20Fetching
[2] http://grails.org/doc/latest/guide/10.%20Internationalization.html
[3] http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html
Just stumbled upon this plugin, seems like it may be what you are looking for.