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| %>
<%= 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!