Merge branch 'master' of git://github.com/couchrest/couchrest

This commit is contained in:
Sam Lown 2010-03-30 20:57:22 +00:00
commit 1b019fa3fe
11 changed files with 222 additions and 113 deletions

View file

@ -20,7 +20,7 @@ begin
gemspec.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments." gemspec.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
gemspec.email = "jchris@apache.org" gemspec.email = "jchris@apache.org"
gemspec.homepage = "http://github.com/couchrest/couchrest" gemspec.homepage = "http://github.com/couchrest/couchrest"
gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos"] gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber"]
gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md ) gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
gemspec.files = %w( LICENSE README.md Rakefile THANKS.md history.txt) + Dir["{examples,lib,spec,utils}/**/*"] - Dir["spec/tmp"] gemspec.files = %w( LICENSE README.md Rakefile THANKS.md history.txt) + Dir["{examples,lib,spec,utils}/**/*"] - Dir["spec/tmp"]
gemspec.has_rdoc = true gemspec.has_rdoc = true

View file

@ -1,8 +1,12 @@
== Next Version == Next Version
* Major enhancements * Major enhancements
* Adds support for continuous replication (sauy7)
* Automatic Type Casting (Alexander Uvarov, Sam Lown, Tim Heighes, Will Leinweber)
* Added a search method to CouchRest:Database to search the documents in a
given database. (Dave Farkas, Arnaud Berthomier, John Wood)
* Minor enhancements * Minor enhancements
* Provide a description of the timeout error (John Wood)
== 0.35 == 0.35

View file

@ -96,15 +96,18 @@ module CouchRest
def parse url def parse url
case url case url
when /^https?:\/\/(.*)\/(.*)\/(.*)/ when /^(https?:\/\/)(.*)\/(.*)\/(.*)/
host = $1 scheme = $1
db = $2 host = $2
docid = $3 db = $3
when /^https?:\/\/(.*)\/(.*)/ docid = $4
host = $1 when /^(https?:\/\/)(.*)\/(.*)/
db = $2 scheme = $1
when /^https?:\/\/(.*)/ host = $2
host = $1 db = $3
when /^(https?:\/\/)(.*)/
scheme = $1
host = $2
when /(.*)\/(.*)\/(.*)/ when /(.*)\/(.*)\/(.*)/
host = $1 host = $1
db = $2 db = $2
@ -119,7 +122,7 @@ module CouchRest
db = nil if db && db.empty? db = nil if db && db.empty?
{ {
:host => host || "127.0.0.1:5984", :host => (scheme || "http://") + (host || "127.0.0.1:5984"),
:database => db, :database => db,
:doc => docid :doc => docid
} }

View file

@ -45,6 +45,13 @@ module CouchRest
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)
@ -297,15 +304,13 @@ module CouchRest
end end
# Replicates via "pulling" from another database to this database. Makes no attempt to deal with conflicts. # Replicates via "pulling" from another database to this database. Makes no attempt to deal with conflicts.
def replicate_from other_db def replicate_from other_db, continuous=false
raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database) replicate other_db, continuous, :target => name
CouchRest.post "#{@host}/_replicate", :source => other_db.root, :target => name
end end
# Replicates via "pushing" to another database. Makes no attempt to deal with conflicts. # Replicates via "pushing" to another database. Makes no attempt to deal with conflicts.
def replicate_to other_db def replicate_to other_db, continuous=false
raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database) replicate other_db, continuous, :source => name
CouchRest.post "#{@host}/_replicate", :target => other_db.root, :source => name
end end
# DELETE the database itself. This is not undoable and could be rather # DELETE the database itself. This is not undoable and could be rather
@ -317,6 +322,19 @@ module CouchRest
private private
def replicate other_db, continuous, options
raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database)
raise ArgumentError, "must provide a target or source option" unless (options.key?(:target) || options.key?(:source))
payload = options
if options.has_key?(:target)
payload[:source] = other_db.root
else
payload[:target] = other_db.root
end
payload[:continuous] = continuous
CouchRest.post "#{@host}/_replicate", payload
end
def clear_extended_doc_fresh_cache def clear_extended_doc_fresh_cache
::CouchRest::ExtendedDocument.subclasses.each{|klass| klass.design_doc_fresh = false if klass.respond_to?(:design_doc_fresh=) } ::CouchRest::ExtendedDocument.subclasses.each{|klass| klass.design_doc_fresh = false if klass.respond_to?(:design_doc_fresh=) }
end end

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)
search = options.delete(:search)
unless search == true
proxy = create_collection_proxy(options) 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

