relaxdb 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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