I'm a big fan of the excellent Sphinx full text engine, and I have some projects that use UltraSphinx and others that just use the Ruby API, Riddle, directly. Riddle supports limits and offsets but (understandably) doesn't do Railsy pagination - so Rich Kilmer wrote some code to do that.
The basic idea is to implement enough of will_paginate's WillPaginate::Collection methods to make things work. In this case we're searching a bunch of Book objects from my military reading list site:
class SearchResults
def initialize(query_results, page, page_size)
@query_results = query_results
@page = page
@page_size = page_size
@books = Book.find(@query_results[:matches].map{|match| match[:doc]})
end
def previous_page
@page == 1 ? @page : @page - 1
end
def next_page
(@page == total_pages ? total_pages : @page + 1)
end
def current_page
@page
end
def total_pages
(@query_results[:total_found]/@page_size)+1
end
def each(&block)
@books.each(&block)
end
def empty?
@books.empty?
end
end
Here's the searching code; we can just put this in a class method on Book:
def self.search(terms, options = {})
client = Riddle::Client.new("localhost", 3312)
page = options[:page].to_i || 1
page_size = options[:page_size] || 20
client.offset = (page - 1) * page_size
client.limit = page_size
SearchResults.new(client.query(terms), page, page_size)
end
We also need a simple controller action:
def search @books = Book.search(params[:term], params) end
And a route to get us there with nice URLs:
map.search "/search/:term/:page", :controller => 'books', :action => 'search'
And, finally, the standard will_paginate view code:
<%= will_paginate(@books)%> <% @books.each do |b| %> <br/><%= b.title %> <% end %>
That's about it! I usually test this stuff by using Mocha to replace Riddle::Client.query with a stub that returns a Hash of search result information. Pretty standard stuff, really. Enjoy!
Why wouldn't you just use ThinkingSphinx? It's a wrapper on the Riddle API written by the developer of the Riddle API and its result sets already have a will_paginate compatible interface.
Posted by: Rein Henrichs | February 17, 2009 at 04:29 PM
@rein - yeah, I haven't used Thinking Sphinx, although I've used UltraSphinx and I hear those two are kind of similar. In the past I've used Riddle directly since Sphinx was running on different machine and the indexer was running on it's own schedule... also, the Riddle API seems pretty straightforward. Nothing against Thinking Sphinx, of course.... whatever works!
Posted by: tomcopeland | February 18, 2009 at 08:16 AM
Maybe I'm missing something here, but doesn't blindly adding 1 to total_results / page_size suggest that on average 1 in page_size times the total pages count will be off by one. Wouldn't it be better to check if total results % page size == 0 and if not then add 1?
Posted by: Luke Randall | February 18, 2009 at 12:53 PM