@ -39,7 +39,7 @@ if RUBY_VERSION.to_f < 1.9
if IO.select([@io], nil, nil, @read_timeout) if IO.select([@io], nil, nil, @read_timeout)
retry retry
else else
raise Timeout::Error raise Timeout::Error, "IO timeout"
end end
end end
else else

View file

@ -46,137 +46,74 @@ describe CouchRest do
it "should parse just a dbname" do it "should parse just a dbname" do
db = CouchRest.parse "my-db" db = CouchRest.parse "my-db"
db[:database].should == "my-db" db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5984" db[:host].should == "http://127.0.0.1:5984"
end end
it "should parse a host and db" do it "should parse a host and db" do
db = CouchRest.parse "127.0.0.1/my-db" db = CouchRest.parse "127.0.0.1/my-db"
db[:database].should == "my-db" db[:database].should == "my-db"
db[:host].should == "127.0.0.1" db[:host].should == "http://127.0.0.1"
end
it "should parse a host and db with http" do
db = CouchRest.parse "https://127.0.0.1/my-db"
db[:database].should == "my-db"
db[:host].should == "127.0.0.1"
end
it "should parse a host with a port and db" do
db = CouchRest.parse "127.0.0.1:5555/my-db"
db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555"
end
it "should parse a host with a port and db with http" do
db = CouchRest.parse "http://127.0.0.1:5555/my-db"
db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555"
end
it "should parse a host with a port and db with https" do
db = CouchRest.parse "https://127.0.0.1:5555/my-db"
db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555"
end
it "should parse just a host" do
db = CouchRest.parse "http://127.0.0.1:5555/"
db[:database].should be_nil
db[:host].should == "127.0.0.1:5555"
end
it "should parse just a host with https" do
db = CouchRest.parse "https://127.0.0.1:5555/"
db[:database].should be_nil
db[:host].should == "127.0.0.1:5555"
end
it "should parse just a host no slash" do
db = CouchRest.parse "http://127.0.0.1:5555"
db[:host].should == "127.0.0.1:5555"
db[:database].should be_nil
end
it "should parse just a host no slash and https" do
db = CouchRest.parse "https://127.0.0.1:5555"
db[:host].should == "127.0.0.1:5555"
db[:database].should be_nil
end
it "should get docid" do
db = CouchRest.parse "127.0.0.1:5555/my-db/my-doc"
db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555"
db[:doc].should == "my-doc"
end
it "should get docid with http" do
db = CouchRest.parse "http://127.0.0.1:5555/my-db/my-doc"
db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555"
db[:doc].should == "my-doc"
end
it "should get docid with https" do
db = CouchRest.parse "https://127.0.0.1:5555/my-db/my-doc"
db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555"
db[:doc].should == "my-doc"
end
it "should parse a host and db" do
db = CouchRest.parse "127.0.0.1/my-db"
db[:database].should == "my-db"
db[:host].should == "127.0.0.1"
end end
it "should parse a host and db with http" do it "should parse a host and db with http" do
db = CouchRest.parse "http://127.0.0.1/my-db" db = CouchRest.parse "http://127.0.0.1/my-db"
db[:database].should == "my-db" db[:database].should == "my-db"
db[:host].should == "127.0.0.1" db[:host].should == "http://127.0.0.1"
end end
it "should parse a host and db with https" do it "should parse a host and db with https" do
db = CouchRest.parse "https://127.0.0.1/my-db" db = CouchRest.parse "https://127.0.0.1/my-db"
db[:database].should == "my-db" db[:database].should == "my-db"
db[:host].should == "127.0.0.1" db[:host].should == "https://127.0.0.1"
end end
it "should parse a host with a port and db" do it "should parse a host with a port and db" do
db = CouchRest.parse "127.0.0.1:5555/my-db" db = CouchRest.parse "127.0.0.1:5555/my-db"
db[:database].should == "my-db" db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555" db[:host].should == "http://127.0.0.1:5555"
end end
it "should parse a host with a port and db with http" do it "should parse a host with a port and db with http" do
db = CouchRest.parse "http://127.0.0.1:5555/my-db" db = CouchRest.parse "http://127.0.0.1:5555/my-db"
db[:database].should == "my-db" db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555" db[:host].should == "http://127.0.0.1:5555"
end end
it "should parse a host with a port and db with https" do it "should parse a host with a port and db with https" do
db = CouchRest.parse "http://127.0.0.1:5555/my-db" db = CouchRest.parse "https://127.0.0.1:5555/my-db"
db[:database].should == "my-db" db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555" db[:host].should == "https://127.0.0.1:5555"
end end
it "should parse just a host" do it "should parse just a host" do
db = CouchRest.parse "http://127.0.0.1:5555/" db = CouchRest.parse "http://127.0.0.1:5555/"
db[:database].should be_nil db[:database].should be_nil
db[:host].should == "127.0.0.1:5555" db[:host].should == "http://127.0.0.1:5555"
end end
it "should parse just a host with https" do it "should parse just a host with https" do
db = CouchRest.parse "https://127.0.0.1:5555/" db = CouchRest.parse "https://127.0.0.1:5555/"
db[:database].should be_nil db[:database].should be_nil
db[:host].should == "127.0.0.1:5555" db[:host].should == "https://127.0.0.1:5555"
end end
it "should parse just a host no slash" do it "should parse just a host no slash" do
db = CouchRest.parse "http://127.0.0.1:5555" db = CouchRest.parse "http://127.0.0.1:5555"
db[:host].should == "127.0.0.1:5555" db[:host].should == "http://127.0.0.1:5555"
db[:database].should be_nil db[:database].should be_nil
end end
it "should parse just a host no slash and https" do it "should parse just a host no slash and https" do
db = CouchRest.parse "https://127.0.0.1:5555" db = CouchRest.parse "https://127.0.0.1:5555"
db[:host].should == "127.0.0.1:5555" db[:host].should == "https://127.0.0.1:5555"
db[:database].should be_nil db[:database].should be_nil
end end
it "should get docid" do it "should get docid" do
db = CouchRest.parse "127.0.0.1:5555/my-db/my-doc" db = CouchRest.parse "127.0.0.1:5555/my-db/my-doc"
db[:database].should == "my-db" db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555" db[:host].should == "http://127.0.0.1:5555"
db[:doc].should == "my-doc" db[:doc].should == "my-doc"
end end
it "should get docid with http" do it "should get docid with http" do
db = CouchRest.parse "http://127.0.0.1:5555/my-db/my-doc" db = CouchRest.parse "http://127.0.0.1:5555/my-db/my-doc"
db[:database].should == "my-db" db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555" db[:host].should == "http://127.0.0.1:5555"
db[:doc].should == "my-doc" db[:doc].should == "my-doc"
end end
it "should get docid with https" do it "should get docid with https" do
db = CouchRest.parse "https://127.0.0.1:5555/my-db/my-doc" db = CouchRest.parse "https://127.0.0.1:5555/my-db/my-doc"
db[:database].should == "my-db" db[:database].should == "my-db"
db[:host].should == "127.0.0.1:5555" db[:host].should == "https://127.0.0.1:5555"
db[:doc].should == "my-doc" db[:doc].should == "my-doc"
end end
end end
@ -185,7 +122,7 @@ describe CouchRest do
it "should be possible without an explicit CouchRest instantiation" do it "should be possible without an explicit CouchRest instantiation" do
db = CouchRest.database "http://127.0.0.1:5984/couchrest-test" db = CouchRest.database "http://127.0.0.1:5984/couchrest-test"
db.should be_an_instance_of(CouchRest::Database) db.should be_an_instance_of(CouchRest::Database)
db.host.should == "127.0.0.1:5984" db.host.should == "http://127.0.0.1:5984"
end end
# TODO add https support (need test environment...) # TODO add https support (need test environment...)
# it "should work with https" # do # it "should work with https" # do

