Best way to dynamically generate conditions for a search

306 views
Skip to first unread message

Richard Hulse

unread,
Feb 29, 2012, 6:05:07 PM2/29/12
to ruby-s...@googlegroups.com

I have a app that is searching many (searchable) tables.

Each table has different text fields. User are able to search across all of them in one go - I compile a block for each table and execute it separately.

The compiled block (of scopes) is EVALed inside the Sunspot.search block.

The reason I do this is so that a field search:

eg. composer:mozart

is assembled only for the tables that have the composer field indexed.

Is there a better way to inject scopes into a search?


Matthew A. Brown

unread,
Feb 29, 2012, 6:13:38 PM2/29/12
to ruby-s...@googlegroups.com
Hi Richard,

Unless you have any sort of weird overlap between your text field
names, you should be able to just do a single search:

Sunspot.search(Model1, Model2) do
fulltext('blah')
end

Mat

> --
> You received this message because you are subscribed to the Google Groups
> "Sunspot" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/ruby-sunspot/-/v2H8OBPSnvMJ.
> To post to this group, send email to ruby-s...@googlegroups.com.
> To unsubscribe from this group, send email to
> ruby-sunspot...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/ruby-sunspot?hl=en.

Richard Hulse

unread,
Feb 29, 2012, 7:07:16 PM2/29/12
to ruby-s...@googlegroups.com
Hi Mat,

The use case is a bit tricky.

Users can enter in stuff like this:

composer:mozart [c20]

This means items where the composer is mozart and the c20 means duration circa 20 minutes.

This get converted to:

keywords('mozart', :fields => [:composer])
with(:duration, 1080..1320)

which is evaled

In acts as solr we used to just send through the field:value parts of the string untouched.

I am using this rather than drop downs on an advanced screen is that this is much, much faster for our types of searches (most fields actually have aliases, so cp:mozart for the above for example). 

The reasons why this is needed is the users are searching over multiple data sources (some that are nothing to do with music) , and we cannot query by every specified field in some of the data. Compiling the search block against what fields are indexed avoids a search error (we just don't quert those tables).

Basically, if the query string is just text we return results from all data sources. If people enter some fields (or durations) then the system is smart and Does The Right Thing by excluding data sources that do not have those fields. 

Results are returned in tabs rather than on one screen (which does not make sense in our context), which is why we query one table at a time.

(If you want to see an explanation of the pre-rails version of the app looks like it was covered in Linux Journal: http://www.linuxjournal.com/article/7774


A side question: what is the correct datetime format that I can use with .less_than and .greater_than if I cannot pass in a Time object?


Richard

Mauro Asprea

unread,
Mar 1, 2012, 3:44:11 AM3/1/12
to ruby-s...@googlegroups.com
Hi Richard, I kind of understand what you are doing ;) but I thinnk I'm somewhow confused about what your questions is. What do you see wrong with the way you are scope that? I mean, what doesn't feel right? I'm doing something similar myself, in a project I'm working on, to retrieve/lookup specific "events" with "locations" like shown below. The only thing I have to do was to change some Solr fields to cover some edge cases (apostrophe stuff)

    search = self.search{
      fulltext "\"#{term}\"", { fields: :name }
      fulltext "\"#{location}\"", { fields: :location_name }
      with(:event_id, nil)
      paginate( :page => 1, :per_page => 1 )
    }

So, back to your questions, would you please explain more?

Cheers ;)
March 1, 2012 1:07 AM
Hi Mat,

The use case is a bit tricky.

Users can enter in stuff like this:

composer:mozart [c20]

This means items where the composer is mozart and the c20 means duration circa 20 minutes.

This get converted to:

keywords('mozart', :fields => [:composer])
with(:duration, 1080..1320)

which is evaled

In acts as solr we used to just send through the field:value parts of the string untouched.

I am using this rather than drop downs on an advanced screen is that this is much, much faster for our types of searches (most fields actually have aliases, so cp:mozart for the above for example). 

