relaxdb 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/LICENSE +20 -0
  2. data/README.textile +200 -0
  3. data/Rakefile +63 -0
  4. data/docs/spec_results.html +1059 -0
  5. data/lib/more/atomic_bulk_save_support.rb +18 -0
  6. data/lib/more/grapher.rb +48 -0
  7. data/lib/relaxdb.rb +50 -0
  8. data/lib/relaxdb/all_delegator.rb +44 -0
  9. data/lib/relaxdb/belongs_to_proxy.rb +29 -0
  10. data/lib/relaxdb/design_doc.rb +57 -0
  11. data/lib/relaxdb/document.rb +600 -0
  12. data/lib/relaxdb/extlib.rb +24 -0
  13. data/lib/relaxdb/has_many_proxy.rb +101 -0
  14. data/lib/relaxdb/has_one_proxy.rb +42 -0
  15. data/lib/relaxdb/migration.rb +40 -0
  16. data/lib/relaxdb/migration_version.rb +21 -0
  17. data/lib/relaxdb/net_http_server.rb +61 -0
  18. data/lib/relaxdb/paginate_params.rb +53 -0
  19. data/lib/relaxdb/paginator.rb +88 -0
  20. data/lib/relaxdb/query.rb +76 -0
  21. data/lib/relaxdb/references_many_proxy.rb +97 -0
  22. data/lib/relaxdb/relaxdb.rb +250 -0
  23. data/lib/relaxdb/server.rb +109 -0
  24. data/lib/relaxdb/taf2_curb_server.rb +63 -0
  25. data/lib/relaxdb/uuid_generator.rb +21 -0
  26. data/lib/relaxdb/validators.rb +11 -0
  27. data/lib/relaxdb/view_object.rb +34 -0
  28. data/lib/relaxdb/view_result.rb +18 -0
  29. data/lib/relaxdb/view_uploader.rb +49 -0
  30. data/lib/relaxdb/views.rb +114 -0
  31. data/readme.rb +80 -0
  32. data/spec/belongs_to_spec.rb +124 -0
  33. data/spec/callbacks_spec.rb +80 -0
  34. data/spec/derived_properties_spec.rb +112 -0
  35. data/spec/design_doc_spec.rb +34 -0
  36. data/spec/doc_inheritable_spec.rb +100 -0
  37. data/spec/document_spec.rb +545 -0
  38. data/spec/has_many_spec.rb +202 -0
  39. data/spec/has_one_spec.rb +123 -0
  40. data/spec/migration_spec.rb +97 -0
  41. data/spec/migration_version_spec.rb +28 -0
  42. data/spec/paginate_params_spec.rb +15 -0
  43. data/spec/paginate_spec.rb +360 -0
  44. data/spec/query_spec.rb +90 -0
  45. data/spec/references_many_spec.rb +173 -0
  46. data/spec/relaxdb_spec.rb +364 -0
  47. data/spec/server_spec.rb +32 -0
  48. data/spec/spec.opts +1 -0
  49. data/spec/spec_helper.rb +65 -0
  50. data/spec/spec_models.rb +199 -0
  51. data/spec/view_by_spec.rb +76 -0
  52. data/spec/view_object_spec.rb +47 -0
  53. data/spec/view_spec.rb +23 -0
  54. metadata +137 -0
