ruote-mongodb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +15 -0
- data/lib/ruote-mongodb.rb +6 -0
- data/lib/ruote-mongodb/mongodb_storage.rb +203 -0
- data/spec/mongodb_storage_spec.rb +225 -0
- data/test/test_storage.rb +264 -0
- metadata +149 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Nathan Stults
|
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
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
ruote-mongodb is a storage provider for the Ruote workflow engine: http://ruote.rubyforge.org/ It enables Ruote to store its data in MongoDB.
|
2
|
+
|
3
|
+
INSTALLATION:
|
4
|
+
If you're using bundler, you can include this in your Gemfile as follows:
|
5
|
+
gem 'ruote-mongodb', :git=>"git://github.com/PlasticLizard/ruote-mongodb.git"
|
6
|
+
|
7
|
+
USAGE:
|
8
|
+
You can initialize Ruote::MongoDbStorage and pass it directly into the constructor for a Ruote::Worker just like any other Ruote storage provider.
|
9
|
+
You can pass in connection information on the constructor as follows:
|
10
|
+
Ruote::MongoDbStorage.new(:connection=>{"host"=>"localhost", "port"=>27017, "database"=>"Ruote", "username"=>"pat", "password"=>"s3cret"})
|
11
|
+
You can pass also pass in a :config option in the constructor parameters with a path to a YAML file containing your configuration settings. If you do that, also specify an :environment option representing which set of config settings to use (eg. "development" or Rails.env).
|
12
|
+
Note that any specific connection settings you pass to the constructor will over-ride the settings from the YAML file.
|
13
|
+
By default (if you don't pass anything in on the constructor), the provider will attempt to connect to host=localhost, port=27017, database=Ruote (unauthenticated)
|
14
|
+
|
15
|
+
*** USE AT YOUR OWN RISK! There is no warrany of any kind for this software. The author accepts no responsibility for data loss or any ohter harm that may come from using this software. In particular, you should be aware that Ruote will call this storage provider's purge! method, which is designed to remove any collections from the database it's using which begin with the string stored in the @@collection_prefix class variable ("ruote_" by default). If that sounds like it could be harmful, consider changing the prefix and/or configuring this provider to use its own database.
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'ruote'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module Ruote
|
5
|
+
class MongoDbStorage
|
6
|
+
include StorageBase
|
7
|
+
include MonitorMixin
|
8
|
+
|
9
|
+
@@collection_prefix = "ruote_"
|
10
|
+
@@encoded_dollar_sign = "~#~"
|
11
|
+
|
12
|
+
def initialize(options={})
|
13
|
+
super()
|
14
|
+
db_config = {"host"=>"localhost", "port"=>27017, "database"=>"Ruote"}
|
15
|
+
options = options.dup
|
16
|
+
if environment = options.delete(:environment)
|
17
|
+
all_db_config=
|
18
|
+
File.open(options.delete(:config) || 'config/database.yml','r') do |f|
|
19
|
+
YAML.load(f)
|
20
|
+
end
|
21
|
+
|
22
|
+
raise "no configuration for environment: #{environment}" unless env_config = all_db_config[environment]
|
23
|
+
db_config.merge!(env_config)
|
24
|
+
end
|
25
|
+
#args take precedent over config
|
26
|
+
db_config.merge! options.delete(:connection) if options[:connection]
|
27
|
+
|
28
|
+
@db = Mongo::Connection.new(db_config['host'], db_config['port']).
|
29
|
+
db(db_config['database'])
|
30
|
+
if db_config['username'] && db_config['password']
|
31
|
+
@db.authenticate(db_config['username'], db_config['password'])
|
32
|
+
end
|
33
|
+
|
34
|
+
unless get('configurations','engine')
|
35
|
+
put(options.merge('type'=>'configurations', '_id'=>'engine'))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def close_connection()
|
40
|
+
@db.connection.close()
|
41
|
+
end
|
42
|
+
|
43
|
+
def put(doc, opts={})
|
44
|
+
synchronize do
|
45
|
+
raise "doc must have a type" unless doc['type']
|
46
|
+
raise "doc must have an ID" unless doc['_id']
|
47
|
+
pre = get(doc['type'], doc['_id'])
|
48
|
+
|
49
|
+
if pre && pre['_rev'] != doc['_rev']
|
50
|
+
return pre
|
51
|
+
end
|
52
|
+
|
53
|
+
if pre.nil? && doc['_rev']
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
|
57
|
+
doc = if opts[:update_rev]
|
58
|
+
doc['_rev'] = pre ? pre['_rev'] : -1
|
59
|
+
doc
|
60
|
+
else
|
61
|
+
doc.merge('_rev' => doc['_rev'] || -1)
|
62
|
+
end
|
63
|
+
|
64
|
+
doc['put_at'] = Ruote.now_to_utc_s
|
65
|
+
doc['_rev'] = doc['_rev'] + 1
|
66
|
+
|
67
|
+
encoded_doc = Rufus::Json.dup(doc)
|
68
|
+
to_mongo encoded_doc
|
69
|
+
get_collection(doc['type']).save(encoded_doc)
|
70
|
+
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def get(type, key)
|
76
|
+
synchronize do
|
77
|
+
doc = get_collection(type).find_one("_id" => key)
|
78
|
+
from_mongo doc if doc
|
79
|
+
doc
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete(doc)
|
84
|
+
drev = doc['_rev']
|
85
|
+
raise ArgumentError.new("can't delete doc without _rev") unless drev
|
86
|
+
synchronize do
|
87
|
+
raise "doc must have a type" unless doc['type']
|
88
|
+
prev = get(doc['type'], doc['_id'])
|
89
|
+
return true if prev.nil?
|
90
|
+
doc['_rev'] ||= 0
|
91
|
+
if prev['_rev'] == drev
|
92
|
+
get_collection(doc['type']).remove("_id" => doc["_id"])
|
93
|
+
nil
|
94
|
+
else
|
95
|
+
prev
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_many(type, key=nil, opts={})
|
101
|
+
synchronize do
|
102
|
+
return get_collection(type).count if opts[:count]
|
103
|
+
criteria = {}
|
104
|
+
find_opts = {}
|
105
|
+
find_opts[:limit] = opts[:limit] if opts[:limit]
|
106
|
+
find_opts[:skip] = opts[:skip] if opts[:skip]
|
107
|
+
find_opts[:sort] = ["_id", opts[:descending] ? :descending : :ascending]
|
108
|
+
if key
|
109
|
+
id_criteria = Array(key).map do |k|
|
110
|
+
case k.class.to_s
|
111
|
+
when "String" then "!#{k}$"
|
112
|
+
when "Regexp" then k.source
|
113
|
+
else k.to_s
|
114
|
+
end
|
115
|
+
end
|
116
|
+
criteria = {"_id" => /#{id_criteria.join "|"}/}
|
117
|
+
end
|
118
|
+
docs = get_collection(type).find(criteria, find_opts).to_a
|
119
|
+
docs.each do |doc|
|
120
|
+
from_mongo doc
|
121
|
+
end
|
122
|
+
docs
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def ids(type)
|
127
|
+
synchronize do
|
128
|
+
result = get_collection(type).find({}, {:fields=>["_id"]}).map do |row|
|
129
|
+
row["_id"].to_s
|
130
|
+
end
|
131
|
+
result.sort
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def purge!
|
136
|
+
synchronize do
|
137
|
+
@db.collection_names.each do |name|
|
138
|
+
@db.drop_collection(name) if name =~ /^#{@@collection_prefix}/
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def add_type(type)
|
144
|
+
synchronize do
|
145
|
+
get_collection(type).create_index("_id")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def purge_type!(type)
|
150
|
+
synchronize do
|
151
|
+
@db.drop_collection(@@collection_prefix + type)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def get_collection(type)
|
158
|
+
@db[@@collection_prefix + type]
|
159
|
+
end
|
160
|
+
|
161
|
+
# encodes unsupported data ($, Date) for storage in MongoDB
|
162
|
+
def from_mongo(doc)
|
163
|
+
mongo_encode(doc, /^#{@@encoded_dollar_sign}/, "$", :backward)
|
164
|
+
end
|
165
|
+
|
166
|
+
# unencodes unsupported values ($, Date) from storage in MongoDB
|
167
|
+
def to_mongo(doc)
|
168
|
+
mongo_encode(doc, /^\$/, @@encoded_dollar_sign, :forward)
|
169
|
+
end
|
170
|
+
|
171
|
+
# called by from_mongo and to_mongo
|
172
|
+
def mongo_encode(doc, pattern, replacement, date_conv)
|
173
|
+
if doc.is_a? Hash
|
174
|
+
doc.each_pair do |key, value|
|
175
|
+
new_key = key
|
176
|
+
if key.is_a?(String) && key =~ pattern
|
177
|
+
new_key = key.sub(pattern, replacement)
|
178
|
+
doc[new_key] = value
|
179
|
+
doc.delete key
|
180
|
+
end
|
181
|
+
mongo_encode(value, pattern, replacement, date_conv)
|
182
|
+
ensure_date_encoding(value, doc, new_key, date_conv)
|
183
|
+
doc[new_key] = value.to_s if value.is_a? Symbol
|
184
|
+
end
|
185
|
+
elsif doc.is_a? Array
|
186
|
+
doc.each_with_index do |entry, i|
|
187
|
+
mongo_encode(entry, pattern, replacement, date_conv)
|
188
|
+
ensure_date_encoding(entry, doc, i, date_conv)
|
189
|
+
doc[i] = entry.to_s if entry.is_a? Symbol
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def ensure_date_encoding(value, doc, key, date_conv)
|
195
|
+
if value.is_a?(Date) && date_conv == :forward
|
196
|
+
doc[key] = "DT_" + value.to_s
|
197
|
+
end
|
198
|
+
if value.is_a?(String) && value[0,3] == "DT_" && date_conv == :backward
|
199
|
+
doc[key] = Date.parse(value[3..-1])
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'ruote-mongodb'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
describe Ruote::MongoDbStorage do
|
5
|
+
describe "options" do
|
6
|
+
after :each do
|
7
|
+
File.delete("config/database.yml") if File.exist?("config/database.yml")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "uses sensible defaults if no DB configuration specified" do
|
11
|
+
lambda {
|
12
|
+
Ruote::MongoDbStorage.new
|
13
|
+
}.should_not raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
it "fails if invalid server passed in on constructor" do
|
17
|
+
lambda {
|
18
|
+
Ruote::MongoDbStorage.new :connection=>{"host"=>"doesntexist"}
|
19
|
+
}.should raise_error Mongo::ConnectionFailure
|
20
|
+
end
|
21
|
+
|
22
|
+
it "fails if an environment is passed in but database.yml doesn't exist" do
|
23
|
+
lambda {
|
24
|
+
Ruote::MongoDbStorage.new :environment=>"test"
|
25
|
+
}.should raise_error "No such file or directory - config/database.yml"
|
26
|
+
#TODO: consider a better error message for this case
|
27
|
+
end
|
28
|
+
|
29
|
+
it "works when database.yml specifies valid settings for the environment" do
|
30
|
+
File.open("config/database.yml", "w") do |f|
|
31
|
+
f.puts "test:"
|
32
|
+
f.puts " host: localhost"
|
33
|
+
end
|
34
|
+
lambda {
|
35
|
+
Ruote::MongoDbStorage.new :environment=>"test"
|
36
|
+
}.should_not raise_error
|
37
|
+
end
|
38
|
+
|
39
|
+
it "fails when environment doesn't exist in database.yml" do
|
40
|
+
File.open("config/database.yml", "w") do |f|
|
41
|
+
f.puts "test:"
|
42
|
+
f.puts " host: localhost"
|
43
|
+
end
|
44
|
+
lambda {
|
45
|
+
Ruote::MongoDbStorage.new :environment=>"development"
|
46
|
+
}.should raise_error "no configuration for environment: development"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "fails when invalid server in database.yml" do
|
50
|
+
File.open("config/database.yml", "w") do |f|
|
51
|
+
f.puts "test:"
|
52
|
+
f.puts " host: doesntexist"
|
53
|
+
end
|
54
|
+
lambda {
|
55
|
+
Ruote::MongoDbStorage.new :environment=>"test"
|
56
|
+
}.should raise_error Mongo::ConnectionFailure
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "provider" do
|
61
|
+
before :each do
|
62
|
+
@repo = Ruote::MongoDbStorage.new
|
63
|
+
@repo.purge!
|
64
|
+
end
|
65
|
+
|
66
|
+
after :each do
|
67
|
+
@repo.close_connection
|
68
|
+
end
|
69
|
+
|
70
|
+
it "can store and retrieve a document by ID" do
|
71
|
+
key = BSON::ObjectId.new.to_s
|
72
|
+
doc = {"_id" => key, "name" => "ralph", "type" => "test"}
|
73
|
+
result = @repo.put doc
|
74
|
+
result.should be_nil
|
75
|
+
doc = @repo.get 'test', key
|
76
|
+
doc["name"].should == "ralph"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "can update a document by ID" do
|
80
|
+
key = BSON::ObjectId.new.to_s
|
81
|
+
doc = {"_id" => key, "name" => "ralph", "type" => "test"}
|
82
|
+
@repo.put doc
|
83
|
+
doc = @repo.get 'test', key
|
84
|
+
doc["name"] = "bill"
|
85
|
+
@repo.put doc
|
86
|
+
doc = @repo.get 'test', key
|
87
|
+
doc["name"].should == "bill"
|
88
|
+
end
|
89
|
+
|
90
|
+
it "can store documents with keys starting with dollar sign" do
|
91
|
+
key = BSON::ObjectId.new.to_s
|
92
|
+
doc = {"_id" => key, "type" => "test", "a" => ["$b" => "c"]}
|
93
|
+
@repo.put doc
|
94
|
+
doc = @repo.get 'test', key
|
95
|
+
doc["a"].should == ["$b" => "c"]
|
96
|
+
end
|
97
|
+
|
98
|
+
it "can store documents with dates" do
|
99
|
+
key = BSON::ObjectId.new.to_s
|
100
|
+
doc = {"_id" => key, "type" => "test", "a" => ["b" => Date.parse("11/9/2010")]}
|
101
|
+
@repo.put doc
|
102
|
+
doc = @repo.get 'test', key
|
103
|
+
doc["a"][0]["b"].to_s.should == "2010-11-09"
|
104
|
+
end
|
105
|
+
|
106
|
+
it "can store large floating point numbers accurately" do
|
107
|
+
key = BSON::ObjectId.new.to_s
|
108
|
+
doc = {"_id" => key, "type" => "test", "raw" => 1289501850.34665} #1289443610.7243}
|
109
|
+
@repo.put doc
|
110
|
+
doc = @repo.get 'test', key
|
111
|
+
doc["raw"].should == 1289501850.34665 #1289443610.7243
|
112
|
+
end
|
113
|
+
|
114
|
+
it "can retrieve a document by a string ID" do
|
115
|
+
key = "hello"
|
116
|
+
doc = {"_id" => key, "name" => "ralph", "type" => "test"}
|
117
|
+
@repo.put doc
|
118
|
+
doc = @repo.get 'test', key
|
119
|
+
doc["name"].should == "ralph"
|
120
|
+
end
|
121
|
+
|
122
|
+
it "can delete a document" do
|
123
|
+
key = BSON::ObjectId.new.to_s
|
124
|
+
doc = {"_id" => key, "name" => "ralph", "type" => "test", "_rev" => 0}
|
125
|
+
@repo.put doc
|
126
|
+
@repo.delete doc
|
127
|
+
@repo.get('test', key).should be_nil
|
128
|
+
end
|
129
|
+
|
130
|
+
it "will provide a list of IDs" do
|
131
|
+
key1 = "hello" + BSON::ObjectId.new.to_s
|
132
|
+
key2 = "hello" + BSON::ObjectId.new.to_s
|
133
|
+
key3 = "hello" + BSON::ObjectId.new.to_s
|
134
|
+
@repo.put({"_id" => key1, "name" => "ralph", "type" => "test"})
|
135
|
+
@repo.put({"_id" => key2, "name" => "ralph", "type" => "test"})
|
136
|
+
@repo.put({"_id" => key3, "name" => "ralph", "type" => "test2"})
|
137
|
+
@repo.ids("test").should == [key1, key2]
|
138
|
+
@repo.ids("test2").should == [key3]
|
139
|
+
end
|
140
|
+
|
141
|
+
it "only purges collections starting with the ruote_ prefix" do
|
142
|
+
db = @repo.instance_eval "@db"
|
143
|
+
db.drop_collection("something_else")
|
144
|
+
@repo.put({"_id" => BSON::ObjectId.new.to_s, "name" => "ralph", "type" => "test"})
|
145
|
+
@repo.put({"_id" => BSON::ObjectId.new.to_s, "name" => "bill", "type" => "test2"})
|
146
|
+
db.collection_names.should == ["system.indexes", "ruote_test", "ruote_test2"]
|
147
|
+
db["something_else"].insert({"name" => "doug"})
|
148
|
+
@repo.purge!
|
149
|
+
db.collection_names.should == ["system.indexes", "something_else"]
|
150
|
+
end
|
151
|
+
|
152
|
+
it "can purge a particular type" do
|
153
|
+
key1 = BSON::ObjectId.new.to_s
|
154
|
+
key2 = BSON::ObjectId.new.to_s
|
155
|
+
@repo.put({"_id" => key1, "name" => "ralph", "type" => "test"})
|
156
|
+
@repo.put({"_id" => key2, "name" => "bill", "type" => "test2"})
|
157
|
+
@repo.purge_type! "test2"
|
158
|
+
@repo.get('test', key1)["name"].should == "ralph"
|
159
|
+
@repo.get('test2', key2).should be_nil
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "can get multiple documents" do
|
163
|
+
before :each do
|
164
|
+
key1 = "TANGO!ALPHA!BRAVO"
|
165
|
+
key2 = "TANGO!ALPHA-BRAVO"
|
166
|
+
key3 = "FOXTROT!ALPHA-BRAVO"
|
167
|
+
@repo.put({"_id"=>key1, "fname"=>"ralph", "lname"=>"A", "type"=>"test"})
|
168
|
+
@repo.put({"_id"=>key2, "fname"=>"bill", "lname"=>"B", "type"=>"test"})
|
169
|
+
@repo.put({"_id"=>key3, "fname"=>"nancy", "lname"=>"A", "type"=>"test"})
|
170
|
+
end
|
171
|
+
|
172
|
+
it "with criteria" do
|
173
|
+
search_key = ["BRAVO", /FOXTROT/]
|
174
|
+
docs = @repo.get_many("test", search_key)
|
175
|
+
docs.count.should == 2
|
176
|
+
(docs.select {|doc| doc["fname"] == "ralph"}).count.should == 1
|
177
|
+
(docs.select {|doc| doc["fname"] == "nancy"}).count.should == 1
|
178
|
+
(docs.select {|doc| doc["fname"] == "bill"}).count.should == 0
|
179
|
+
end
|
180
|
+
|
181
|
+
it "without criteria" do
|
182
|
+
docs = @repo.get_many("test", nil)
|
183
|
+
docs.count.should == 3
|
184
|
+
end
|
185
|
+
|
186
|
+
it "up to a certain limit" do
|
187
|
+
docs = @repo.get_many("test", nil, {:limit => 2})
|
188
|
+
docs.count.should == 2
|
189
|
+
(docs.select {|doc| doc["fname"] == "nancy"}).count.should == 1
|
190
|
+
(docs.select {|doc| doc["fname"] == "ralph"}).count.should == 1
|
191
|
+
end
|
192
|
+
|
193
|
+
it "skipping a certain number" do
|
194
|
+
docs = @repo.get_many("test", nil, {:skip => 2})
|
195
|
+
docs.count.should == 1
|
196
|
+
(docs.select {|doc| doc["fname"] == "bill"}).count.should == 1
|
197
|
+
end
|
198
|
+
|
199
|
+
it "in descending order" do
|
200
|
+
docs = @repo.get_many("test", nil, {:descending => true})
|
201
|
+
docs[0]["fname"].should == "bill"
|
202
|
+
docs[1]["fname"].should == "ralph"
|
203
|
+
docs[2]["fname"].should == "nancy"
|
204
|
+
end
|
205
|
+
|
206
|
+
it "in ascending order" do
|
207
|
+
docs = @repo.get_many("test", nil, {:descending => false})
|
208
|
+
docs[0]["fname"].should == "nancy"
|
209
|
+
docs[1]["fname"].should == "ralph"
|
210
|
+
docs[2]["fname"].should == "bill"
|
211
|
+
end
|
212
|
+
|
213
|
+
it "in asceneding order, by default" do
|
214
|
+
docs = @repo.get_many("test", nil)
|
215
|
+
docs[0]["fname"].should == "nancy"
|
216
|
+
docs[1]["fname"].should == "ralph"
|
217
|
+
docs[2]["fname"].should == "bill"
|
218
|
+
end
|
219
|
+
|
220
|
+
it "count" do
|
221
|
+
@repo.get_many("test", nil, {:count => true}).should == 3
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'mongo'
|
4
|
+
require 'lib/ruote-mongodb'
|
5
|
+
|
6
|
+
#
|
7
|
+
# note : using the 'errors' type, but this test is about generic storage, not
|
8
|
+
# about errors per se.
|
9
|
+
#
|
10
|
+
|
11
|
+
class UtStorage < Test::Unit::TestCase
|
12
|
+
|
13
|
+
def setup
|
14
|
+
|
15
|
+
# @s = determine_storage({})
|
16
|
+
@s = Ruote::MongoDbStorage.new
|
17
|
+
|
18
|
+
#@s.add_type('errors')
|
19
|
+
|
20
|
+
@s.put(
|
21
|
+
'_id' => 'toto',
|
22
|
+
'type' => 'errors',
|
23
|
+
'message' => 'testing')
|
24
|
+
end
|
25
|
+
def teardown
|
26
|
+
|
27
|
+
@s.get_many('errors').each do |d|
|
28
|
+
@s.delete(d)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_get_configuration
|
33
|
+
|
34
|
+
assert_not_nil @s.get_configuration('engine')
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_get
|
38
|
+
|
39
|
+
h = @s.get('errors', 'toto')
|
40
|
+
|
41
|
+
assert_not_nil h['_rev']
|
42
|
+
|
43
|
+
h = @s.get('errors', 'nada')
|
44
|
+
|
45
|
+
assert_nil h
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_put
|
49
|
+
|
50
|
+
doc = {
|
51
|
+
'_id' => 'test_put', 'type' => 'errors', 'message' => 'testing (2)' }
|
52
|
+
|
53
|
+
@s.put(doc)
|
54
|
+
|
55
|
+
assert_nil doc['_rev']
|
56
|
+
|
57
|
+
h = @s.get('errors', 'test_put')
|
58
|
+
|
59
|
+
assert_not_nil h['_rev']
|
60
|
+
assert_not_nil h['put_at']
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_put_fail
|
64
|
+
|
65
|
+
r = @s.put('_id' => 'toto', 'type' => 'errors', 'message' => 'more')
|
66
|
+
|
67
|
+
assert_equal 'toto', r['_id']
|
68
|
+
assert_not_nil r['_rev']
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_put_update_rev
|
72
|
+
|
73
|
+
doc = { '_id' => 'tpur', 'type' => 'errors', 'message' => 'more' }
|
74
|
+
|
75
|
+
r = @s.put(doc, :update_rev => true)
|
76
|
+
|
77
|
+
assert_not_nil doc['_rev']
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_put_put_and_put
|
81
|
+
|
82
|
+
doc = { '_id' => 'whiskas', 'type' => 'errors', 'message' => 'miam' }
|
83
|
+
|
84
|
+
r = @s.put(doc)
|
85
|
+
doc = @s.get('errors', 'whiskas')
|
86
|
+
|
87
|
+
r = @s.put(doc)
|
88
|
+
assert_nil r
|
89
|
+
|
90
|
+
doc = @s.get('errors', 'whiskas')
|
91
|
+
|
92
|
+
assert_not_nil doc['put_at']
|
93
|
+
|
94
|
+
r = @s.put(doc)
|
95
|
+
assert_nil r
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_put_update_rev_twice
|
99
|
+
|
100
|
+
doc = { '_id' => 'tpurt', 'type' => 'errors', 'message' => 'more' }
|
101
|
+
|
102
|
+
r = @s.put(doc, :update_rev => true)
|
103
|
+
assert_nil r
|
104
|
+
|
105
|
+
doc = { '_id' => 'tpurt', 'type' => 'errors', 'message' => 'more' }
|
106
|
+
|
107
|
+
r = @s.put(doc, :update_rev => true)
|
108
|
+
assert_not_nil r
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_delete_fail
|
112
|
+
|
113
|
+
# missing _rev
|
114
|
+
|
115
|
+
assert_raise(ArgumentError) do
|
116
|
+
@s.delete('_id' => 'toto')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_delete
|
121
|
+
|
122
|
+
doc = @s.get('errors', 'toto')
|
123
|
+
puts "test_delete: #{doc}"
|
124
|
+
r = @s.delete(doc)
|
125
|
+
|
126
|
+
assert_nil r
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_delete_missing
|
130
|
+
|
131
|
+
r = @s.delete('_id' => 'x', '_rev' => '12-13231123132', 'type' => 'errors')
|
132
|
+
|
133
|
+
assert_equal true, r
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_keys_should_be_string
|
137
|
+
|
138
|
+
doc = { '_id' => 'h0', 'type' => 'errors', :m0 => :z, :m1 => [ :a, :b ] }
|
139
|
+
|
140
|
+
@s.put(doc)
|
141
|
+
|
142
|
+
doc = @s.get('errors', 'h0')
|
143
|
+
|
144
|
+
assert_equal 'z', doc['m0']
|
145
|
+
assert_equal %w[ a b ], doc['m1']
|
146
|
+
end
|
147
|
+
|
148
|
+
# Updating a gone document must result in a 'true' reply.
|
149
|
+
#
|
150
|
+
def test_put_gone
|
151
|
+
|
152
|
+
h = @s.get('errors', 'toto')
|
153
|
+
|
154
|
+
assert_nil @s.delete(h)
|
155
|
+
|
156
|
+
h['colour'] = 'blue'
|
157
|
+
|
158
|
+
assert_equal true, @s.put(h)
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_purge_type
|
162
|
+
|
163
|
+
@s.purge_type!('errors')
|
164
|
+
|
165
|
+
assert_equal 0, @s.get_many('errors').size
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_clear
|
169
|
+
|
170
|
+
@s.clear
|
171
|
+
|
172
|
+
assert_equal 0, @s.get_many('errors').size
|
173
|
+
end
|
174
|
+
|
175
|
+
#def test_purge
|
176
|
+
# @s.purge!
|
177
|
+
# assert_equal 0, @s.get_many('errors').size
|
178
|
+
#end
|
179
|
+
|
180
|
+
def test_ids
|
181
|
+
|
182
|
+
@s.put('_id' => 't_ids0', 'type' => 'errors', 'message' => 'testing')
|
183
|
+
@s.put('_id' => 't_ids1', 'type' => 'errors', 'message' => 'testing')
|
184
|
+
@s.put('_id' => 't_ids2', 'type' => 'errors', 'message' => 'testing')
|
185
|
+
|
186
|
+
assert_equal %w[ t_ids0 t_ids1 t_ids2 toto ], @s.ids('errors').sort
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_get_many
|
190
|
+
|
191
|
+
30.times do |i|
|
192
|
+
@s.put(
|
193
|
+
'_id' => "xx!#{i}",
|
194
|
+
'type' => 'errors',
|
195
|
+
'wfid' => i.to_s,
|
196
|
+
'msg' => "whatever #{i}")
|
197
|
+
end
|
198
|
+
|
199
|
+
assert_equal 31, @s.get_many('errors').size
|
200
|
+
assert_equal 1, @s.get_many('errors', '7').size
|
201
|
+
assert_equal 1, @s.get_many('errors', /!7$/).size
|
202
|
+
assert_equal 30, @s.get_many('errors', /^xx!/).size
|
203
|
+
assert_equal 30, @s.get_many('errors', /x/).size
|
204
|
+
assert_equal 10, @s.get_many('errors', nil, :limit => 10).size
|
205
|
+
end
|
206
|
+
|
207
|
+
def load_30_errors
|
208
|
+
30.times do |i|
|
209
|
+
@s.put(
|
210
|
+
'_id' => sprintf("yy!%0.2d", i),
|
211
|
+
'type' => 'errors',
|
212
|
+
'msg' => "whatever #{i}")
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_get_many_options
|
217
|
+
load_30_errors
|
218
|
+
|
219
|
+
# limit
|
220
|
+
|
221
|
+
assert_equal 10, @s.get_many('errors', nil, :limit => 10).size
|
222
|
+
|
223
|
+
# count
|
224
|
+
|
225
|
+
assert_equal 31, @s.get_many('errors', nil, :count => true)
|
226
|
+
|
227
|
+
# skip and limit
|
228
|
+
|
229
|
+
assert_equal(
|
230
|
+
%w[ toto yy!00 yy!01 yy!02 ],
|
231
|
+
@s.get_many(
|
232
|
+
'errors', nil, :skip => 0, :limit => 4
|
233
|
+
).collect { |d| d['_id'] })
|
234
|
+
assert_equal(
|
235
|
+
%w[ yy!02 yy!03 yy!04 ],
|
236
|
+
@s.get_many(
|
237
|
+
'errors', nil, :skip => 3, :limit => 3
|
238
|
+
).collect { |d| d['_id'] })
|
239
|
+
|
240
|
+
# skip, limit and reverse
|
241
|
+
|
242
|
+
assert_equal(
|
243
|
+
%w[ yy!29 yy!28 yy!27 ],
|
244
|
+
@s.get_many(
|
245
|
+
'errors', nil, :skip => 0, :limit => 3, :descending => true
|
246
|
+
).collect { |d| d['_id'] })
|
247
|
+
assert_equal(
|
248
|
+
%w[ yy!29 yy!28 yy!27 ],
|
249
|
+
@s.get_many(
|
250
|
+
'errors', nil, :skip => 0, :limit => 3, :descending => true
|
251
|
+
).collect { |d| d['_id'] })
|
252
|
+
end
|
253
|
+
|
254
|
+
# def test_dump
|
255
|
+
# load_30_errors
|
256
|
+
# assert @s.dump('errors').length > 0
|
257
|
+
# end
|
258
|
+
|
259
|
+
def test_ids
|
260
|
+
load_30_errors
|
261
|
+
assert_equal 31, @s.ids('errors').length
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruote-mongodb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Patrick Gannon
|
14
|
+
- Nathan Stults
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-12-10 00:00:00 -08:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: mongo
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 17
|
31
|
+
segments:
|
32
|
+
- 1
|
33
|
+
- 1
|
34
|
+
- 1
|
35
|
+
version: 1.1.1
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: bson
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 17
|
47
|
+
segments:
|
48
|
+
- 1
|
49
|
+
- 1
|
50
|
+
- 1
|
51
|
+
version: 1.1.1
|
52
|
+
type: :runtime
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: bson_ext
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 17
|
63
|
+
segments:
|
64
|
+
- 1
|
65
|
+
- 1
|
66
|
+
- 1
|
67
|
+
version: 1.1.1
|
68
|
+
type: :runtime
|
69
|
+
version_requirements: *id003
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rspec
|
72
|
+
prerelease: false
|
73
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 3
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
type: :development
|
83
|
+
version_requirements: *id004
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: test-unit
|
86
|
+
prerelease: false
|
87
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 3
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
96
|
+
type: :development
|
97
|
+
version_requirements: *id005
|
98
|
+
description:
|
99
|
+
email:
|
100
|
+
- hereiam@sonic.net
|
101
|
+
executables: []
|
102
|
+
|
103
|
+
extensions: []
|
104
|
+
|
105
|
+
extra_rdoc_files: []
|
106
|
+
|
107
|
+
files:
|
108
|
+
- lib/ruote-mongodb.rb
|
109
|
+
- lib/ruote-mongodb/mongodb_storage.rb
|
110
|
+
- test/test_storage.rb
|
111
|
+
- spec/mongodb_storage_spec.rb
|
112
|
+
- LICENSE
|
113
|
+
- README
|
114
|
+
has_rdoc: true
|
115
|
+
homepage: http://github.com/PlasticLizard/ruote-mongodb
|
116
|
+
licenses: []
|
117
|
+
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
hash: 3
|
129
|
+
segments:
|
130
|
+
- 0
|
131
|
+
version: "0"
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
hash: 3
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
version: "0"
|
141
|
+
requirements: []
|
142
|
+
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 1.3.7
|
145
|
+
signing_key:
|
146
|
+
specification_version: 3
|
147
|
+
summary: MongoDB persistence for Ruote
|
148
|
+
test_files: []
|
149
|
+
|