hotseat 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +35 -0
- data/VERSION +1 -0
- data/hotseat.gemspec +63 -0
- data/lib/hotseat/hotseat.rb +89 -0
- data/lib/hotseat/queue.rb +157 -0
- data/lib/hotseat.rb +2 -0
- data/spec/hotseat/hotseat_spec.rb +50 -0
- data/spec/hotseat/queue_spec.rb +394 -0
- data/spec/spec_helper.rb +43 -0
- metadata +119 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Elad Kehat
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= Hotseat
|
2
|
+
|
3
|
+
Add work queue functionality to an existing CouchDB database.
|
4
|
+
|
5
|
+
== Contributing to Hotseat
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
+
* Fork the project
|
10
|
+
* Start a feature/bugfix branch
|
11
|
+
* Commit and push until you are happy with your contribution
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2011 Elad Kehat. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
gem.name = "hotseat"
|
17
|
+
gem.homepage = "http://github.com/eladkehat/hotseat"
|
18
|
+
gem.license = "MIT"
|
19
|
+
gem.summary = %Q{Add work queue functionality to an existing CouchDB database}
|
20
|
+
#gem.description = %Q{longer description of the gem}
|
21
|
+
gem.email = "eladkehat@gmail.com"
|
22
|
+
gem.authors = ["Elad Kehat"]
|
23
|
+
end
|
24
|
+
Jeweler::RubygemsDotOrgTasks.new
|
25
|
+
|
26
|
+
require 'rspec/core'
|
27
|
+
require 'rspec/core/rake_task'
|
28
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
29
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
30
|
+
end
|
31
|
+
|
32
|
+
task :default => :spec
|
33
|
+
|
34
|
+
require 'yard'
|
35
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
data/hotseat.gemspec
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{hotseat}
|
8
|
+
s.version = "0.1.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Elad Kehat"]
|
12
|
+
s.date = %q{2011-08-06}
|
13
|
+
s.email = %q{eladkehat@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE.txt",
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".document",
|
20
|
+
".rspec",
|
21
|
+
"Gemfile",
|
22
|
+
"LICENSE.txt",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/hotseat.rb",
|
27
|
+
"lib/hotseat/hotseat.rb",
|
28
|
+
"lib/hotseat/queue.rb",
|
29
|
+
"spec/hotseat/hotseat_spec.rb",
|
30
|
+
"spec/hotseat/queue_spec.rb",
|
31
|
+
"spec/spec_helper.rb"
|
32
|
+
]
|
33
|
+
s.homepage = %q{http://github.com/eladkehat/hotseat}
|
34
|
+
s.licenses = ["MIT"]
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
s.rubygems_version = %q{1.7.2}
|
37
|
+
s.summary = %q{Add work queue functionality to an existing CouchDB database}
|
38
|
+
|
39
|
+
if s.respond_to? :specification_version then
|
40
|
+
s.specification_version = 3
|
41
|
+
|
42
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
43
|
+
s.add_runtime_dependency(%q<couchrest>, [">= 1.1.0"])
|
44
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
45
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
46
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
47
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
48
|
+
else
|
49
|
+
s.add_dependency(%q<couchrest>, [">= 1.1.0"])
|
50
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
51
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
52
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
53
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
54
|
+
end
|
55
|
+
else
|
56
|
+
s.add_dependency(%q<couchrest>, [">= 1.1.0"])
|
57
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
58
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
59
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
60
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Hotseat
|
2
|
+
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def queue(db)
|
6
|
+
Hotseat::Queue.new(db)
|
7
|
+
end
|
8
|
+
alias :make_queue :queue
|
9
|
+
|
10
|
+
def queue?(db)
|
11
|
+
# ignore system dbs like _replicator and _users
|
12
|
+
return false if db.name =~ /^_/
|
13
|
+
begin
|
14
|
+
db.get design_doc_id
|
15
|
+
rescue RestClient::ResourceNotFound
|
16
|
+
# either the database or the design doc does not exist
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def queues(couch_server)
|
22
|
+
couch_server.databases.select do |db|
|
23
|
+
queue?(couch_server.database(db))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def design_doc_id
|
28
|
+
"_design/#{config[:design_doc_name]}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def pending_view_name
|
32
|
+
"#{config[:design_doc_name]}/#{config[:pending_view_name]}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def locked_view_name
|
36
|
+
"#{config[:design_doc_name]}/#{config[:locked_view_name]}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def done_view_name
|
40
|
+
"#{config[:design_doc_name]}/#{config[:done_view_name]}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def all_view_name
|
44
|
+
"#{config[:design_doc_name]}/#{config[:all_view_name]}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def design_doc
|
48
|
+
q = "doc.#{config[:object_name]}"
|
49
|
+
lock = "#{q}.lock"
|
50
|
+
done = "#{q}.done"
|
51
|
+
pending_func = <<-JAVASCRIPT
|
52
|
+
function(doc) { if (#{q} && !(#{lock} || #{done})) emit(#{q}.at, null); }
|
53
|
+
JAVASCRIPT
|
54
|
+
locked_func = <<-JAVASCRIPT
|
55
|
+
function(doc) { if (#{q} && #{lock}) emit(#{lock}.at, null); }
|
56
|
+
JAVASCRIPT
|
57
|
+
done_func = <<-JAVASCRIPT
|
58
|
+
function(doc) { if (#{q} && #{done}) emit(#{done}.at, null); }
|
59
|
+
JAVASCRIPT
|
60
|
+
all_func = <<-JAVASCRIPT
|
61
|
+
function(doc) { if (#{q}) emit(#{q}.at, null); }
|
62
|
+
JAVASCRIPT
|
63
|
+
{
|
64
|
+
'_id' => "_design/#{config[:design_doc_name]}",
|
65
|
+
:views => {
|
66
|
+
config[:pending_view_name] => { :map => pending_func.chomp },
|
67
|
+
config[:locked_view_name] => { :map => locked_func.chomp },
|
68
|
+
config[:done_view_name] => { :map => done_func.chomp },
|
69
|
+
config[:all_view_name] => { :map => all_func.chomp },
|
70
|
+
}
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def config
|
75
|
+
CONFIG
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
CONFIG = {
|
81
|
+
:design_doc_name => 'hotseat_queue',
|
82
|
+
:pending_view_name => 'pending',
|
83
|
+
:locked_view_name => 'locked',
|
84
|
+
:done_view_name => 'done',
|
85
|
+
:all_view_name => 'all',
|
86
|
+
:object_name => 'hotseat',
|
87
|
+
}
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Hotseat
|
4
|
+
|
5
|
+
class QueueError < RuntimeError
|
6
|
+
end
|
7
|
+
|
8
|
+
class Queue
|
9
|
+
attr_reader :db
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def patch(doc)
|
14
|
+
doc[Hotseat.config[:object_name]] = {'at' => Time.now.utc.iso8601, 'by' => $$}
|
15
|
+
doc
|
16
|
+
end
|
17
|
+
|
18
|
+
def unpatch(doc)
|
19
|
+
doc.delete( Hotseat.config[:object_name] )
|
20
|
+
doc
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_lock(doc)
|
24
|
+
obj = doc[Hotseat.config[:object_name]]
|
25
|
+
obj['lock'] = {'at' => Time.now.utc.iso8601, 'by' => $$}
|
26
|
+
doc
|
27
|
+
end
|
28
|
+
|
29
|
+
def locked?(doc)
|
30
|
+
if obj = doc[Hotseat.config[:object_name]]
|
31
|
+
obj.has_key? 'lock'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove_lock(doc)
|
36
|
+
obj = doc[Hotseat.config[:object_name]]
|
37
|
+
obj.delete 'lock'
|
38
|
+
doc
|
39
|
+
end
|
40
|
+
|
41
|
+
def mark_done(doc)
|
42
|
+
obj = doc[Hotseat.config[:object_name]]
|
43
|
+
obj['done'] = {'at' => Time.now.utc.iso8601, 'by' => $$}
|
44
|
+
doc
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(db)
|
50
|
+
@db = db
|
51
|
+
unless Hotseat.queue?(@db)
|
52
|
+
@db.save_doc Hotseat.design_doc
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def add(doc_id)
|
57
|
+
@db.update_doc(doc_id) {|doc| Queue.patch doc }
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_bulk(doc_ids)
|
61
|
+
#Note: this silently ignores missing doc_ids
|
62
|
+
docs = @db.bulk_load(doc_ids)['rows'].map{|row| row['doc']}.compact
|
63
|
+
docs.each {|doc| Queue.patch doc }
|
64
|
+
@db.bulk_save docs, use_uuids=false
|
65
|
+
end
|
66
|
+
|
67
|
+
def num_pending
|
68
|
+
@db.view(Hotseat.pending_view_name, :limit => 0)['total_rows']
|
69
|
+
end
|
70
|
+
alias :size :num_pending
|
71
|
+
|
72
|
+
def get(n=1)
|
73
|
+
rows = @db.view(Hotseat.pending_view_name, :limit => n, :include_docs => true)['rows']
|
74
|
+
rows.map{|row| row['doc']} unless rows.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
def lease(n=1)
|
78
|
+
if docs = get(n)
|
79
|
+
docs.each {|doc| Queue.add_lock doc }
|
80
|
+
response = @db.bulk_save docs, use_uuids=false
|
81
|
+
# Some docs may have failed to lock - probably updated by another process
|
82
|
+
locked_ids = response.reject{|res| res['error']}.map{|res| res['id']}
|
83
|
+
if locked_ids.length < docs.length
|
84
|
+
# This runs in O(n^2) time. Performance will be bad here if the number of documents
|
85
|
+
# is very large. Assuming that this isn't normally the case I'm keeping it simple.
|
86
|
+
docs.keep_if{|doc| locked_ids.include? doc['_id']}
|
87
|
+
end
|
88
|
+
docs
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def num_locked
|
93
|
+
@db.view(Hotseat.locked_view_name, :limit => 0)['total_rows']
|
94
|
+
end
|
95
|
+
|
96
|
+
def remove(doc_id, opts={})
|
97
|
+
@db.update_doc(doc_id) do |doc|
|
98
|
+
raise(QueueError, "Document was already removed") unless Queue.locked?(doc)
|
99
|
+
if opts.delete(:forget)
|
100
|
+
Queue.unpatch doc
|
101
|
+
else
|
102
|
+
Queue.mark_done( Queue.remove_lock( doc ) )
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def remove_bulk(doc_ids, opts={})
|
108
|
+
rows = @db.bulk_load(doc_ids)['rows']
|
109
|
+
docs, missing = rows.partition {|row| row['doc'] }
|
110
|
+
docs.map! {|row| row['doc'] }
|
111
|
+
locked, unlocked = docs.partition {|doc| Queue.locked? doc }
|
112
|
+
forget = opts.delete(:forget)
|
113
|
+
locked.each do |doc|
|
114
|
+
if forget
|
115
|
+
Queue.unpatch doc
|
116
|
+
else
|
117
|
+
Queue.mark_done( Queue.remove_lock( doc ) )
|
118
|
+
end
|
119
|
+
end
|
120
|
+
@db.bulk_save locked, use_uuids=false
|
121
|
+
{'errors' =>
|
122
|
+
unlocked.map {|doc| {'id' => doc['_id'], 'error' => 'unlocked' } } +
|
123
|
+
missing.map {|row| {'id' => row['key'], 'error' => row['error']} }
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
def num_done
|
128
|
+
@db.view(Hotseat.done_view_name, :limit => 0)['total_rows']
|
129
|
+
end
|
130
|
+
|
131
|
+
def num_all
|
132
|
+
@db.view(Hotseat.all_view_name, :limit => 0)['total_rows']
|
133
|
+
end
|
134
|
+
alias :num_total :num_all
|
135
|
+
|
136
|
+
def forget(doc_id)
|
137
|
+
@db.update_doc(doc_id) do |doc|
|
138
|
+
Queue.unpatch doc
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def forget_bulk(doc_ids)
|
143
|
+
#Note: this silently ignores missing doc_ids
|
144
|
+
docs = @db.bulk_load(doc_ids)['rows'].map{|row| row['doc']}.compact
|
145
|
+
docs.each {|doc| Queue.unpatch doc }
|
146
|
+
@db.bulk_save docs, use_uuids=false
|
147
|
+
end
|
148
|
+
|
149
|
+
def purge
|
150
|
+
rows = @db.view(Hotseat.all_view_name, :include_docs => true)['rows']
|
151
|
+
docs = rows.map{|row| row['doc']}
|
152
|
+
docs.each{|doc| Queue.unpatch doc }
|
153
|
+
@db.bulk_save docs, use_uuids=false
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
data/lib/hotseat.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
module Hotseat
|
4
|
+
|
5
|
+
describe "#queue" do
|
6
|
+
it "should return a queue on the given database" do
|
7
|
+
reset_test_db!
|
8
|
+
q = Hotseat.queue DB
|
9
|
+
q.should be_instance_of Hotseat::Queue
|
10
|
+
Hotseat.queue?(DB).should be_true
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#queue?" do
|
16
|
+
it "should be true when a given database is a queue" do
|
17
|
+
reset_test_db!
|
18
|
+
Hotseat.queue DB
|
19
|
+
Hotseat.queue?(DB).should be_true
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be false when a given database is not a queue" do
|
23
|
+
reset_test_db!
|
24
|
+
Hotseat.queue?(DB).should be_false
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should be false when a given database does not exist" do
|
28
|
+
delete_test_db!
|
29
|
+
Hotseat.queue?(DB).should be_false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#queues" do
|
34
|
+
# not making sure that non-queues aren't returned since the host we test on
|
35
|
+
# may be have some non-testing (production) queues on it which we don't know about
|
36
|
+
it "should return a list of queues (database names) on the host" do
|
37
|
+
delete_test_db!
|
38
|
+
test_dbs = (1..3).map{|i| TEST_SERVER.database("#{TESTDB}#{i}") }
|
39
|
+
begin
|
40
|
+
test_dbs.each do |db|
|
41
|
+
db.create!
|
42
|
+
Hotseat.queue(db)
|
43
|
+
end
|
44
|
+
results = Hotseat.queues TEST_SERVER
|
45
|
+
test_dbs.each {|db| results.should include URI.unescape(db.name) }
|
46
|
+
ensure test_dbs.each{|db| db.delete! rescue nil } end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,394 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
module Hotseat
|
4
|
+
describe Queue do
|
5
|
+
|
6
|
+
########################
|
7
|
+
### Helper methods ###
|
8
|
+
########################
|
9
|
+
def reset_test_queue!
|
10
|
+
reset_test_db!
|
11
|
+
@q = Hotseat.make_queue DB
|
12
|
+
end
|
13
|
+
|
14
|
+
def sample_doc(var=nil)
|
15
|
+
{:field => var || 'value' }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the new doc id's
|
19
|
+
def create_some_docs(n=3)
|
20
|
+
(1..n).map {|i| DB.save_doc(sample_doc(i))['id'] }
|
21
|
+
end
|
22
|
+
|
23
|
+
def enqueue(doc_ids)
|
24
|
+
@q.add_bulk doc_ids
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#initialize" do
|
28
|
+
it "should create a Hotseat design doc in the database if one does not exist" do
|
29
|
+
reset_test_db!
|
30
|
+
q = Hotseat::Queue.new(DB)
|
31
|
+
q.db.get(Hotseat.design_doc_id).should_not be_nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#patch" do
|
36
|
+
it "should add a queue object on a document" do
|
37
|
+
doc = sample_doc
|
38
|
+
Queue.patch doc
|
39
|
+
doc.should have_key(Hotseat.config[:object_name])
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return the patched document with original data intact" do
|
43
|
+
doc = Queue.patch sample_doc
|
44
|
+
sample_doc.each do |k,v|
|
45
|
+
doc[k].should == v
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#unpatch" do
|
51
|
+
it "should remove the queue object from a document" do
|
52
|
+
doc = Queue.unpatch( Queue.patch( sample_doc ) )
|
53
|
+
doc.should_not have_key(Hotseat.config[:object_name])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#add_lock" do
|
58
|
+
it "should add a lock object on a patched document" do
|
59
|
+
doc = Queue.add_lock( Queue.patch( sample_doc ) )
|
60
|
+
patch = doc[Hotseat.config[:object_name]]
|
61
|
+
patch.should have_key('lock')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#remove_lock" do
|
66
|
+
before(:each) { @doc = Queue.remove_lock( Queue.add_lock( Queue.patch( sample_doc ) ) ) }
|
67
|
+
it "should remove a lock object added by #add_lock" do
|
68
|
+
patch = @doc[Hotseat.config[:object_name]]
|
69
|
+
patch.should_not have_key('lock')
|
70
|
+
end
|
71
|
+
it "should leave the queue patch intact" do
|
72
|
+
@doc.should have_key(Hotseat.config[:object_name])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#locked?" do
|
77
|
+
it "should be true for a locked document" do
|
78
|
+
doc = Queue.add_lock( Queue.patch( sample_doc ) )
|
79
|
+
Queue.locked?(doc).should be_true
|
80
|
+
end
|
81
|
+
it "should be false for a patched, but not locked document" do
|
82
|
+
doc = Queue.patch( sample_doc )
|
83
|
+
Queue.locked?(doc).should be_false
|
84
|
+
end
|
85
|
+
it "should be false for an unlocked document" do
|
86
|
+
doc = Queue.remove_lock( Queue.add_lock( Queue.patch( sample_doc ) ) )
|
87
|
+
Queue.locked?(doc).should be_false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "#mark_done" do
|
92
|
+
before(:each) { @doc = Queue.mark_done( Queue.patch( sample_doc ) ) }
|
93
|
+
it "should leave the queue patch intact" do
|
94
|
+
@doc.should have_key(Hotseat.config[:object_name])
|
95
|
+
end
|
96
|
+
it "should add a 'done' object on a patched document" do
|
97
|
+
patch = @doc[Hotseat.config[:object_name]]
|
98
|
+
patch.should have_key('done')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "#add" do
|
103
|
+
it "should add a document to the queue, given a doc id" do
|
104
|
+
reset_test_queue!
|
105
|
+
doc_id = DB.save_doc(sample_doc)['id']
|
106
|
+
@q.add doc_id
|
107
|
+
DB.get(doc_id).should have_key(Hotseat.config[:object_name])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "#add_bulk" do
|
112
|
+
it "should add multiple documents in bulk, given multiple doc ids" do
|
113
|
+
reset_test_queue!
|
114
|
+
doc_ids = create_some_docs
|
115
|
+
@q.add_bulk doc_ids
|
116
|
+
doc_ids.each do |doc_id|
|
117
|
+
DB.get(doc_id).should have_key(Hotseat.config[:object_name])
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "#num_pending" do
|
123
|
+
it "should return the number of documents available for lease" do
|
124
|
+
reset_test_queue!
|
125
|
+
enqueue( create_some_docs(3) )
|
126
|
+
@q.num_pending.should == 3
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "#lease" do
|
131
|
+
before(:each) do
|
132
|
+
reset_test_queue!
|
133
|
+
enqueue( create_some_docs(3) )
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should return an array of queued documents" do
|
137
|
+
docs = @q.lease 2
|
138
|
+
docs.should_not be_nil
|
139
|
+
docs.should have(2).items
|
140
|
+
docs.each do |doc|
|
141
|
+
db_doc = DB.get(doc['_id'])
|
142
|
+
db_doc.should be_kind_of CouchRest::Document
|
143
|
+
db_doc.should have_key(Hotseat.config[:object_name])
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should lock a pending document" do
|
148
|
+
doc_id = @q.lease.first['_id']
|
149
|
+
doc = DB.get(doc_id)
|
150
|
+
doc.should have_key(Hotseat.config[:object_name])
|
151
|
+
doc[Hotseat.config[:object_name]].should have_key('lock')
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should lock and return up to the specified number of documents" do
|
155
|
+
ids = @q.lease 4
|
156
|
+
ids.should have(3).items
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context "when no documents are pending" do
|
161
|
+
before(:each) { reset_test_queue! }
|
162
|
+
|
163
|
+
it "#lease should return nil" do
|
164
|
+
@q.lease.should be_nil
|
165
|
+
end
|
166
|
+
|
167
|
+
it "#get should return nil" do
|
168
|
+
@q.get.should be_nil
|
169
|
+
end
|
170
|
+
|
171
|
+
it "#num_pending should return zero" do
|
172
|
+
@q.num_pending.should be_zero
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context "locked documents" do
|
177
|
+
before(:each) do
|
178
|
+
reset_test_queue!
|
179
|
+
enqueue( create_some_docs(3) )
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should not be pending" do
|
183
|
+
locked_id = @q.lease.first['_id']
|
184
|
+
pending_ids = @q.db.view(Hotseat.pending_view_name)['rows'].map{|row| row['id']}
|
185
|
+
pending_ids.should_not include(locked_id)
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should be counted by #num_locked" do
|
189
|
+
@q.lease 2
|
190
|
+
@q.num_locked.should == 2
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe "#get" do
|
195
|
+
before(:each) do
|
196
|
+
reset_test_queue!
|
197
|
+
enqueue( create_some_docs(3) )
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should return an array of pending documents" do
|
201
|
+
docs = @q.get 2
|
202
|
+
docs.should_not be_nil
|
203
|
+
docs.should have(2).items
|
204
|
+
docs.each do |doc|
|
205
|
+
db_doc = DB.get(doc['_id'])
|
206
|
+
db_doc.should have_key(Hotseat.config[:object_name])
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should not lock the documents it returns" do
|
211
|
+
doc_id = @q.get.first['_id']
|
212
|
+
doc = DB.get(doc_id)
|
213
|
+
doc.should have_key(Hotseat.config[:object_name])
|
214
|
+
doc[Hotseat.config[:object_name]].should_not have_key('lock')
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should return up to the specified number of documents" do
|
218
|
+
docs = @q.get 4
|
219
|
+
docs.should have(3).items
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "#remove" do
|
224
|
+
before(:each) do
|
225
|
+
reset_test_queue!
|
226
|
+
enqueue( create_some_docs(3) )
|
227
|
+
@leased = @q.lease 2
|
228
|
+
@doc_id = @leased.first['_id']
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should unlock a leased document" do
|
232
|
+
@q.remove @doc_id
|
233
|
+
doc = DB.get(@doc_id)
|
234
|
+
doc.should have_key(Hotseat.config[:object_name])
|
235
|
+
doc[Hotseat.config[:object_name]].should_not have_key('lock')
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should remove a document from the queue" do
|
239
|
+
@q.remove @doc_id
|
240
|
+
pending_docs = @q.get 3 # ensure we get all remaining pending docs
|
241
|
+
pending_docs.map{|doc| doc['_id']}.should_not include(@doc_id)
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should raise an error if the lock was already removed" do
|
245
|
+
doc = @leased.first
|
246
|
+
@q.remove @doc_id
|
247
|
+
expect {
|
248
|
+
@q.remove @doc_id
|
249
|
+
}.to raise_error(Hotseat::QueueError)
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should raise an error if the document is missing from the database" do
|
253
|
+
doc_id = @leased.first['_id']
|
254
|
+
doc = @q.db.get(doc_id)
|
255
|
+
@q.db.delete_doc(doc)
|
256
|
+
expect {
|
257
|
+
@q.remove doc['_id']
|
258
|
+
}.to raise_error
|
259
|
+
end
|
260
|
+
|
261
|
+
it "should leave queue history in the document (mark as done) by default" do
|
262
|
+
@q.remove @doc_id
|
263
|
+
doc = DB.get(@doc_id)
|
264
|
+
doc.should have_key(Hotseat.config[:object_name])
|
265
|
+
doc[Hotseat.config[:object_name]].should have_key('done')
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should delete queue history from the document when forget=true" do
|
269
|
+
@q.remove @doc_id, :forget => true
|
270
|
+
doc = DB.get(@doc_id)
|
271
|
+
doc.should_not have_key(Hotseat.config[:object_name])
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe "#remove_bulk" do
|
276
|
+
before(:each) do
|
277
|
+
reset_test_queue!
|
278
|
+
enqueue( create_some_docs(10) )
|
279
|
+
@leased = @q.lease 8
|
280
|
+
@doc_ids = @leased.take(5).map{|doc| doc['_id'] }
|
281
|
+
end
|
282
|
+
|
283
|
+
it "should unlock leased documents" do
|
284
|
+
@q.remove_bulk @doc_ids
|
285
|
+
docs = DB.get_bulk(@doc_ids)['rows'].map{|row| row['doc']}
|
286
|
+
docs.each do |doc|
|
287
|
+
doc.should have_key(Hotseat.config[:object_name])
|
288
|
+
doc[Hotseat.config[:object_name]].should_not have_key('lock')
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
it "should remove multiple documents from the queue" do
|
293
|
+
@q.remove_bulk @doc_ids
|
294
|
+
pending_docs = @q.get 3 # ensure we get all remaining pending docs
|
295
|
+
pending_ids = pending_docs.map{|doc| doc['_id'] }
|
296
|
+
(pending_ids - @doc_ids).should eql(pending_ids)
|
297
|
+
end
|
298
|
+
|
299
|
+
it "should report docs whose lock was already removed" do
|
300
|
+
rem_ids = @doc_ids.take(2)
|
301
|
+
@q.remove_bulk rem_ids
|
302
|
+
res = @q.remove_bulk @doc_ids
|
303
|
+
res['errors'].should have(2).errors
|
304
|
+
res['errors'].map{|err| err['id']}.should == rem_ids
|
305
|
+
end
|
306
|
+
|
307
|
+
it "should report docs that are missing from the database" do
|
308
|
+
rem_ids = @doc_ids.take(2)
|
309
|
+
docs = rem_ids.map{|id| @q.db.get(id) }
|
310
|
+
docs.each {|doc| @q.db.delete_doc(doc) }
|
311
|
+
res = @q.remove_bulk @doc_ids
|
312
|
+
res['errors'].should have(2).errors
|
313
|
+
res['errors'].map{|err| err['id']}.should == rem_ids
|
314
|
+
end
|
315
|
+
|
316
|
+
it "should leave queue history in the document (mark as done) by default" do
|
317
|
+
@q.remove_bulk @doc_ids
|
318
|
+
docs = DB.get_bulk(@doc_ids)['rows'].map{|row| row['doc']}
|
319
|
+
docs.each do |doc|
|
320
|
+
doc.should have_key(Hotseat.config[:object_name])
|
321
|
+
doc[Hotseat.config[:object_name]].should have_key('done')
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
it "should delete queue history from the document when forget=true" do
|
326
|
+
@q.remove_bulk @doc_ids, :forget => true
|
327
|
+
docs = DB.get_bulk(@doc_ids)['rows'].map{|row| row['doc']}
|
328
|
+
docs.each do |doc|
|
329
|
+
doc.should_not have_key(Hotseat.config[:object_name])
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
describe "#num_done" do
|
335
|
+
it "should return the number of documents done" do
|
336
|
+
reset_test_queue!
|
337
|
+
enqueue( create_some_docs(10) )
|
338
|
+
@leased = @q.lease 8
|
339
|
+
@doc_ids = @leased.take(5).map{|doc| doc['_id'] }
|
340
|
+
@q.remove_bulk @doc_ids
|
341
|
+
@q.num_done.should == 5
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
describe "#num_all" do
|
346
|
+
it "should return the total number of documents in the queue" do
|
347
|
+
reset_test_queue!
|
348
|
+
enqueue( create_some_docs(10) )
|
349
|
+
@leased = @q.lease 8
|
350
|
+
@doc_ids = @leased.take(5).map{|doc| doc['_id'] }
|
351
|
+
@q.remove_bulk @doc_ids
|
352
|
+
@q.num_all.should == 10
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
describe "#forget" do
|
357
|
+
it "should delete queue history from a document" do
|
358
|
+
reset_test_queue!
|
359
|
+
enqueue( create_some_docs(1) )
|
360
|
+
doc_id = @q.get.first['_id']
|
361
|
+
@q.forget doc_id
|
362
|
+
doc = DB.get(doc_id)
|
363
|
+
doc.should_not have_key(Hotseat.config[:object_name])
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
describe "#forget_bulk" do
|
368
|
+
it "should delete queue history from multiple document" do
|
369
|
+
reset_test_queue!
|
370
|
+
enqueue( create_some_docs(3) )
|
371
|
+
doc_ids = @q.get(3).map{|doc| doc['_id'] }
|
372
|
+
@q.forget_bulk doc_ids
|
373
|
+
@q.db.bulk_load(doc_ids)['rows'].map{|row| row['doc']}.each do |doc|
|
374
|
+
doc.should_not have_key(Hotseat.config[:object_name])
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
describe "#purge" do
|
380
|
+
before do
|
381
|
+
reset_test_queue!
|
382
|
+
enqueue( create_some_docs(10) )
|
383
|
+
leased = @q.lease 5
|
384
|
+
@q.remove_bulk leased.take(2)
|
385
|
+
end
|
386
|
+
|
387
|
+
it "should remove (and forget) all documents from the queue" do
|
388
|
+
@q.purge
|
389
|
+
@q.num_all.should == 0
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.require(:default, :test)
|
4
|
+
require 'simplecov'
|
5
|
+
SimpleCov.start
|
6
|
+
$LOAD_PATH << File.expand_path('../../lib', __FILE__)
|
7
|
+
require 'hotseat'
|
8
|
+
|
9
|
+
# Requires supporting files with custom matchers and macros, etc,
|
10
|
+
# in ./support/ and its subdirectories.
|
11
|
+
#Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
12
|
+
|
13
|
+
# copied and modified from https://github.com/couchrest/couchrest/blob/master/spec/spec_helper.rb
|
14
|
+
unless defined?(TESTDB)
|
15
|
+
COUCHHOST = ENV['COUCHHOST'] || "http://127.0.0.1:5984"
|
16
|
+
TESTDB = "hotseat%2Ftest"
|
17
|
+
TEST_SERVER = CouchRest.new COUCHHOST
|
18
|
+
TEST_SERVER.default_database = TESTDB
|
19
|
+
DB = TEST_SERVER.database(TESTDB)
|
20
|
+
end
|
21
|
+
|
22
|
+
def reset_test_db!
|
23
|
+
DB.recreate! rescue nil
|
24
|
+
DB
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete_test_db!
|
28
|
+
DB.delete! rescue nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def clean_up_test_dbs
|
32
|
+
cr = TEST_SERVER
|
33
|
+
test_dbs = cr.databases.select { |db| db =~ /^#{URI.unescape(TESTDB)}/ }
|
34
|
+
test_dbs.each do |db|
|
35
|
+
cr.database(db).delete! rescue nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
RSpec.configure do |config|
|
40
|
+
config.after(:all) do
|
41
|
+
clean_up_test_dbs
|
42
|
+
end
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hotseat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Elad Kehat
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-08-06 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: couchrest
|
16
|
+
requirement: &76166650 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.1.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *76166650
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &76165700 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *76165700
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: yard
|
38
|
+
requirement: &76165210 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *76165210
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: &76160770 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *76160770
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: jeweler
|
60
|
+
requirement: &76160430 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *76160430
|
69
|
+
description:
|
70
|
+
email: eladkehat@gmail.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files:
|
74
|
+
- LICENSE.txt
|
75
|
+
- README.rdoc
|
76
|
+
files:
|
77
|
+
- .document
|
78
|
+
- .rspec
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.rdoc
|
82
|
+
- Rakefile
|
83
|
+
- VERSION
|
84
|
+
- hotseat.gemspec
|
85
|
+
- lib/hotseat.rb
|
86
|
+
- lib/hotseat/hotseat.rb
|
87
|
+
- lib/hotseat/queue.rb
|
88
|
+
- spec/hotseat/hotseat_spec.rb
|
89
|
+
- spec/hotseat/queue_spec.rb
|
90
|
+
- spec/spec_helper.rb
|
91
|
+
homepage: http://github.com/eladkehat/hotseat
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
hash: -1003361075
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 1.7.2
|
116
|
+
signing_key:
|
117
|
+
specification_version: 3
|
118
|
+
summary: Add work queue functionality to an existing CouchDB database
|
119
|
+
test_files: []
|