View file

@ -703,12 +703,12 @@ describe CouchRest::Database do
end end
end end
describe "replicating a database" do describe "simply replicating a database" do
before do before do
@db.save_doc({'_id' => 'test_doc', 'some-value' => 'foo'}) @db.save_doc({'_id' => 'test_doc', 'some-value' => 'foo'})
@other_db = @cr.database 'couchrest-test-replication' @other_db = @cr.database REPLICATIONDB
@other_db.delete! rescue nil @other_db.delete! rescue nil
@other_db = @cr.create_db 'couchrest-test-replication' @other_db = @cr.create_db REPLICATIONDB
end end
describe "via pulling" do describe "via pulling" do
@ -734,6 +734,53 @@ describe CouchRest::Database do
end end
end end
describe "continuously replicating a database" do
before do
@db.save_doc({'_id' => 'test_doc', 'some-value' => 'foo'})
@other_db = @cr.database REPLICATIONDB
@other_db.delete! rescue nil
@other_db = @cr.create_db REPLICATIONDB
end
describe "via pulling" do
before do
@other_db.replicate_from @db, true
end
it "contains the document from the original database" do
sleep(1) # Allow some time to replicate
doc = @other_db.get('test_doc')
doc['some-value'].should == 'foo'
end
it "contains documents saved after replication initiated" do
@db.save_doc({'_id' => 'test_doc_after', 'some-value' => 'bar'})
sleep(1) # Allow some time to replicate
doc = @other_db.get('test_doc_after')
doc['some-value'].should == 'bar'
end
end
describe "via pushing" do
before do
@db.replicate_to @other_db, true
end
it "copies the document to the other database" do
sleep(1) # Allow some time to replicate
doc = @other_db.get('test_doc')
doc['some-value'].should == 'foo'
end
it "copies documents saved after replication initiated" do
@db.save_doc({'_id' => 'test_doc_after', 'some-value' => 'bar'})
sleep(1) # Allow some time to replicate
doc = @other_db.get('test_doc_after')
doc['some-value'].should == 'bar'
end
end
end
describe "creating a database" do describe "creating a database" do
before(:each) do before(:each) do
@db = @cr.database('couchrest-test-db_to_create') @db = @cr.database('couchrest-test-db_to_create')
@ -769,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

@ -172,7 +172,7 @@ describe "ExtendedDocument properties" do
describe "when type primitive is an Object" do describe "when type primitive is an Object" do
it "it should not cast given value" do it "it should not cast given value" do
@course.participants = [{}, 'q', 1] @course.participants = [{}, 'q', 1]
@course['participants'].should eql([{}, 'q', 1]) @course['participants'].should == [{}, 'q', 1]
end end
it "should cast started_on to Date" do it "should cast started_on to Date" do

View file

@ -10,6 +10,7 @@ unless defined?(FIXTURE_PATH)
COUCHHOST = "http://127.0.0.1:5984" COUCHHOST = "http://127.0.0.1:5984"
TESTDB = 'couchrest-test' TESTDB = 'couchrest-test'
REPLICATIONDB = 'couchrest-test-replication'
TEST_SERVER = CouchRest.new TEST_SERVER = CouchRest.new
TEST_SERVER.default_database = TESTDB TEST_SERVER.default_database = TESTDB
DB = TEST_SERVER.database(TESTDB) DB = TEST_SERVER.database(TESTDB)
@ -35,3 +36,14 @@ Spec::Runner.configure do |config|
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