ceritium-relaxdb 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/LICENSE +20 -0
  2. data/README.textile +185 -0
  3. data/Rakefile +58 -0
  4. data/docs/spec_results.html +604 -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 +40 -0
  8. data/lib/relaxdb/all_delegator.rb +68 -0
  9. data/lib/relaxdb/belongs_to_proxy.rb +29 -0
  10. data/lib/relaxdb/design_doc.rb +55 -0
  11. data/lib/relaxdb/document.rb +531 -0
  12. data/lib/relaxdb/extlib.rb +15 -0
  13. data/lib/relaxdb/has_many_proxy.rb +104 -0
  14. data/lib/relaxdb/has_one_proxy.rb +45 -0
  15. data/lib/relaxdb/paginate_params.rb +54 -0
  16. data/lib/relaxdb/paginator.rb +78 -0
  17. data/lib/relaxdb/query.rb +75 -0
  18. data/lib/relaxdb/references_many_proxy.rb +101 -0
  19. data/lib/relaxdb/relaxdb.rb +208 -0
  20. data/lib/relaxdb/server.rb +156 -0
  21. data/lib/relaxdb/sorted_by_view.rb +65 -0
  22. data/lib/relaxdb/uuid_generator.rb +21 -0
  23. data/lib/relaxdb/validators.rb +11 -0
  24. data/lib/relaxdb/view_object.rb +34 -0
  25. data/lib/relaxdb/view_result.rb +18 -0
  26. data/lib/relaxdb/view_uploader.rb +47 -0
  27. data/lib/relaxdb/views.rb +54 -0
  28. data/spec/belongs_to_spec.rb +129 -0
  29. data/spec/callbacks_spec.rb +80 -0
  30. data/spec/derived_properties_spec.rb +117 -0
  31. data/spec/design_doc_spec.rb +34 -0
  32. data/spec/document_spec.rb +556 -0
  33. data/spec/has_many_spec.rb +176 -0
  34. data/spec/has_one_spec.rb +128 -0
  35. data/spec/query_spec.rb +80 -0
  36. data/spec/references_many_spec.rb +178 -0
  37. data/spec/relaxdb_spec.rb +226 -0
  38. data/spec/spec.opts +1 -0
  39. data/spec/spec_helper.rb +10 -0
  40. data/spec/spec_models.rb +151 -0
  41. data/spec/view_object_spec.rb +47 -0
  42. metadata +123 -0
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/spec_models.rb'
3
+
4
+ describe RelaxDB::DesignDocument do
5
+
6
+ before(:all) do
7
+ RelaxDB.configure(:host => "localhost", :port => 5984)
8
+ end
9
+
10
+ before(:each) do
11
+ RelaxDB.delete_db "relaxdb_spec_db" rescue "ok"
12
+ RelaxDB.use_db "relaxdb_spec_db"
13
+ end
14
+
15
+ describe "#save" do
16
+
17
+ it "should create a corresponding document in CouchDB" do
18
+ RelaxDB::DesignDocument.get("foo").save
19
+ RelaxDB.load("_design/foo").should_not be_nil
20
+ end
21
+
22
+ end
23
+
24
+ describe "#destroy" do
25
+
26
+ it "should delete the corresponding document from CouchDB" do
27
+ dd = RelaxDB::DesignDocument.get("foo").save
28
+ dd.destroy!
29
+ RelaxDB.load("_design/foo").should be_nil
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,556 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/spec_models.rb'
3
+
4
+ describe RelaxDB::Document do
5
+
6
+ before(:all) do
7
+ RelaxDB.configure(:host => "localhost", :port => 5984)
8
+ end
9
+
10
+ before(:each) do
11
+ RelaxDB.delete_db "relaxdb_spec_db" rescue "ok"
12
+ RelaxDB.use_db "relaxdb_spec_db"
13
+ end
14
+
15
+ describe ".new" do
16
+
17
+ it "should create an object with an id" do
18
+ Atom.new._id.should_not be_nil
19
+ end
20
+
21
+ it "should create an object with a nil revision" do
22
+ Atom.new._rev.should be_nil
23
+ end
24
+
25
+ it "should convert attributes that end in _at to Times" do
26
+ now = Time.now
27
+ p = Post.new(:viewed_at => now).save
28
+ p = RelaxDB.load(p._id)
29
+ p.viewed_at.class.should == Time
30
+ p.viewed_at.should be_close(now, 1)
31
+ end
32
+
33
+ it "will silently ignore parameters that don't specify class attributes" do
34
+ # Consider this a feature or bug. It allows an object containing both request params
35
+ # and superflous data to be passed directly to a constructor.
36
+ Post.new(:foo => "").save
37
+ end
38
+
39
+ it "should create a document with a non conflicing state" do
40
+ Atom.new.should_not be_update_conflict
41
+ end
42
+
43
+ end
44
+
45
+ describe "#initialize" do
46
+
47
+ it "may be overridden by inheriting classes" do
48
+ i = Initiative.new(:x => "y").save
49
+ i = RelaxDB.load("y")
50
+ i.x.should == "y"
51
+ i.foo.should == :bar
52
+ end
53
+
54
+ end
55
+
56
+ describe "#to_json" do
57
+
58
+ it "should not output nil attributes" do
59
+ Atom.new.to_json.should_not include("rev")
60
+ end
61
+
62
+ it "should convert times to '%Y/%m/%d %H:%M:%S +0000' format" do
63
+ s = Time.at(0)
64
+ p = Primitives.new(:created_at => s).save
65
+ json = RelaxDB.get(p._id)
66
+ json["created_at"].should == "1970/01/01 00:00:00 +0000"
67
+ end
68
+
69
+ end
70
+
71
+ describe "#save" do
72
+
73
+ it "should set an object's revision" do
74
+ p = Atom.new.save
75
+ p._rev.should_not be_nil
76
+ end
77
+
78
+ it "should result in an object considered saved" do
79
+ Atom.new.save.should_not be_unsaved
80
+ end
81
+
82
+ it "should be invokable multiple times" do
83
+ p = Atom.new
84
+ p.save
85
+ p.save
86
+ end
87
+
88
+ it "should set created_at when first saved" do
89
+ now = Time.now
90
+ created_at = Post.new.save.created_at
91
+ now.should be_close(created_at, 1)
92
+ end
93
+
94
+ it "should set created_at when first saved unless supplied to the constructor" do
95
+ back_then = Time.now - 1000
96
+ p = Post.new(:created_at => back_then).save
97
+ p.created_at.should be_close(back_then, 1)
98
+ end
99
+
100
+ it "should set document conflict state on conflicting save" do
101
+ a1 = Atom.new
102
+ a2 = a1.dup
103
+ a1.save!
104
+ a2.save
105
+ a2.should be_update_conflict
106
+ end
107
+
108
+ end
109
+
110
+ describe "#save!" do
111
+
112
+ it "should save objects" do
113
+ a = Atom.new.save
114
+ RelaxDB.load(a._id).should == a
115
+ end
116
+
117
+ it "should raise ValidationFailure on validation failure" do
118
+ r = Class.new(RelaxDB::Document) do
119
+ property :thumbs_up, :validator => lambda { false }
120
+ end
121
+ lambda do
122
+ r.new.save!
123
+ end.should raise_error(RelaxDB::ValidationFailure)
124
+ end
125
+
126
+ it "should raise UpdateConflict on an update conflict" do
127
+ a1 = Atom.new
128
+ a2 = a1.dup
129
+ a1.save!
130
+ lambda { a2.save! }.should raise_error(RelaxDB::UpdateConflict)
131
+ end
132
+
133
+ end
134
+
135
+ describe "#save_all" do
136
+
137
+ before(:each) do
138
+ # Create the underlying views
139
+ User.new(:items => [], :invites_received => [], :invites_sent => [])
140
+ end
141
+
142
+ # it should issue a single POST
143
+ it "should issue no PUT requests" do
144
+ RelaxDB.db.put_count = 0
145
+ RelaxDB.db.get_count = 0
146
+
147
+ i1, i2 = Item.new(:name => "i1"), Item.new(:name => "i2")
148
+ u = User.new(:items => [i1, i2])
149
+ u.save_all!
150
+
151
+ RelaxDB.db.put_count.should == 0
152
+ RelaxDB.db.get_count.should == 3
153
+ end
154
+
155
+ end
156
+
157
+ describe "#all_children" do
158
+
159
+ it "should return an array containing all children" do
160
+ r = Rating.new
161
+ p = Photo.new(:rating => r)
162
+ t = Tag.new
163
+ t1, t2 = Tagging.new(:photo => p, :tag => t), Tagging.new(:photo => p, :tag => t)
164
+ p.taggings = [t1, t2]
165
+ p.all_children.size.should == 3
166
+ [r, t1, t2].each { |c| p.all_children.should include(c) }
167
+ end
168
+
169
+ end
170
+
171
+ describe "user defined property reader" do
172
+
173
+ it "should not effect normal operation" do
174
+ o = BespokeReader.new(:val => 101).save
175
+ o = RelaxDB.load o._id
176
+ o.val.should == 106
177
+ end
178
+
179
+ it "should not modify internal state" do
180
+ o = BespokeReader.new(:val => 101).save
181
+ o = RelaxDB.load o._id
182
+ o.instance_variable_get(:@val).should == 101
183
+ end
184
+
185
+ end
186
+
187
+ describe "user defined property writer" do
188
+
189
+ it "should not be used" do
190
+ o = BespokeWriter.new(:val => 101).save
191
+ o = RelaxDB.load o._id
192
+ o.val.should == 81
193
+ end
194
+
195
+ end
196
+
197
+ describe "loaded objects" do
198
+
199
+ it "should contain state as when saved" do
200
+ now = Time.now
201
+ p = Primitives.new(:str => "foo", :num => 19.30, :true_bool => true, :false_bool => false, :created_at => now).save
202
+ p = RelaxDB.load(p._id)
203
+ p.str.should == "foo"
204
+ p.num.should == 19.30
205
+ p.true_bool.should be_true
206
+ # p.false_bool.should be_false
207
+ p.false_bool.should_not be
208
+ p.created_at.should be_close(now, 1)
209
+ p.empty.should be_nil
210
+ end
211
+
212
+ it "should be saveable" do
213
+ a = Atom.new.save
214
+ a = RelaxDB.load(a._id)
215
+ a.save
216
+ end
217
+
218
+ end
219
+
220
+ describe "#destroy" do
221
+
222
+ it "should delete the corresponding document from CouchDB" do
223
+ p = Atom.new.save.destroy!
224
+ RelaxDB.load(p._id).should be_nil
225
+ end
226
+
227
+ it "should prevent the object from being resaved" do
228
+ p = Atom.new.save.destroy!
229
+ lambda { p.save! }.should raise_error
230
+ end
231
+
232
+ it "will result in undefined behaviour when invoked on unsaved objects" do
233
+ lambda { Atom.new.destroy! }.should raise_error
234
+ end
235
+
236
+ end
237
+
238
+ describe "#all.destroy!" do
239
+
240
+ it "should delete from CouchDB all documents of the corresponding class" do
241
+ Atom.new.save
242
+ Post.new.save
243
+ Post.new.save
244
+ Post.all.destroy!
245
+ Post.all.should be_empty
246
+ Atom.all.size.should == 1
247
+ end
248
+
249
+ end
250
+
251
+ describe "==" do
252
+
253
+ it "should define equality based on CouchDB id" do
254
+ i1 = Atom.new.save
255
+ i2 = Atom.new.save
256
+ i3 = RelaxDB.load(i1._id)
257
+ i1.should_not == i2
258
+ i1.should == i3
259
+ end
260
+
261
+ it "should return false when passed a nil object" do
262
+ (Atom.new == nil).should_not be_true
263
+ end
264
+
265
+ end
266
+
267
+ describe ".all" do
268
+
269
+ it "should return all instances of that class" do
270
+ Photo.new.save
271
+ Rating.new.save
272
+ Rating.new.save
273
+ Rating.all.size.should == 2
274
+ end
275
+
276
+ it "should return an empty array when no instances exist" do
277
+ Atom.all.should be_an_instance_of(Array)
278
+ Atom.all.should be_empty
279
+ end
280
+
281
+ end
282
+
283
+ describe ".all.size" do
284
+
285
+ it "should return the total number of docs" do
286
+ docs = []
287
+ 100.times { docs << Atom.new }
288
+ RelaxDB.bulk_save(*docs)
289
+ Atom.all.size.should == 100
290
+ end
291
+
292
+ it "should return 0 when no docs exist" do
293
+ Atom.all.size.should == 0
294
+ end
295
+
296
+ end
297
+
298
+ describe ".all.sorted_by" do
299
+
300
+ it "should sort ascending by default" do
301
+ Post.new(:content => "b").save
302
+ Post.new(:content => "a").save
303
+ posts = Post.all.sorted_by(:content)
304
+ posts[0].content.should == "a"
305
+ posts[1].content.should == "b"
306
+ end
307
+
308
+ it "should sort desc when specified" do
309
+ Post.new(:content => "a").save
310
+ Post.new(:content => "b").save
311
+ posts = Post.all.sorted_by(:content) { |q| q.descending(true) }
312
+ posts[0].content.should == "b"
313
+ posts[1].content.should == "a"
314
+ end
315
+
316
+ it "should sort date attributes lexicographically" do
317
+ t = Time.new
318
+ Post.new(:viewed_at => t+1000, :content => "late").save
319
+ Post.new(:viewed_at => t, :content => "early").save
320
+ posts = Post.all.sorted_by(:viewed_at)
321
+ posts[0].content.should == "early"
322
+ posts[1].content.should == "late"
323
+ end
324
+
325
+ it "should return the count when queried with reduce=true" do
326
+ docs = []
327
+ 100.times { |i| docs << Primitives.new(:num => i) }
328
+ RelaxDB.bulk_save(*docs)
329
+ # Create the view
330
+ Primitives.all.sorted_by(:num)
331
+ res = RelaxDB.view("Primitives", "all_sorted_by_num") { |q| q.reduce(true) }
332
+ count = RelaxDB.reduce_result(res)
333
+ count.should == 100
334
+ end
335
+
336
+ describe "results" do
337
+
338
+ it "should be an empty array when no docs match" do
339
+ Post.all.sorted_by(:subject).should == []
340
+ end
341
+
342
+ it "should be retrievable by exact criteria" do
343
+ Post.new(:subject => "foo").save
344
+ Post.new(:subject => "foo").save
345
+ Post.new(:subject => "bar").save
346
+ Post.all.sorted_by(:subject) { |q| q.key("foo") }.size.should == 2
347
+ end
348
+
349
+ it "should be retrievable by relative criteria" do
350
+ Rating.new(:stars => 1).save
351
+ Rating.new(:stars => 5).save
352
+ Rating.all.sorted_by(:stars) { |q| q.endkey(3) }.size.should == 1
353
+ end
354
+
355
+ it "should be retrievable by combined criteria" do
356
+ User.new(:name => "paul", :age => 28).save
357
+ User.new(:name => "paul", :age => 72).save
358
+ User.new(:name => "atlas", :age => 99).save
359
+ User.all.sorted_by(:name, :age) { |q| q.startkey(["paul",0 ]).endkey(["paul", 50]) }.size.should == 1
360
+ end
361
+
362
+ it "should be retrievable by combined criteria where not all docs contain all attributes" do
363
+ User.new(:name => "paul", :age => 28).save
364
+ User.new(:name => "paul", :age => 72).save
365
+ User.new(:name => "atlas").save
366
+ User.all.sorted_by(:name, :age) { |q| q.startkey(["paul",0 ]).endkey(["paul", 50]) }.size.should == 1
367
+ end
368
+
369
+ it "should be retrievable by a multi key post" do
370
+ 5.times { |i| Primitives.new(:num => i).save }
371
+ ps = Primitives.all.sorted_by(:num) { |q| q.keys([0, 4]) }
372
+ ps.map { |p| p.num }.should == [0, 4]
373
+ end
374
+
375
+ end
376
+
377
+ end
378
+
379
+ describe "defaults" do
380
+
381
+ it "should be set on initialisation" do
382
+ r = Rating.new
383
+ r.stars.should == 5
384
+ end
385
+
386
+ it "should be saved" do
387
+ r = Rating.new.save
388
+ RelaxDB.load(r._id).stars.should == 5
389
+ end
390
+
391
+ it "should be ignored once overwritten" do
392
+ r = Rating.new
393
+ r.stars = nil
394
+ r.save
395
+ RelaxDB.load(r._id).stars.should be_nil
396
+ end
397
+
398
+ it "may be a simple value" do
399
+ simple = Class.new(RelaxDB::Document) do
400
+ property :foo, :default => :bar
401
+ end
402
+ simple.new.foo.should == :bar
403
+ end
404
+
405
+ it "may be a proc" do
406
+ simple = Class.new(RelaxDB::Document) do
407
+ property :foo, :default => lambda { :bar }
408
+ end
409
+ simple.new.foo.should == :bar
410
+ end
411
+
412
+ end
413
+
414
+ describe "validator" do
415
+
416
+ it "should prevent an object from being saved if it evaluates to false" do
417
+ r = Class.new(RelaxDB::Document) do
418
+ property :thumbs_up, :validator => lambda { false }
419
+ end
420
+ r.new.save.should be_false
421
+ end
422
+
423
+ it "should prevent an object from being saved if it throws an exception" do
424
+ r = Class.new(RelaxDB::Document) do
425
+ property :thumbs_up, :validator => lambda { raise }
426
+ end
427
+ r.new.save.should be_false
428
+ end
429
+
430
+ it "should pass the property value to the validator" do
431
+ r = Class.new(RelaxDB::Document) do
432
+ property :thumbs_up, :validator => lambda { |tu| tu >=0 && tu < 3 }
433
+ end
434
+ r.new(:thumbs_up => 2).save.should be
435
+ r.new(:thumbs_up => 3).save.should be_false
436
+ end
437
+
438
+ it "should pass the property value and object to the validator" do
439
+ r = Class.new(RelaxDB::Document) do
440
+ property :thumbs_up, :validator => lambda { |tu, o| tu >=0 && o.thumbs_up < 3 }
441
+ end
442
+ r.new(:thumbs_up => 2).save.should be
443
+ r.new(:thumbs_up => 3).save.should be_false
444
+ end
445
+
446
+ it "should perform all validations" do
447
+ r = Class.new(RelaxDB::Document) do
448
+ property :foo, :validator => lambda { raise }, :validation_msg => "oof"
449
+ property :bar, :validator => lambda { raise }, :validation_msg => "rab"
450
+ end
451
+ x = r.new
452
+ x.save
453
+ x.errors[:foo].should == "oof"
454
+ x.errors[:bar].should == "rab"
455
+ end
456
+
457
+ it "should prevent saving unless all validations pass" do
458
+ r = Class.new(RelaxDB::Document) do
459
+ property :foo, :validator => lambda { false }
460
+ property :bar, :validator => lambda { true }
461
+ end
462
+ x = r.new
463
+ x.save.should == false
464
+ end
465
+
466
+ it "should add a default error message if none is specified" do
467
+ r = Class.new(RelaxDB::Document) do
468
+ property :foo, :validator => lambda { raise }
469
+ end
470
+ x = r.new
471
+ x.save
472
+ x.errors[:foo].should_not be_blank
473
+ end
474
+
475
+ it "may be a proc" do
476
+ r = Class.new(RelaxDB::Document) do
477
+ property :thumbs_up, :validator => lambda { false }
478
+ end
479
+ r.new.save.should be_false
480
+ end
481
+
482
+ it "may be a method" do
483
+ r = Class.new(RelaxDB::Document) do
484
+ property :thumbs_up, :validator => :count_em
485
+ def count_em(tu)
486
+ tu >=0 && tu < 3
487
+ end
488
+ end
489
+ r.new(:thumbs_up => 1).save.should be
490
+ end
491
+
492
+ it "may be skipped by passing the property symbol to save" do
493
+ r = Class.new(RelaxDB::Document) do
494
+ property :thumbs_up, :validator => lambda { raise }
495
+ end
496
+ x = r.new
497
+ x.validation_skip_list << :thumbs_up
498
+ x.save!
499
+ end
500
+
501
+ end
502
+
503
+ describe "validation message" do
504
+
505
+ it "should be set on failure" do
506
+ r = Class.new(RelaxDB::Document) do
507
+ property :thumbs_up, :validator => lambda { false }, :validation_msg => "Too many thumbs"
508
+ end
509
+ x = r.new
510
+ x.save
511
+ x.errors[:thumbs_up].should == "Too many thumbs"
512
+ end
513
+
514
+ it "may be a proc accepting the prop only" do
515
+ r = Class.new(RelaxDB::Document) do
516
+ property :thumbs_up, :validator => lambda { false },
517
+ :validation_msg => lambda { |tu| "#{tu}" }
518
+ end
519
+ x = r.new(:thumbs_up => 13)
520
+ x.save
521
+ x.errors[:thumbs_up].should == "13"
522
+ end
523
+
524
+
525
+ it "may be a proc accepting the prop and object" do
526
+ r = Class.new(RelaxDB::Document) do
527
+ property :thumbs_up, :validator => lambda { false },
528
+ :validation_msg => lambda { |tu, o| "#{tu} #{o.thumbs_up}" }
529
+ end
530
+ x = r.new(:thumbs_up => 13)
531
+ x.save
532
+ x.errors[:thumbs_up].should == "13 13"
533
+ end
534
+
535
+ end
536
+
537
+ describe "predefined validator" do
538
+
539
+ it "should be invoked when a symbol clash exists" do
540
+ c = Class.new(RelaxDB::Document) do
541
+ property :foo, :validator => :required
542
+ def required; raise; end;
543
+ end
544
+ c.new(:foo => :bar).save!.should be
545
+ end
546
+
547
+ it "should prevent an object from being saved if validation fails" do
548
+ c = Class.new(RelaxDB::Document) do
549
+ property :foo, :validator => :required
550
+ end
551
+ c.new.save.should be_false
552
+ end
553
+
554
+ end
555
+
556
+ end