Added support for couchdb-lucene.

Added a search method to CouchRest:Database to search the documents in a
given database.  Added support for a :search parameter to Collection's
paginated_each method, which will allow you to paginate over a set of
search results.

This code has been brought to you by Dave Farkas (sakrafd) and
Arnaud Berthomier (oz).
This commit is contained in:
John Wood 2010-03-11 11:17:15 -06:00 committed by Will Leinweber
parent 13d76d38de
commit b26f90d2ff
5 changed files with 115 additions and 9 deletions

View file

@ -44,7 +44,14 @@ module CouchRest
CouchRest.get url CouchRest.get url
end end
end end
# Query a CouchDB-Lucene search view
def search(name, params={})
# -> http://localhost:5984/yourdb/_fti/YourDesign/by_name?include_docs=true&q=plop*'
url = CouchRest.paramify_url "#{root}/_fti/#{name}", params
CouchRest.get url
end
# load a set of documents by passing an array of ids # load a set of documents by passing an array of ids
def get_bulk(ids) def get_bulk(ids)
documents(:keys => ids, :include_docs => true) documents(:keys => ids, :include_docs => true)

View file

@ -42,7 +42,12 @@ module CouchRest
# #
# Defaults are used if these options are not specified. # Defaults are used if these options are not specified.
def paginated_each(options, &block) def paginated_each(options, &block)
proxy = create_collection_proxy(options) search = options.delete(:search)
unless search == true
proxy = create_collection_proxy(options)
else
proxy = create_search_collection_proxy(options)
end
proxy.paginated_each(options, &block) proxy.paginated_each(options, &block)
end end
@ -61,6 +66,11 @@ module CouchRest
CollectionProxy.new(database, design_doc, view_name, view_options, self) CollectionProxy.new(database, design_doc, view_name, view_options, self)
end end
def create_search_collection_proxy(options)
design_doc, search_name, search_options = parse_search_options(options)
CollectionProxy.new(@database, design_doc, search_name, search_options, self, :search)
end
def parse_view_options(options) def parse_view_options(options)
design_doc = options.delete(:design_doc) design_doc = options.delete(:design_doc)
raise ArgumentError, 'design_doc is required' if design_doc.nil? raise ArgumentError, 'design_doc is required' if design_doc.nil?
@ -75,6 +85,18 @@ module CouchRest
[design_doc, view_name, view_options] [design_doc, view_name, view_options]
end end
def parse_search_options(options)
design_doc = options.delete(:design_doc)
raise ArgumentError, 'design_doc is required' if design_doc.nil?
search_name = options.delete(:view_name)
raise ArgumentError, 'search_name is required' if search_name.nil?
search_options = options.clone
[design_doc, search_name, search_options]
end
end end
class CollectionProxy class CollectionProxy
@ -91,11 +113,12 @@ module CouchRest
# #
# The CollectionProxy provides support for paginating over a collection # The CollectionProxy provides support for paginating over a collection
# via the paginate, and paginated_each methods. # via the paginate, and paginated_each methods.
def initialize(database, design_doc, view_name, view_options = {}, container_class = nil) def initialize(database, design_doc, view_name, view_options = {}, container_class = nil, query_type = :view)
raise ArgumentError, "database is a required parameter" if database.nil? raise ArgumentError, "database is a required parameter" if database.nil?
@database = database @database = database
@container_class = container_class @container_class = container_class
@query_type = query_type
strip_pagination_options(view_options) strip_pagination_options(view_options)
@view_options = view_options @view_options = view_options
@ -110,10 +133,22 @@ module CouchRest
# See Collection.paginate # See Collection.paginate
def paginate(options = {}) def paginate(options = {})
page, per_page = parse_options(options) page, per_page = parse_options(options)
results = @database.view(@view_name, pagination_options(page, per_page)) results = @database.send(@query_type, @view_name, pagination_options(page, per_page))
remember_where_we_left_off(results, page) remember_where_we_left_off(results, page)
results = convert_to_container_array(results) instances = convert_to_container_array(results)
results
begin
if Kernel.const_get('WillPaginate')
total_rows = results['total_rows'].to_i
paginated = WillPaginate::Collection.create(page, per_page, total_rows) do |pager|
pager.replace(instances)
end
return paginated
end
rescue NameError
# When not using will_paginate, not much we could do about this. :x
end
return instances
end end
# See Collection.paginated_each # See Collection.paginated_each
@ -152,7 +187,8 @@ module CouchRest
def load_target def load_target
unless loaded? unless loaded?
results = @database.view(@view_name, @view_options) @view_options.merge!({:include_docs => true}) if @query_type == :search
results = @database.send(@query_type, @view_name, @view_options)
@target = convert_to_container_array(results) @target = convert_to_container_array(results)
end end
@loaded = true @loaded = true
@ -189,7 +225,7 @@ module CouchRest
def pagination_options(page, per_page) def pagination_options(page, per_page)
view_options = @view_options.clone view_options = @view_options.clone
if @last_key && @last_docid && @last_page == page - 1 if @query_type == :view && @last_key && @last_docid && @last_page == page - 1
key = view_options.delete(:key) key = view_options.delete(:key)
end_key = view_options[:endkey] || key end_key = view_options[:endkey] || key
options = { :startkey => @last_key, :endkey => end_key, :startkey_docid => @last_docid, :limit => per_page, :skip => 1 } options = { :startkey => @last_key, :endkey => end_key, :startkey_docid => @last_docid, :limit => per_page, :skip => 1 }