The reasons why this is needed is the users are searching over multiple data sources (some that are nothing to do with music) , and we cannot query by every specified field in some of the data. Compiling the search block against what fields are indexed avoids a search error (we just don't quert those tables).

Basically, if the query string is just text we return results from all data sources. If people enter some fields (or durations) then the system is smart and Does The Right Thing by excluding data sources that do not have those fields. 

Results are returned in tabs rather than on one screen (which does not make sense in our context), which is why we query one table at a time.

(If you want to see an explanation of the pre-rails version of the app looks like it was covered in Linux Journal: http://www.linuxjournal.com/article/7774


A side question: what is the correct datetime format that I can use with .less_than and .greater_than if I cannot pass in a Time object?


Richard



On Thursday, March 1, 2012 12:13:38 PM UTC+13, outoftime wrote: --
You received this message because you are subscribed to the Google Groups "Sunspot" group.
To view this discussion on the web visit https://groups.google.com/d/msg/ruby-sunspot/-/TW3uieyUMCwJ.

To post to this group, send email to ruby-s...@googlegroups.com.
To unsubscribe from this group, send email to ruby-sunspot...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/ruby-sunspot?hl=en.
March 1, 2012 12:13 AM
Hi Richard,

Unless you have any sort of weird overlap between your text field
names, you should be able to just do a single search:

Sunspot.search(Model1, Model2) do
fulltext('blah')
end

Mat

March 1, 2012 12:05 AM

I have a app that is searching many (searchable) tables.

Each table has different text fields. User are able to search across all of them in one go - I compile a block for each table and execute it separately.

The compiled block (of scopes) is EVALed inside the Sunspot.search block.

The reason I do this is so that a field search:

eg. composer:mozart

is assembled only for the tables that have the composer field indexed.

Is there a better way to inject scopes into a search?


--
You received this message because you are subscribed to the Google Groups "Sunspot" group.
To view this discussion on the web visit https://groups.google.com/d/msg/ruby-sunspot/-/v2H8OBPSnvMJ.
To post to this group, send email to ruby-s...@googlegroups.com.
To unsubscribe from this group, send email to ruby-sunspot...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/ruby-sunspot?hl=en.

--
Mauro Asprea

E-Mail: mauro...@gmail.com
Mobile: +34 654297582
Skype: mauro.asprea

Richard Hulse

unread,
Mar 1, 2012, 1:20:52 PM3/1/12
to ruby-s...@googlegroups.com


On Thursday, March 1, 2012 9:44:11 PM UTC+13, Mauro Asprea wrote:
So, back to your questions, would you please explain more?


Sure.

The search app has multiple data sources. Some of these are imported each night from legacy systems that we cannot yet replace. The purpose is to provide an power search that people can use across all of the data in disparate systems.

The system has search zones. People in each area of the business have a set of data sources that are their default search tables.

The reason for having the dynamic search criteria in the code, is to make the process of searching, and particularly field searching as transparent as possible, and allow it to be done from the search field using simple text constructs.

It also makes my job a lot easier, because an advanced search for would have to be dynamically change based on what data was being searched in order to make any sense at all.

Also, the search block would be (as I have seen in other people's code) dozens of if statements to handle all the changes.

Compiling the scopes is the most elegant way to do this, (and when debugging I can just write the compiled search scopes to the log to see what the Sunspot is getting).

I guess my objection was to using eval, and was wondering if there was some other way of doing things. For example:

search = Sunspot.search_on(ModelName)

search.fulltext( 'string')
search.with(:date).less_than(Time.now)

results = search.run!



Cheers,

Richard


 




Mauro Asprea

unread,
Mar 4, 2012, 4:34:04 AM3/4/12
to ruby-s...@googlegroups.com
I think that for building up the search query it would be better if you use the #build method and build it up block by block ;)

http://rubydoc.info/gems/sunspot/Sunspot/Search/AbstractSearch#build-instance_method

Richard Hulse wrote:
--
You received this message because you are subscribed to the Google
Groups "Sunspot" group.
To view this discussion on the web visit

Carlos Monson

unread,
Apr 4, 2012, 4:33:08 PM4/4/12
to Sunspot
How can I pass a different block

I try to do

block = lambda { with(:fullname, 'Pepe') }
search = Sunspot.new_search(User)
search.build block.call
search.execute

Without luck, because it can't find with in the scope the block is
called.


On Mar 4, 6:34 am, Mauro Asprea <mauroasp...@gmail.com> wrote:
> I think that for building up the search query it would be better if you
> use the #build method and build it up block by block ;)
>
> http://rubydoc.info/gems/sunspot/Sunspot/Search/AbstractSearch#build-...

Richard Hulse

unread,
Apr 4, 2012, 5:06:09 PM4/4/12
to ruby-s...@googlegroups.com
The way I do it is something like this:


scopes = "with (:fullname, 'Pepe')"

search_object = Sunspot.search(User) do
   eval scopes
end

Matthew A. Brown

unread,
Apr 4, 2012, 5:26:27 PM4/4/12
to ruby-s...@googlegroups.com
block = lambda { with(:fullname, 'Pepe') }
search = Sunspot.new_search(User)
search.build(&block)
search.execute

> --
> You received this message because you are subscribed to the Google Groups
> "Sunspot" group.
> To view this discussion on the web visit

> https://groups.google.com/d/msg/ruby-sunspot/-/FfZqnvBBafAJ.

Reply all
Reply to author
Forward
0 new messages