diff --git a/lib/couchrest.rb b/lib/couchrest.rb
index f55d606..ad99639 100644
--- a/lib/couchrest.rb
+++ b/lib/couchrest.rb
@@ -37,6 +37,8 @@ module CouchRest
autoload :FileManager, 'couchrest/helper/file_manager'
autoload :Streamer, 'couchrest/helper/streamer'
+ require File.join(File.dirname(__FILE__), 'couchrest', 'mixins')
+
# The CouchRest module methods handle the basic JSON serialization
# and deserialization, as well as query parameters. The module also includes
# some helpers for tasks like instantiating a new Database or Server instance.
diff --git a/lib/couchrest/core/document.rb b/lib/couchrest/core/document.rb
index 681814e..51fea23 100644
--- a/lib/couchrest/core/document.rb
+++ b/lib/couchrest/core/document.rb
@@ -14,62 +14,39 @@ module CouchRest
end
class Document < Response
+ include CouchRest::Mixins::Views
attr_accessor :database
-
- # alias for self['_id']
- def id
- self['_id']
+ @@database = nil
+
+ # override the CouchRest::Model-wide default_database
+ # This is not a thread safe operation, do not change the model
+ # database at runtime.
+ def self.use_database(db)
+ @@database = db
end
-
- # alias for self['_rev']
- def rev
- self['_rev']
+
+ def self.database
+ @@database
end
-
- # returns true if the document has never been saved
- def new_document?
- !rev
- end
-
- # Saves the document to the db using create or update. Also runs the :save
- # callbacks. Sets the _id and _rev fields based on
- # CouchDB's response.
- # If bulk is true (defaults to false) the document is cached for bulk save.
- def save(bulk = false)
- raise ArgumentError, "doc.database required for saving" unless database
- result = database.save_doc self, bulk
- result['ok']
- end
-
- # Deletes the document from the database. Runs the :delete callbacks.
- # Removes the _id and _rev fields, preparing the
- # document to be saved to a new _id.
- # If bulk is true (defaults to false) the document won't
- # actually be deleted from the db until bulk save.
- def destroy(bulk = false)
- raise ArgumentError, "doc.database required to destroy" unless database
- result = database.delete_doc(self, bulk)
- if result['ok']
- self['_rev'] = nil
- self['_id'] = nil
+
+ # Returns the CouchDB uri for the document
+ def uri(append_rev = false)
+ return nil if new_document?
+ couch_uri = "http://#{database.uri}/#{CGI.escape(id)}"
+ if append_rev == true
+ couch_uri << "?rev=#{rev}"
+ elsif append_rev.kind_of?(Integer)
+ couch_uri << "?rev=#{append_rev}"
end
- result['ok']
+ couch_uri
end
- def copy(dest)
- raise ArgumentError, "doc.database required to copy" unless database
- result = database.copy_doc(self, dest)
- result['ok']
- end
-
- def move(dest)
- raise ArgumentError, "doc.database required to copy" unless database
- result = database.move_doc(self, dest)
- result['ok']
+ # Returns the document's database
+ def database
+ @database || self.class.database
end
end
-
end
diff --git a/lib/couchrest/core/server.rb b/lib/couchrest/core/server.rb
index e76ab82..8d39998 100644
--- a/lib/couchrest/core/server.rb
+++ b/lib/couchrest/core/server.rb
@@ -1,25 +1,62 @@
module CouchRest
class Server
- attr_accessor :uri, :uuid_batch_count
- def initialize server = 'http://127.0.0.1:5984', uuid_batch_count = 1000
+ attr_accessor :uri, :uuid_batch_count, :available_databases
+ def initialize(server = 'http://127.0.0.1:5984', uuid_batch_count = 1000)
@uri = server
@uuid_batch_count = uuid_batch_count
end
- # List all databases on the server
+ # Lists all "available" databases.
+ # An available database, is a database that was specified
+ # as avaiable by your code.
+ # It allows to define common databases to use and reuse in your code
+ def available_databases
+ @available_databases ||= {}
+ end
+
+ # Adds a new available database and create it unless it already exists
+ #
+ # Example:
+ #
+ # @couch = CouchRest::Server.new
+ # @couch.define_available_database(:default, "tech-blog")
+ #
+ def define_available_database(reference, db_name, create_unless_exists = true)
+ available_databases[reference.to_sym] = create_unless_exists ? database!(db_name) : database(db_name)
+ end
+
+ # Checks that a database is set as available
+ #
+ # Example:
+ #
+ # @couch.available_database?(:default)
+ #
+ def available_database?(ref_or_name)
+ ref_or_name.is_a?(Symbol) ? available_databases.keys.include?(ref_or_name) : available_databases.values.map{|db| db.name}.include?(ref_or_name)
+ end
+
+ def default_database=(name, create_unless_exists = true)
+ define_available_database(:default, name, create_unless_exists = true)
+ end
+
+ def default_database
+ available_databases[:default]
+ end
+
+ # Lists all databases on the server
def databases
CouchRest.get "#{@uri}/_all_dbs"
end
# Returns a CouchRest::Database for the given name
- def database name
+ def database(name)
CouchRest::Database.new(self, name)
end
# Creates the database if it doesn't exist
- def database! name
+ def database!(name)
create_db(name) rescue nil
- database name
+ database(name)
end
# GET the welcome message
@@ -28,9 +65,9 @@ module CouchRest
end
# Create a database
- def create_db name
+ def create_db(name)
CouchRest.put "#{@uri}/#{name}"
- database name
+ database(name)
end
# Restart the CouchDB instance
@@ -39,7 +76,7 @@ module CouchRest
end
# Retrive an unused UUID from CouchDB. Server instances manage caching a list of unused UUIDs.
- def next_uuid count = @uuid_batch_count
+ def next_uuid(count = @uuid_batch_count)
@uuids ||= []
if @uuids.empty?
@uuids = CouchRest.post("#{@uri}/_uuids?count=#{count}")["uuids"]
diff --git a/lib/couchrest/mixins.rb b/lib/couchrest/mixins.rb
new file mode 100644
index 0000000..e482ffb
--- /dev/null
+++ b/lib/couchrest/mixins.rb
@@ -0,0 +1,3 @@
+mixins_dir = File.join(File.dirname(__FILE__), 'mixins')
+
+require File.join(mixins_dir, 'views')
\ No newline at end of file
diff --git a/lib/couchrest/mixins/views.rb b/lib/couchrest/mixins/views.rb
new file mode 100644
index 0000000..36eebf0
--- /dev/null
+++ b/lib/couchrest/mixins/views.rb
@@ -0,0 +1,59 @@
+module CouchRest
+ module Mixins
+ module Views
+
+ # alias for self['_id']
+ def id
+ self['_id']
+ end
+
+ # alias for self['_rev']
+ def rev
+ self['_rev']
+ end
+
+ # returns true if the document has never been saved
+ def new_document?
+ !rev
+ end
+
+ # Saves the document to the db using create or update. Also runs the :save
+ # callbacks. Sets the _id and _rev fields based on
+ # CouchDB's response.
+ # If bulk is true (defaults to false) the document is cached for bulk save.
+ def save(bulk = false)
+ raise ArgumentError, "doc.database required for saving" unless database
+ result = database.save_doc self, bulk
+ result['ok']
+ end
+
+ # Deletes the document from the database. Runs the :delete callbacks.
+ # Removes the _id and _rev fields, preparing the
+ # document to be saved to a new _id.
+ # If bulk is true (defaults to false) the document won't
+ # actually be deleted from the db until bulk save.
+ def destroy(bulk = false)
+ raise ArgumentError, "doc.database required to destroy" unless database
+ result = database.delete_doc(self, bulk)
+ if result['ok']
+ self['_rev'] = nil
+ self['_id'] = nil
+ end
+ result['ok']
+ end
+
+ def copy(dest)
+ raise ArgumentError, "doc.database required to copy" unless database
+ result = database.copy_doc(self, dest)
+ result['ok']
+ end
+
+ def move(dest)
+ raise ArgumentError, "doc.database required to copy" unless database
+ result = database.move_doc(self, dest)
+ result['ok']
+ end
+
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/couchrest/core/document_spec.rb b/spec/couchrest/core/document_spec.rb
index dc59dbf..8caf0cd 100644
--- a/spec/couchrest/core/document_spec.rb
+++ b/spec/couchrest/core/document_spec.rb
@@ -1,213 +1,241 @@
require File.dirname(__FILE__) + '/../../spec_helper'
-describe CouchRest::Document, "[]=" do
- before(:each) do
- @doc = CouchRest::Document.new
- end
- it "should work" do
- @doc["enamel"].should == nil
- @doc["enamel"] = "Strong"
- @doc["enamel"].should == "Strong"
- end
- it "[]= should convert to string" do
- @doc["enamel"].should == nil
- @doc[:enamel] = "Strong"
- @doc["enamel"].should == "Strong"
- end
- it "should read as a string" do
- @doc[:enamel] = "Strong"
- @doc[:enamel].should == "Strong"
- end
-end
+class Video < CouchRest::Document; end
-describe CouchRest::Document, "new" do
- before(:each) do
- @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
- end
- it "should create itself from a Hash" do
- @doc["key"].should == [1,2,3]
- @doc["more"].should == "values"
- end
- it "should not have rev and id" do
- @doc.rev.should be_nil
- @doc.id.should be_nil
- end
- it "should freak out when saving without a database" do
- lambda{@doc.save}.should raise_error(ArgumentError)
- end
-end
-
-# move to database spec
-describe CouchRest::Document, "saving using a database" do
+describe CouchRest::Document do
+
before(:all) do
- @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
- @db = reset_test_db!
- @resp = @db.save_doc(@doc)
- end
- it "should apply the database" do
- @doc.database.should == @db
- end
- it "should get id and rev" do
- @doc.id.should == @resp["id"]
- @doc.rev.should == @resp["rev"]
- end
-end
-
-describe CouchRest::Document, "bulk saving" do
- before :all do
- @db = reset_test_db!
+ @couch = CouchRest.new
+ @db = @couch.database!(TESTDB)
end
- it "should use the document bulk save cache" do
- doc = CouchRest::Document.new({"_id" => "bulkdoc", "val" => 3})
- doc.database = @db
- doc.save(true)
- lambda { doc.database.get(doc["_id"]) }.should raise_error(RestClient::ResourceNotFound)
- doc.database.bulk_save
- doc.database.get(doc["_id"])["val"].should == doc["val"]
- end
-end
-
-describe "getting from a database" do
- before(:all) do
- @db = reset_test_db!
- @resp = @db.save_doc({
- "key" => "value"
- })
- @doc = @db.get @resp['id']
- end
- it "should return a document" do
- @doc.should be_an_instance_of(CouchRest::Document)
- end
- it "should have a database" do
- @doc.database.should == @db
- end
- it "should be saveable and resavable" do
- @doc["more"] = "keys"
- @doc.save
- @db.get(@resp['id'])["more"].should == "keys"
- @doc["more"] = "these keys"
- @doc.save
- @db.get(@resp['id'])["more"].should == "these keys"
- end
-end
-
-describe "destroying a document from a db" do
- before(:all) do
- @db = reset_test_db!
- @resp = @db.save_doc({
- "key" => "value"
- })
- @doc = @db.get @resp['id']
- end
- it "should make it disappear" do
- @doc.destroy
- lambda{@db.get @resp['id']}.should raise_error
- end
- it "should error when there's no db" do
- @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
- lambda{@doc.destroy}.should raise_error(ArgumentError)
- end
-end
-
-
-describe "destroying a document from a db using bulk save" do
- before(:all) do
- @db = reset_test_db!
- @resp = @db.save_doc({
- "key" => "value"
- })
- @doc = @db.get @resp['id']
- end
- it "should defer actual deletion" do
- @doc.destroy(true)
- @doc['_id'].should == nil
- @doc['_rev'].should == nil
- lambda{@db.get @resp['id']}.should_not raise_error
- @db.bulk_save
- lambda{@db.get @resp['id']}.should raise_error
- end
-end
-
-describe "copying a document" do
- before :each do
- @db = reset_test_db!
- @resp = @db.save_doc({'key' => 'value'})
- @docid = 'new-location'
- @doc = @db.get(@resp['id'])
- end
- describe "to a new location" do
+ describe "[]=" do
+ before(:each) do
+ @doc = CouchRest::Document.new
+ end
it "should work" do
- @doc.copy @docid
- newdoc = @db.get(@docid)
- newdoc['key'].should == 'value'
+ @doc["enamel"].should == nil
+ @doc["enamel"] = "Strong"
+ @doc["enamel"].should == "Strong"
end
- it "should fail without a database" do
- lambda{CouchRest::Document.new({"not"=>"a real doc"}).copy}.should raise_error(ArgumentError)
+ it "[]= should convert to string" do
+ @doc["enamel"].should == nil
+ @doc[:enamel] = "Strong"
+ @doc["enamel"].should == "Strong"
+ end
+ it "should read as a string" do
+ @doc[:enamel] = "Strong"
+ @doc[:enamel].should == "Strong"
end
end
- describe "to an existing location" do
- before :each do
- @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
- end
- it "should fail without a rev" do
- lambda{@doc.copy @docid}.should raise_error(RestClient::RequestFailed)
- end
- it "should succeed with a rev" do
- @to_be_overwritten = @db.get(@docid)
- @doc.copy "#{@docid}?rev=#{@to_be_overwritten['_rev']}"
- newdoc = @db.get(@docid)
- newdoc['key'].should == 'value'
- end
- it "should succeed given the doc to overwrite" do
- @to_be_overwritten = @db.get(@docid)
- @doc.copy @to_be_overwritten
- newdoc = @db.get(@docid)
- newdoc['key'].should == 'value'
- end
- end
-end
-describe "MOVE existing document" do
- before :each do
- @db = reset_test_db!
- @resp = @db.save_doc({'key' => 'value'})
- @docid = 'new-location'
- @doc = @db.get(@resp['id'])
- end
- describe "to a new location" do
- it "should work" do
- @doc.move @docid
- newdoc = @db.get(@docid)
- newdoc['key'].should == 'value'
- lambda {@db.get(@resp['id'])}.should raise_error(RestClient::ResourceNotFound)
+ describe "default database" do
+ it "should be set using use_database on the model" do
+ Video.new.database.should be_nil
+ Video.use_database @db
+ Video.new.database.should == @db
+ Video.use_database nil
end
- it "should fail without a database" do
- lambda{CouchRest::Document.new({"not"=>"a real doc"}).move}.should raise_error(ArgumentError)
- lambda{CouchRest::Document.new({"_id"=>"not a real doc"}).move}.should raise_error(ArgumentError)
+
+ it "should be overwritten by instance" do
+ db = @couch.database('test')
+ article = Video.new
+ article.database.should be_nil
+ article.database = db
+ article.database.should_not be_nil
+ article.database.should == db
end
end
- describe "to an existing location" do
+
+ describe "new" do
+ before(:each) do
+ @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
+ end
+ it "should create itself from a Hash" do
+ @doc["key"].should == [1,2,3]
+ @doc["more"].should == "values"
+ end
+ it "should not have rev and id" do
+ @doc.rev.should be_nil
+ @doc.id.should be_nil
+ end
+ it "should freak out when saving without a database" do
+ lambda{@doc.save}.should raise_error(ArgumentError)
+ end
+ end
+
+ # move to database spec
+ describe "saving using a database" do
+ before(:all) do
+ @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
+ @db = reset_test_db!
+ @resp = @db.save_doc(@doc)
+ end
+ it "should apply the database" do
+ @doc.database.should == @db
+ end
+ it "should get id and rev" do
+ @doc.id.should == @resp["id"]
+ @doc.rev.should == @resp["rev"]
+ end
+ end
+
+ describe "bulk saving" do
+ before :all do
+ @db = reset_test_db!
+ end
+
+ it "should use the document bulk save cache" do
+ doc = CouchRest::Document.new({"_id" => "bulkdoc", "val" => 3})
+ doc.database = @db
+ doc.save(true)
+ lambda { doc.database.get(doc["_id"]) }.should raise_error(RestClient::ResourceNotFound)
+ doc.database.bulk_save
+ doc.database.get(doc["_id"])["val"].should == doc["val"]
+ end
+ end
+
+ describe "getting from a database" do
+ before(:all) do
+ @db = reset_test_db!
+ @resp = @db.save_doc({
+ "key" => "value"
+ })
+ @doc = @db.get @resp['id']
+ end
+ it "should return a document" do
+ @doc.should be_an_instance_of(CouchRest::Document)
+ end
+ it "should have a database" do
+ @doc.database.should == @db
+ end
+ it "should be saveable and resavable" do
+ @doc["more"] = "keys"
+ @doc.save
+ @db.get(@resp['id'])["more"].should == "keys"
+ @doc["more"] = "these keys"
+ @doc.save
+ @db.get(@resp['id'])["more"].should == "these keys"
+ end
+ end
+
+ describe "destroying a document from a db" do
+ before(:all) do
+ @db = reset_test_db!
+ @resp = @db.save_doc({
+ "key" => "value"
+ })
+ @doc = @db.get @resp['id']
+ end
+ it "should make it disappear" do
+ @doc.destroy
+ lambda{@db.get @resp['id']}.should raise_error
+ end
+ it "should error when there's no db" do
+ @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
+ lambda{@doc.destroy}.should raise_error(ArgumentError)
+ end
+ end
+
+
+ describe "destroying a document from a db using bulk save" do
+ before(:all) do
+ @db = reset_test_db!
+ @resp = @db.save_doc({
+ "key" => "value"
+ })
+ @doc = @db.get @resp['id']
+ end
+ it "should defer actual deletion" do
+ @doc.destroy(true)
+ @doc['_id'].should == nil
+ @doc['_rev'].should == nil
+ lambda{@db.get @resp['id']}.should_not raise_error
+ @db.bulk_save
+ lambda{@db.get @resp['id']}.should raise_error
+ end
+ end
+
+ describe "copying a document" do
before :each do
- @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
+ @db = reset_test_db!
+ @resp = @db.save_doc({'key' => 'value'})
+ @docid = 'new-location'
+ @doc = @db.get(@resp['id'])
end
- it "should fail without a rev" do
- lambda{@doc.move @docid}.should raise_error(RestClient::RequestFailed)
- lambda{@db.get(@resp['id'])}.should_not raise_error
+ describe "to a new location" do
+ it "should work" do
+ @doc.copy @docid
+ newdoc = @db.get(@docid)
+ newdoc['key'].should == 'value'
+ end
+ it "should fail without a database" do
+ lambda{CouchRest::Document.new({"not"=>"a real doc"}).copy}.should raise_error(ArgumentError)
+ end
end
- it "should succeed with a rev" do
- @to_be_overwritten = @db.get(@docid)
- @doc.move "#{@docid}?rev=#{@to_be_overwritten['_rev']}"
- newdoc = @db.get(@docid)
- newdoc['key'].should == 'value'
- lambda {@db.get(@resp['id'])}.should raise_error(RestClient::ResourceNotFound)
+ describe "to an existing location" do
+ before :each do
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
+ end
+ it "should fail without a rev" do
+ lambda{@doc.copy @docid}.should raise_error(RestClient::RequestFailed)
+ end
+ it "should succeed with a rev" do
+ @to_be_overwritten = @db.get(@docid)
+ @doc.copy "#{@docid}?rev=#{@to_be_overwritten['_rev']}"
+ newdoc = @db.get(@docid)
+ newdoc['key'].should == 'value'
+ end
+ it "should succeed given the doc to overwrite" do
+ @to_be_overwritten = @db.get(@docid)
+ @doc.copy @to_be_overwritten
+ newdoc = @db.get(@docid)
+ newdoc['key'].should == 'value'
+ end
end
- it "should succeed given the doc to overwrite" do
- @to_be_overwritten = @db.get(@docid)
- @doc.move @to_be_overwritten
- newdoc = @db.get(@docid)
- newdoc['key'].should == 'value'
- lambda {@db.get(@resp['id'])}.should raise_error(RestClient::ResourceNotFound)
+ end
+
+ describe "MOVE existing document" do
+ before :each do
+ @db = reset_test_db!
+ @resp = @db.save_doc({'key' => 'value'})
+ @docid = 'new-location'
+ @doc = @db.get(@resp['id'])
+ end
+ describe "to a new location" do
+ it "should work" do
+ @doc.move @docid
+ newdoc = @db.get(@docid)
+ newdoc['key'].should == 'value'
+ lambda {@db.get(@resp['id'])}.should raise_error(RestClient::ResourceNotFound)
+ end
+ it "should fail without a database" do
+ lambda{CouchRest::Document.new({"not"=>"a real doc"}).move}.should raise_error(ArgumentError)
+ lambda{CouchRest::Document.new({"_id"=>"not a real doc"}).move}.should raise_error(ArgumentError)
+ end
+ end
+ describe "to an existing location" do
+ before :each do
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
+ end
+ it "should fail without a rev" do
+ lambda{@doc.move @docid}.should raise_error(RestClient::RequestFailed)
+ lambda{@db.get(@resp['id'])}.should_not raise_error
+ end
+ it "should succeed with a rev" do
+ @to_be_overwritten = @db.get(@docid)
+ @doc.move "#{@docid}?rev=#{@to_be_overwritten['_rev']}"
+ newdoc = @db.get(@docid)
+ newdoc['key'].should == 'value'
+ lambda {@db.get(@resp['id'])}.should raise_error(RestClient::ResourceNotFound)
+ end
+ it "should succeed given the doc to overwrite" do
+ @to_be_overwritten = @db.get(@docid)
+ @doc.move @to_be_overwritten
+ newdoc = @db.get(@docid)
+ newdoc['key'].should == 'value'
+ lambda {@db.get(@resp['id'])}.should raise_error(RestClient::ResourceNotFound)
+ end
end
end
end
diff --git a/spec/couchrest/core/server_spec.rb b/spec/couchrest/core/server_spec.rb
new file mode 100644
index 0000000..8a23e10
--- /dev/null
+++ b/spec/couchrest/core/server_spec.rb
@@ -0,0 +1,34 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+describe CouchRest::Server do
+
+ before(:all) do
+ @couch = CouchRest::Server.new
+ end
+
+ after(:all) do
+ @couch.available_databases.each do |ref, db|
+ db.delete!
+ end
+ end
+
+ describe "available databases" do
+ it "should let you add more databases" do
+ @couch.available_databases.should be_empty
+ @couch.define_available_database(:default, "cr-server-test-db")
+ @couch.available_databases.keys.should include(:default)
+ end
+
+ it "should verify that a database is available" do
+ @couch.available_database?(:default).should be_true
+ @couch.available_database?("cr-server-test-db").should be_true
+ @couch.available_database?(:matt).should be_false
+ end
+
+ it "should let you set a default database" do
+ @couch.default_database = 'cr-server-test-default-db'
+ @couch.available_database?(:default).should be_true
+ end
+ end
+
+end
\ No newline at end of file