View file

@ -816,5 +816,25 @@ describe CouchRest::Database do
end end
describe "searching a database" do
before(:each) do
search_function = { 'defaults' => {'store' => 'no', 'index' => 'analyzed_no_norms'},
'index' => "function(doc) { ret = new Document(); ret.add(doc['name'], {'field':'name'}); ret.add(doc['age'], {'field':'age'}); return ret; }" }
@db.save_doc({'_id' => '_design/search', 'fulltext' => {'people' => search_function}})
@db.save_doc({'_id' => 'john', 'name' => 'John', 'age' => '31'})
@db.save_doc({'_id' => 'jack', 'name' => 'Jack', 'age' => '32'})
@db.save_doc({'_id' => 'dave', 'name' => 'Dave', 'age' => '33'})
end
it "should be able to search a database using couchdb-lucene" do
if couchdb_lucene_available?
result = @db.search('search/people', :q => 'name:J*')
doc_ids = result['rows'].collect{ |row| row['id'] }
doc_ids.size.should == 2
doc_ids.should include('john')
doc_ids.should include('jack')
end
end
end
end end

View file

@ -762,4 +762,36 @@ describe "ExtendedDocument" do
cat.save.should be_true cat.save.should be_true
end end
end end
describe "searching the contents of an extended document" do
before :each do
@db = reset_test_db!
names = ["Fuzzy", "Whiskers", "Mr Bigglesworth", "Sockington", "Smitty", "Sammy", "Samson", "Simon"]
names.each { |name| Cat.create(:name => name) }
search_function = { 'defaults' => {'store' => 'no', 'index' => 'analyzed_no_norms'},
'index' => "function(doc) { ret = new Document(); ret.add(doc['name'], {'field':'name'}); return ret; }" }
@db.save_doc({'_id' => '_design/search', 'fulltext' => {'cats' => search_function}})
end
it "should be able to paginate through a large set of search results" do
if couchdb_lucene_available?
names = []
Cat.paginated_each(:design_doc => "search", :view_name => "cats",
:q => 'name:S*', :search => true, :include_docs => true, :per_page => 3) do |cat|
cat.should_not be_nil
names << cat.name
end
names.size.should == 5
names.should include('Sockington')
names.should include('Smitty')
names.should include('Sammy')
names.should include('Samson')
names.should include('Simon')
end
end
end
end end

View file

@ -35,4 +35,15 @@ Spec::Runner.configure do |config|
cr.database(db).delete! rescue nil cr.database(db).delete! rescue nil
end end
end end
end end
def couchdb_lucene_available?
lucene_path = "http://localhost:5985/"
url = URI.parse(lucene_path)
req = Net::HTTP::Get.new(url.path)
res = Net::HTTP.new(url.host, url.port).start { |http| http.request(req) }
true
rescue Exception => e
false
end