@@ -0,0 +1,173 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/spec_models.rb'
3
+
4
+ describe RelaxDB::ReferencesManyProxy do
5
+
6
+ before(:all) do
7
+ setup_test_db
8
+ end
9
+
10
+ describe "references_many" do
11
+
12
+ it "should preserve the relationships across the save / load boundary" do
13
+ p = Photo.new
14
+ t = Tag.new
15
+ t.photos << p
16
+
17
+ p = RelaxDB.load p._id
18
+ p.tags.size.should == 1
19
+ p.tags[0].should == t
20
+
21
+ t = RelaxDB.load t._id
22
+ t.photos.size.should == 1
23
+ t.photos[0].should == p
24
+ end
25
+
26
+ it "should issue only a single request to resolve the relationship" do
27
+ p, t = Photo.new, Tag.new
28
+ p.tags << t
29
+
30
+ # Create the views
31
+ p.tags.map { |t| t._id }
32
+
33
+ p = RelaxDB.load p._id
34
+ RelaxDB.db.get_count = 0
35
+ p.tags.map { |t| t._id }
36
+ p.tags.map { |t| t._id }
37
+ RelaxDB.db.get_count.should == 1
38
+ end
39
+
40
+ it "should not resolve the relationship when an object is instantiated" do
41
+ p, t = Photo.new, Tag.new
42
+ p.tags << t
43
+
44
+ RelaxDB.db.get_count = 0
45
+ p = RelaxDB.load p._id
46
+ RelaxDB.db.get_count.should == 1
47
+ end
48
+
49
+ it "should make the ids available as a property" do
50
+ p, t = Photo.new, Tag.new
51
+ p.tags << t
52
+
53
+ p.tags_ids.should == [t._id]
54
+ end
55
+
56
+ describe "#=" do
57
+ it "should not be invoked" do
58
+ end
59
+ end
60
+
61
+ describe "#<<" do
62
+
63
+ it "should set the relationship on both sides" do
64
+ p = Photo.new(:name => "photo")
65
+ t = Tag.new(:name => "tag")
66
+ p.tags << t
67
+
68
+ p.tags.size.should == 1
69
+ p.tags[0].name.should == "tag"
70
+
71
+ t.photos.size.should == 1
72
+ t.photos[0].name.should == "photo"
73
+ end
74
+
75
+ it "should not create duplicates when the same object is added more than once" do
76
+ p = Photo.new
77
+ t = Tag.new
78
+ p.tags << t << t
79
+ p.tags.size.should == 1
80
+ end
81
+
82
+ it "should not create duplicates when reciprocal objects are added from opposite sides" do
83
+ p = Photo.new
84
+ t = Tag.new
85
+ p.tags << t
86
+ t.photos << p
87
+ p.tags.size.should == 1
88
+ t.photos.size.should == 1
89
+ end
90
+
91
+ it "will resolve the reciprocal relationship" do
92
+ # Create the views
93
+ p, t = Photo.new, Tag.new
94
+ p.tags << t
95
+
96
+ p, t = Photo.new, Tag.new
97
+ RelaxDB.db.get_count = 0
98
+ p.tags << t
99
+ RelaxDB.db.get_count.should == 2
100
+ end
101
+
102
+ end
103
+
104
+ describe "#delete" do
105
+
106
+ it "should nullify relationship on both sides" do
107
+ p = Photo.new
108
+ t = Tag.new
109
+ p.tags << t
110
+
111
+ p.tags.delete(t)
112
+ p.tags.should be_empty
113
+ t.photos.should be_empty
114
+ end
115
+
116
+ end
117
+
118
+ describe "owner#destroy" do
119
+
120
+ it "should remove its membership from its peers in memory" do
121
+ p = Photo.new
122
+ t = Tag.new
123
+ p.tags << t
124
+
125
+ p.destroy!
126
+ t.photos.size.should == 0
127
+ end
128
+
129
+ it "should remove its membership from its peers in CouchDB" do
130
+ p = Photo.new
131
+ t = Tag.new
132
+ p.tags << t
133
+
134
+ p.destroy!
135
+ RelaxDB.load(t._id).photos.should be_empty
136
+ end
137
+
138
+ end
139
+
140
+ # Leaving this test as a reminder of problems with all.destroy and a self referential
141
+ # references_many
142
+ #
143
+ # This test more complex than it needs to be to prove the point
144
+ # It also serves as a proof of a self referential references_many, but there are better places for that
145
+ # it "all.destroy should play nice with self referential references_many" do
146
+ # u1 = TwitterUser.new(:name => "u1")
147
+ # u2 = TwitterUser.new(:name => "u2")
148
+ # u3 = TwitterUser.new(:name => "u3")
149
+ #
150
+ # u1.followers << u2
151
+ # u1.followers << u3
152
+ # u3.leaders << u2
153
+ #
154
+ # u1f = u1.followers.map { |u| u.name }
155
+ # u1f.sort.should == ["u2", "u3"]
156
+ # u1.leaders.should be_empty
157
+ #
158
+ # u2.leaders.size.should == 1
159
+ # u2.leaders[0].name.should == "u1"
160
+ # u2.followers.size.should == 1
161
+ # u2.followers[0].name.should == "u3"
162
+ #
163
+ # u3l = u3.leaders.map { |u| u.name }
164
+ # u3l.sort.should == ["u1", "u2"]
165
+ # u3.followers.should be_empty
166
+ #
167
+ # TwitterUser.all.destroy!
168
+ # TwitterUser.all.should be_empty
169
+ # end
170
+
171
+ end
172
+
173
+ end
@@ -0,0 +1,364 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/spec_models.rb'
3
+
4
+ describe RelaxDB do
5
+
6
+ before(:each) do
7
+ setup_test_db
8
+ end
9
+
10
+ describe ".create_object" do
11
+
12
+ it "should return an instance of a known object if passed a hash with a class key" do
13
+ data = { "relaxdb_class" => "Item" }
14
+ obj = RelaxDB.create_object(data)
15
+ obj.should be_instance_of(Item)
16
+ end
17
+
18
+ it "should return an instance of a dynamically created object if no class key is provided" do
19
+ data = { "name" => "tesla coil", "strength" => 5000 }
20
+ obj = RelaxDB.create_object(data)
21
+ obj.name.should == "tesla coil"
22
+ obj.strength.should == 5000
23
+ end
24
+
25
+ end
26
+
27
+ # bulk_save and bulk_save! should match Document#save and Document#save! semantics
28
+ describe ".bulk_save" do
29
+
30
+ it "should be invokable multiple times" do
31
+ t1, t2 = Tag.new, Tag.new
32
+ RelaxDB.bulk_save(t1, t2)
33
+ RelaxDB.bulk_save(t1, t2)
34
+ end
35
+
36
+ it "should return the objects it was passed" do
37
+ t1, t2 = Tag.new, Tag.new
38
+ ta, tb = RelaxDB.bulk_save(t1, t2)
39
+ ta.should == t1
40
+ tb.should == t2
41
+ end
42
+
43
+ it "should return false on failure" do
44
+ c = Class.new(RelaxDB::Document) do
45
+ property :foo, :validator => lambda { false }
46
+ end
47
+ x = c.new
48
+ RelaxDB.bulk_save(x).should be_false
49
+ end
50
+
51
+ it "should not attempt to save if a pre-save stage fails" do
52
+ c = Class.new(RelaxDB::Document) do
53
+ property :foo, :validator => lambda { false }
54
+ end
55
+ x = c.new
56
+ RelaxDB.bulk_save(x)
57
+ x.should be_new_document
58
+ end
59
+
60
+ it "should invoke the after-save stage after a successful save" do
61
+ c = Class.new(RelaxDB::Document) do
62
+ attr_accessor :foo
63
+ after_save lambda { |c| c.foo = :bar }
64
+ end
65
+ x = c.new
66
+ RelaxDB.bulk_save(x).first.foo.should == :bar
67
+ end
68
+
69
+ it "should save non conflicting docs and mark conflicting docs" do
70
+ p1, p2 = Atom.new.save!, Atom.new.save!
71
+ p1.dup.save!
72
+ RelaxDB.bulk_save p1, p2
73
+ p1._rev.should =~ /1-/
74
+ p1.should be_update_conflict
75
+ p2._rev.should =~ /2-/
76
+ end
77
+
78
+ #
79
+ # This spec is as much a verification of my understanding of
80
+ # bulk_save semantics as it is a test of RelaxDB
81
+ #
82
+ # See http://mail-archives.apache.org/mod_mbox/couchdb-dev/200905.mbox/%3CF476A3D8-8F50-40A0-8668-C00D72196FBA@apache.org%3E
83
+ # for an explanation of the final section
84
+ #
85
+ describe "all-or-nothing" do
86
+ it "should save non conflicting and conflicting docs" do
87
+ p1, p2 = Primitives.new(:num => 1).save!, Primitives.new(:num => 2).save!
88
+ p1d = p1.dup
89
+ p1d.num = 11
90
+ p1d.save!
91
+ p1.num = 6
92
+ RelaxDB.bulk_save :all_or_nothing, p1, p2
93
+ p1._rev.should =~ /2-/
94
+ p2._rev.should =~ /2-/
95
+
96
+ p1 = RelaxDB.load p1._id, :conflicts => true
97
+ p1n1 = p1.num
98
+ p1 = RelaxDB.load p1._id, :rev => p1._conflicts[0]
99
+ p1n2 = p1.num
100
+ if p1n1 == 11
101
+ p1n2.should == 6
102
+ else
103
+ p1n1.should == 6 && p1n2.should == 11
104
+ end
105
+ end
106
+
107
+ #
108
+ # Test behind
109
+ # http://mail-archives.apache.org/mod_mbox/couchdb-dev/200905.mbox/%3CF476A3D8-8F50-40A0-8668-C00D72196FBA@apache.org%3E
110
+ # Effectively defunct
111
+ #
112
+ # it "non-deterministic winner" do
113
+ # p = Primitives.new(:num => 1).save!
114
+ # pd = p.dup
115
+ # p.num = 2
116
+ # p.save!
117
+ # pd.num = 3
118
+ # RelaxDB.bulk_save :all_or_nothing, pd
119
+ # RelaxDB.reload(p).num.should == 2
120
+ # end
121
+ end
122
+
123
+ end
124
+
125
+ describe ".bulk_save!" do
126
+
127
+ it "should succeed when passed no args" do
128
+ RelaxDB.bulk_save!
129
+ end
130
+
131
+ it "should raise when passed a nil value" do
132
+ lambda do
133
+ RelaxDB.bulk_save! *[nil]
134
+ end.should raise_error
135
+ end
136
+
137
+ it "should raise an exception if a obj fails validation" do
138
+ c = Class.new(RelaxDB::Document) do
139
+ property :foo, :validator => lambda { false }
140
+ end
141
+ lambda { RelaxDB.bulk_save!(c.new) }.should raise_error(RelaxDB::ValidationFailure)
142
+ end
143
+
144
+ it "should raise an exception on document conflict after all docs have been processed" do
145
+ p1, p2 = Atom.new.save!, Atom.new.save!
146
+ p1.dup.save!
147
+ lambda { RelaxDB.bulk_save!(p1, p2) }.should raise_error(RelaxDB::UpdateConflict)
148
+ p2._rev.should =~ /2-/
149
+ end
150
+
151
+ end
152
+
153
+ describe ".db_info" do
154
+ it "should return db info" do
155
+ RelaxDB.db_info.doc_count.should == 1
156
+ end
157
+ end
158
+
159
+ describe ".replicate_db" do
160
+
161
+ it "should replicate the named database" do
162
+ orig = "relaxdb_spec"
163
+ replica = "relaxdb_spec_replicate_test"
164
+ RelaxDB.delete_db replica rescue :ok
165
+ class ReplicaTest < RelaxDB::Document; end
166
+ ReplicaTest.new.save # implicitly saved to orig
167
+ RelaxDB.replicate_db orig, replica
168
+ RelaxDB.use_db replica
169
+ ReplicaTest.all.size.should == 1
170
+ end
171
+
172
+ end
173
+
174
+ describe ".load" do
175
+
176
+ it "should load a single document" do
177
+ a = Atom.new.save
178
+ ar = RelaxDB.load a._id
179
+ ar.should == a
180
+ end
181
+
182
+ it "should load an arbitrary number of documents" do
183
+ a1, a2 = Atom.new.save, Atom.new.save
184
+ ar1, ar2 = RelaxDB.load [a1._id, a2._id]
185
+ ar1.should == a1
186
+ ar2.should == a2
187
+ end
188
+
189
+ it "should return nil when given a id for a non existant doc" do
190
+ RelaxDB.load("nothere").should be_nil
191
+ end
192
+
193
+ it "should return an array with correctly placed nils when given a list containing non existant doc ids" do
194
+ a1, a2 = Atom.new.save, Atom.new.save
195
+ res = RelaxDB.load [nil, a1._id, nil, a2._id, nil]
196
+ res.should == [nil, a1, nil, a2, nil]
197
+ end
198
+
199
+ end
200
+
201
+ describe ".load!" do
202
+
203
+ it "should load a single document" do
204
+ a = Atom.new.save
205
+ ar = RelaxDB.load! a._id
206
+ ar.should == a
207
+ end
208
+
209
+ it "should load multiple documents" do
210
+ a1, a2 = Atom.new.save, Atom.new.save
211
+ ar1, ar2 = RelaxDB.load! [a1._id, a2._id]
212
+ ar1.should == a1
213
+ ar2.should == a2
214
+ end
215
+
216
+ it "should load multiple documents in order" do
217
+ ns = (0...100).map { rand(1_000_000_000).to_s }
218
+ objs = ns.map { |n| Primitives.new :_id => n }
219
+ RelaxDB.bulk_save! *objs
220
+ ns = ns.reverse
221
+ objs = RelaxDB.load! ns
222
+ 99.downto(0) do |i|
223
+ ns[i].should == objs[i]._id
224
+ end
225
+ end
226
+
227
+ it "should throw an exception if given a single id for a non-existant doc" do
228
+ lambda do
229
+ RelaxDB.load! "nothere"
230
+ end.should raise_error(RelaxDB::NotFound)
231
+ end
232
+
233
+ it "should throw an exception if any of a list of doc ids is for a non-existant doc" do
234
+ a = Atom.new.save
235
+ lambda do
236
+ RelaxDB.load! [nil, a._id]
237
+ end.should raise_error(RelaxDB::NotFound)
238
+ end
239
+
240
+ end
241
+
242
+ describe ".view" do
243
+
244
+ map_func = %Q<
245
+ function (doc) {
246
+ emit(doc._id, doc);
247
+ }
248
+ >
249
+
250
+ it "should request a view and return an array" do
251
+ RelaxDB::DesignDocument.get(RelaxDB.dd).add_view("simple", "map", map_func).save
252
+ data = RelaxDB.view("simple")
253
+ data.should == []
254
+ end
255
+
256
+ it "may accept query params" do
257
+ RelaxDB::DesignDocument.get(RelaxDB.dd).add_view("simple", "map", map_func).save
258
+ RelaxDB.db.put("x", {}.to_json)
259
+ RelaxDB.db.put("y", {}.to_json)
260
+ res = RelaxDB.view "simple", :key => "x"
261
+ res.first._id.should == "x"
262
+ end
263
+
264
+ it "should be queryable with a multi key post" do
265
+ Primitives.view_by :num
266
+
267
+ 5.times { |i| Primitives.new(:num => i).save }
268
+ Primitives.by_num
269
+ result = RelaxDB.view "Primitives_by_num", :keys => [0, 4], :reduce => false
270
+ result.map{ |p| p.num }.should == [0, 4]
271
+ end
272
+
273
+ it "should return nil for a reduce view with no results" do
274
+ Primitives.view_by :num
275
+ RelaxDB.view("Primitives_by_num", :reduce => true).should be_nil
276
+ end
277
+
278
+ it "should return a single value for a reduce view with a single result" do
279
+ Primitives.view_by :num
280
+ Primitives.new(:num => :x).save!
281
+ RelaxDB.view("Primitives_by_num", :reduce => true).should == 1
282
+ end
283
+
284
+ it "should return an array for a reduce view with multiple results" do
285
+ Primitives.view_by :num
286
+ 2.times { |i| Primitives.new(:num => i).save! }
287
+ res = RelaxDB.view("Primitives_by_num", :reduce => true, :group => true)
288
+ res.should be_an_instance_of(Array)
289
+ res.size.should == 2
290
+ end
291
+
292
+
293
+ end
294
+
295
+ describe ".merge" do
296
+
297
+ it "should merge rows sharing a common merge key into a single ViewObject" do
298
+ rows = [
299
+ {"value" => {"sculptor_id" => 1, "sculpture_name" => "strandbeesten"} },
300
+ {"value" => {"sculptor_id" => 1, "sculptor_name" => "hans"} },
301
+ {"value" => {"sculptor_id" => 2, "sculpture_name" => "parading dogs"} },
302
+ {"value" => {"sculptor_id" => 2, "sculptor_name" => "holmes"} }
303
+ ]
304
+ data = {"rows" => rows}
305
+ result = RelaxDB.merge(data, "sculptor_id")
306
+ result = result.sort { |a, b| a.sculptor_name <=> b.sculptor_name }
307
+
308
+ result[0].sculptor_name.should == "hans"
309
+ result[0].sculpture_name.should == "strandbeesten"
310
+ result[1].sculptor_name.should == "holmes"
311
+ result[1].sculpture_name.should == "parading dogs"
312
+ end
313
+
314
+ end
315
+
316
+ describe "create_views disabled" do
317
+
318
+ before(:each) do
319
+ create_test_db
320
+ RelaxDB.enable_view_creation false
321
+
322
+ class CvdBar < RelaxDB::Document
323
+ view_by :foo
324
+ has_one :foo1
325
+ has_many :foon
326
+ references_many :foor
327
+ end
328
+ end
329
+
330
+ it "should not create any views" do
331
+ dd = RelaxDB::DesignDocument.get "spec_doc"
332
+ dd.data["views"].should be_nil
333
+ end
334
+
335
+ end
336
+
337
+ describe "create_views enabled" do
338
+
339
+ before(:each) do
340
+ create_test_db
341
+
342
+ class CveBar < RelaxDB::Document
343
+ view_by :foo
344
+ has_one :foo1
345
+ has_many :foon
346
+ references_many :foor
347
+ end
348
+ end
349
+
350
+ it "should create all views" do
351
+ dd = RelaxDB::DesignDocument.get "spec_doc"
352
+ dd.data["views"]["CveBar_all"].should be
353
+ dd.data["views"]["CveBar_by_foo"].should be
354
+ dd.data["views"]["CveBar_foo1"].should be
355
+ dd.data["views"]["CveBar_foon"].should be
356
+ dd.data["views"]["CveBar_foor"].should be
357
+ end
358
+
359
+ end
360
+
361
+ # if caching is added
362
+ # it "should offer an example where behaviour is different with caching enabled and caching disabled"
363
+
364
+ end