Advanced Searching in Rails
Advanced Searching in Rails
Spelled With An
“S”
Advanced Searching in Rails
Steve Midgley
http://www.misuse.org/science
http://www.hutz.com
May 30th 2008
Why?
Who Should Be Here?
• Free text search isn’t enough.
• Want multi-column parameterized
searching.
– Buying/finding things with
specific attributes
• Date-driven
• Price-driven
• Categorized
• Building advanced searching is HARD.
Here
The Four Things You
Have to Deal With
• POST/GET Params representing
Search Criteria
• Merging Search Criteria with
Persistent Criteria
– Session/backend, hidden input
tags or on the URL line
• Converting Search Criteria to
Search Rules to SQL
• Paginating the Search & Render
Advanced
Search
Anatomy of a Search
HTTP Form Previous
POST/GET Criteria SQL
Query
Params Whatever
Object Object
Data
Objects
Merge
Add/Merge/
Replace/Delete
Persist
back to
session
Search
Criteria
Send to
Template
Search
Business Pagination
Rules
Post
Converting HTTP Params
to Search Criteria
• For clean URL’s use POST’s. Make your
URL’s are distinct for core search
options!
http://mysite.com/find-
vacations/p/1/United-
States/California
NOT
http://mysite.com/controller/action?wt
f=is&all=this
• Why: google-juice, page caching, LB
splitting, happy customers, decoupled
controller from URL logic
Converting HTTP Params to
Search Criteria
• You are probably going to have to
create custom input tags for your
search.
<input name=“property[min_price]”>
• Think of incoming params as
associated with columns in your
database
Property[id] = 1092
Property[min_price] = 299
Region[id] = 11
Region[ids][] = [11,22,44]
Merge
Merging Search Criteria
to/from Session
HTTP Form Previous
POST/GET Criteria
Params Session
Object Object
Merge
Merging Search Criteria
to/from Session
• User searches for a set of
Regions:
Region[ids][] = [11,22,44]
• User wants to adjust that
by adding one more region
Region[ids][] = [33]
• Our search criteria should now
be: [11,22,33,44]
• This makes your UI more flexible.
Merging Search Criteria
to/from Session, more
[11,22,33,44]
• User wants to clear this set of
parameters and add two
Region[ids][] = [--,55,66]
[55,66]
• We have to cope with single item
changes especially if you use Ajax:
remove only item: [--55]
[66]
Misuse.org/science => “deep_merge”
Params + SQL
Converting Search Criteria
to Search Rules to SQL
• This is the core of your search. You
have to convert:
Region[ids] = [33,44,55]
• Into SQL:
region.id in (33,44,55)
Property[min_search_rate] = 245
• Into SQL:
@query.offset = @paginator.current.offset
mysite.com/rentals/p/1/city/
San-Diego/United-States/
California/SoCal
Persistent params to SQL
• Is this a controller thingy or a model
thingy?
• Many options: I use a controller
“module mix-in” (i.e.
“acts_as_search_engine”).
• A Model based mix-in seems ok too.
• Key concept: Build SQL incrementally:
pass around whatever SQL storage
container you’ve got
– Don’t try to do all your SQL builds
in one method: that leads to
spaghetti.
– Be Modular
Controller Search Logic
Module Search
public
def merge_params(params, session);.. end
def sql_assembler(sql_obj, criteria);..end
protected
def build_rate_sql(sql_obj, criteria);..end
def build_sqft_sql(sql_obj, criteria);.. end
//etc...
end
//...
Class SearchController
include Search
def results
criteria = merge_params(params, session)
sql_assembler(sql_obj.new, criteria)
Property.count_by_sql(sql_obj.count_sql)
Property.find_by_sql(sql_obj.find_sql)
end
end
Persisting a Search
• Store as individual elements
– More coding, some pain, flexible
• Marshall your SQL object
– Less coding, less pain, less
flexible
• Marshall criteria object
– Less coding, some pain, some flex
• Store as SQL clauses
– Please don’t
PostgreSQL
Geographic Search, DB
• PostgreSQL is really, really great
• Built-in functions and indices to find
all points within a polygon
• This makes rough geo-searching
ridiculously fast
– (the world is made flat but if your
polygon is small relative to the
surface of the earth, who cares?)
• Ara T Howard says “Divide the world
into a flat grid, map features into a
grid cell, use normal db indexing.”
Be Prepared
Gotchas
• ActiveRecord is a dog
– ActiveRecord is not built for lots of
objects. Find all the rows you need in
SQL. Then pull only those into AR.
– If you need to loop through rows use
something like Hash Extension which
will pull down SQL data as hashes – you
can then iterate quickly and convert
the ones you want to AR objects as
needed:
http://enterpriserails.rubyforge.org/hash_e
Gotchas, page 2
• Managing the Browser Cache
– Browser caching can screw up your
search tool, when the user uses the
“back” button to a POST page.
– They get a message along the lines of
“Cache expired: click reload to post
data again.”
– Normally this is a good thing, in that
case you must tell Rails to tell the
browser that caching is “OK” for these
specific pages. You do that with this
code in your controller action (I use a
filter for this):
expires_in 24.hours, :private => false
There is Always More
• Steve Midgley
• [email protected]
• www.misuse.org/science
– GeoX: Simple Rails geocoding
– MojoMagick: Simple Rails image tool
• www.hutz.com
• Happy Coding!
• Questions!