Merge branch 'master' of git://github.com/thewordnerd/couchrest
* 'master' of git://github.com/thewordnerd/couchrest: Removed model create/update callbacks and integrated with new bulk save infrastructure. Make bulk saving more flexible. Add support for database compaction.
This commit is contained in:
commit
efae804920
6 changed files with 122 additions and 41 deletions
|
@ -4,6 +4,7 @@ require "base64"
|
||||||
module CouchRest
|
module CouchRest
|
||||||
class Database
|
class Database
|
||||||
attr_reader :server, :host, :name, :root
|
attr_reader :server, :host, :name, :root
|
||||||
|
attr_accessor :bulk_save_cache_limit
|
||||||
|
|
||||||
# Create a CouchRest::Database adapter for the supplied CouchRest::Server
|
# Create a CouchRest::Database adapter for the supplied CouchRest::Server
|
||||||
# and database name.
|
# and database name.
|
||||||
|
@ -18,6 +19,8 @@ module CouchRest
|
||||||
@host = server.uri
|
@host = server.uri
|
||||||
@root = "#{host}/#{name}"
|
@root = "#{host}/#{name}"
|
||||||
@streamer = Streamer.new(self)
|
@streamer = Streamer.new(self)
|
||||||
|
@bulk_save_cache = []
|
||||||
|
@bulk_save_cache_limit = 50
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns the database's uri
|
# returns the database's uri
|
||||||
|
@ -106,10 +109,20 @@ module CouchRest
|
||||||
# documents on the client side because POST has the curious property of
|
# documents on the client side because POST has the curious property of
|
||||||
# being automatically retried by proxies in the event of network
|
# being automatically retried by proxies in the event of network
|
||||||
# segmentation and lost responses.
|
# segmentation and lost responses.
|
||||||
def save doc
|
#
|
||||||
|
# If <tt>bulk</tt> is true (false by default) the document is cached for bulk-saving later.
|
||||||
|
# Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
|
||||||
|
def save (doc, bulk = false)
|
||||||
if doc['_attachments']
|
if doc['_attachments']
|
||||||
doc['_attachments'] = encode_attachments(doc['_attachments'])
|
doc['_attachments'] = encode_attachments(doc['_attachments'])
|
||||||
end
|
end
|
||||||
|
if bulk
|
||||||
|
@bulk_save_cache << doc
|
||||||
|
return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
|
||||||
|
return {"ok" => true} # Compatibility with Document#save
|
||||||
|
elsif !bulk && @bulk_save_cache.length > 0
|
||||||
|
bulk_save
|
||||||
|
end
|
||||||
result = if doc['_id']
|
result = if doc['_id']
|
||||||
slug = CGI.escape(doc['_id'])
|
slug = CGI.escape(doc['_id'])
|
||||||
CouchRest.put "#{@root}/#{slug}", doc
|
CouchRest.put "#{@root}/#{slug}", doc
|
||||||
|
@ -131,7 +144,13 @@ module CouchRest
|
||||||
|
|
||||||
# POST an array of documents to CouchDB. If any of the documents are
|
# POST an array of documents to CouchDB. If any of the documents are
|
||||||
# missing ids, supply one from the uuid cache.
|
# missing ids, supply one from the uuid cache.
|
||||||
def bulk_save docs
|
#
|
||||||
|
# If called with no arguments, bulk saves the cache of documents to be bulk saved.
|
||||||
|
def bulk_save (docs = nil)
|
||||||
|
if docs.nil?
|
||||||
|
docs = @bulk_save_cache
|
||||||
|
@bulk_save_cache = []
|
||||||
|
end
|
||||||
ids, noids = docs.partition{|d|d['_id']}
|
ids, noids = docs.partition{|d|d['_id']}
|
||||||
uuid_count = [noids.length, @server.uuid_batch_count].max
|
uuid_count = [noids.length, @server.uuid_batch_count].max
|
||||||
noids.each do |doc|
|
noids.each do |doc|
|
||||||
|
@ -149,7 +168,12 @@ module CouchRest
|
||||||
slug = CGI.escape(doc['_id'])
|
slug = CGI.escape(doc['_id'])
|
||||||
CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}"
|
CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Compact the database, removing old document revisions and optimizing space use.
|
||||||
|
def compact!
|
||||||
|
CouchRest.post "#{@root}/_compact"
|
||||||
|
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
|
||||||
# catastrophic. Use with care!
|
# catastrophic. Use with care!
|
||||||
def delete!
|
def delete!
|
||||||
|
|
|
@ -35,9 +35,10 @@ module CouchRest
|
||||||
# Saves the document to the db using create or update. Also runs the :save
|
# Saves the document to the db using create or update. Also runs the :save
|
||||||
# callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
|
# callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
|
||||||
# CouchDB's response.
|
# CouchDB's response.
|
||||||
def save
|
# If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document is cached for bulk save.
|
||||||
|
def save(bulk = false)
|
||||||
raise ArgumentError, "doc.database required for saving" unless database
|
raise ArgumentError, "doc.database required for saving" unless database
|
||||||
result = database.save self
|
result = database.save self, bulk
|
||||||
result['ok']
|
result['ok']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -57,4 +58,4 @@ module CouchRest
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -215,8 +215,9 @@ module CouchRest
|
||||||
# on the document whenever saving occurs. CouchRest uses a pretty
|
# on the document whenever saving occurs. CouchRest uses a pretty
|
||||||
# decent time format by default. See Time#to_json
|
# decent time format by default. See Time#to_json
|
||||||
def timestamps!
|
def timestamps!
|
||||||
before(:create) do
|
before(:save) do
|
||||||
self['updated_at'] = self['created_at'] = Time.now
|
self['updated_at'] = Time.now
|
||||||
|
self['created_at'] = self['updated_at'] if new_document?
|
||||||
end
|
end
|
||||||
before(:update) do
|
before(:update) do
|
||||||
self['updated_at'] = Time.now
|
self['updated_at'] = Time.now
|
||||||
|
@ -478,19 +479,10 @@ module CouchRest
|
||||||
# for compatibility with old-school frameworks
|
# for compatibility with old-school frameworks
|
||||||
alias :new_record? :new_document?
|
alias :new_record? :new_document?
|
||||||
|
|
||||||
# We override this to create the create and update callback opportunities.
|
# Overridden to set the unique ID.
|
||||||
# I think we should drop those and just have save. If you care, in your callback,
|
def save bulk = false
|
||||||
# check new_document?
|
set_unique_id if self.respond_to?(:set_unique_id)
|
||||||
def save actually=false
|
super(bulk)
|
||||||
if actually
|
|
||||||
super()
|
|
||||||
else
|
|
||||||
if new_document?
|
|
||||||
create
|
|
||||||
else
|
|
||||||
update
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Saves the document to the db using create or update. Raises an exception
|
# Saves the document to the db using create or update. Raises an exception
|
||||||
|
@ -498,10 +490,6 @@ module CouchRest
|
||||||
def save!
|
def save!
|
||||||
raise "#{self.inspect} failed to save" unless self.save
|
raise "#{self.inspect} failed to save" unless self.save
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
|
||||||
save :actually
|
|
||||||
end
|
|
||||||
|
|
||||||
# Deletes the document from the database. Runs the :delete callbacks.
|
# Deletes the document from the database. Runs the :delete callbacks.
|
||||||
# Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
|
# Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
|
||||||
|
@ -515,12 +503,6 @@ module CouchRest
|
||||||
result['ok']
|
result['ok']
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
|
||||||
# can we use the callbacks for this?
|
|
||||||
set_unique_id if self.respond_to?(:set_unique_id)
|
|
||||||
save :actually
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def apply_defaults
|
def apply_defaults
|
||||||
|
@ -553,9 +535,7 @@ module CouchRest
|
||||||
end
|
end
|
||||||
|
|
||||||
include ::Extlib::Hook
|
include ::Extlib::Hook
|
||||||
# todo: drop create and update hooks...
|
register_instance_hooks :save, :destroy
|
||||||
# (use new_record? in callbacks if you care)
|
|
||||||
register_instance_hooks :save, :create, :update, :destroy
|
|
||||||
|
|
||||||
end # class Model
|
end # class Model
|
||||||
end # module CouchRest
|
end # module CouchRest
|
||||||
|
|
|
@ -190,6 +190,16 @@ describe CouchRest::Database do
|
||||||
@db.get('twoB')
|
@db.get('twoB')
|
||||||
end.should raise_error(RestClient::ResourceNotFound)
|
end.should raise_error(RestClient::ResourceNotFound)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should empty the bulk save cache if no documents are given" do
|
||||||
|
@db.save({"_id" => "bulk_cache_1", "val" => "test"}, true)
|
||||||
|
lambda do
|
||||||
|
@db.get('bulk_cache_1')
|
||||||
|
end.should raise_error(RestClient::ResourceNotFound)
|
||||||
|
@db.bulk_save
|
||||||
|
@db.get("bulk_cache_1")["val"].should == "test"
|
||||||
|
end
|
||||||
|
|
||||||
it "should raise an error that is useful for recovery" do
|
it "should raise an error that is useful for recovery" do
|
||||||
@r = @db.save({"_id" => "taken", "field" => "stuff"})
|
@r = @db.save({"_id" => "taken", "field" => "stuff"})
|
||||||
begin
|
begin
|
||||||
|
@ -400,7 +410,48 @@ describe CouchRest::Database do
|
||||||
now['them-keys'].should == 'huge'
|
now['them-keys'].should == 'huge'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "cached bulk save" do
|
||||||
|
it "stores documents in a database-specific cache" do
|
||||||
|
td = {"_id" => "btd1", "val" => "test"}
|
||||||
|
@db.save(td, true)
|
||||||
|
@db.instance_variable_get("@bulk_save_cache").should == [td]
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't save to the database until the configured cache size is exceded" do
|
||||||
|
@db.bulk_save_cache_limit = 3
|
||||||
|
td1 = {"_id" => "td1", "val" => true}
|
||||||
|
td2 = {"_id" => "td2", "val" => 4}
|
||||||
|
@db.save(td1, true)
|
||||||
|
@db.save(td2, true)
|
||||||
|
lambda do
|
||||||
|
@db.get(td1["_id"])
|
||||||
|
end.should raise_error(RestClient::ResourceNotFound)
|
||||||
|
lambda do
|
||||||
|
@db.get(td2["_id"])
|
||||||
|
end.should raise_error(RestClient::ResourceNotFound)
|
||||||
|
td3 = {"_id" => "td3", "val" => "foo"}
|
||||||
|
@db.save(td3, true)
|
||||||
|
@db.get(td1["_id"])["val"].should == td1["val"]
|
||||||
|
@db.get(td2["_id"])["val"].should == td2["val"]
|
||||||
|
@db.get(td3["_id"])["val"].should == td3["val"]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "clears the bulk save cache the first time a non bulk save is requested" do
|
||||||
|
td1 = {"_id" => "blah", "val" => true}
|
||||||
|
td2 = {"_id" => "steve", "val" => 3}
|
||||||
|
@db.bulk_save_cache_limit = 50
|
||||||
|
@db.save(td1, true)
|
||||||
|
lambda do
|
||||||
|
@db.get(td1["_id"])
|
||||||
|
end.should raise_error(RestClient::ResourceNotFound)
|
||||||
|
@db.save(td2)
|
||||||
|
@db.get(td1["_id"])["val"].should == td1["val"]
|
||||||
|
@db.get(td2["_id"])["val"].should == td2["val"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "DELETE existing document" do
|
describe "DELETE existing document" do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
@r = @db.save({'lemons' => 'from texas', 'and' => 'spain'})
|
@r = @db.save({'lemons' => 'from texas', 'and' => 'spain'})
|
||||||
|
@ -461,6 +512,16 @@ describe CouchRest::Database do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
describe "compacting a database" do
|
||||||
|
it "should compact the database" do
|
||||||
|
db = @cr.database('couchrest-test')
|
||||||
|
# r =
|
||||||
|
db.compact!
|
||||||
|
# r['ok'].should == true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "deleting a database" do
|
describe "deleting a database" do
|
||||||
it "should start with the test database" do
|
it "should start with the test database" do
|
||||||
@cr.databases.should include('couchrest-test')
|
@cr.databases.should include('couchrest-test')
|
||||||
|
@ -475,4 +536,4 @@ describe CouchRest::Database do
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -53,6 +53,21 @@ describe CouchRest::Document, "saving using a database" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe CouchRest::Document, "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
|
describe "getting from a database" do
|
||||||
before(:all) do
|
before(:all) do
|
||||||
@db = reset_test_db!
|
@db = reset_test_db!
|
||||||
|
@ -93,4 +108,4 @@ describe "destroying a document from a db" do
|
||||||
@doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
|
@doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
|
||||||
lambda{@doc.destroy}.should raise_error(ArgumentError)
|
lambda{@doc.destroy}.should raise_error(ArgumentError)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -72,9 +72,9 @@ class Article < CouchRest::Model
|
||||||
|
|
||||||
timestamps!
|
timestamps!
|
||||||
|
|
||||||
before(:create, :generate_slug_from_title)
|
before(:save, :generate_slug_from_title)
|
||||||
def generate_slug_from_title
|
def generate_slug_from_title
|
||||||
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'')
|
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new_document?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -684,4 +684,4 @@ describe CouchRest::Model do
|
||||||
lambda{Basic.get(@obj.id)}.should raise_error
|
lambda{Basic.get(@obj.id)}.should raise_error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue