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,176 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/spec_models.rb'
3
+
4
+ describe RelaxDB::HasManyProxy 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 "has_many" do
16
+
17
+ it "should be considered enumerable" do
18
+ u = User.new.save
19
+ u.items.should be_a_kind_of(Enumerable)
20
+ end
21
+
22
+ it "should actually be enumerable" do
23
+ u = User.new.save
24
+ u.items << Item.new(:name => "a")
25
+ u.items << Item.new(:name => "b")
26
+ names = u.items.inject("") { |memo, i| memo << i.name }
27
+ names.should == "ab"
28
+ end
29
+
30
+ it "should preserve the collection across the load / save boundary" do
31
+ u = User.new.save
32
+ u.items << Item.new
33
+ u = RelaxDB.load u._id
34
+ u.items.size.should == 1
35
+ end
36
+
37
+ it "should work with MultiWordClassNames" do
38
+ c = MultiWordChild.new
39
+ m = MultiWordClass.new.save
40
+ m.multi_word_children << c
41
+ m = RelaxDB.load m._id
42
+ m.multi_word_children[0].should == c
43
+ end
44
+
45
+ describe "#<<" do
46
+
47
+ it "should link the added item to the parent" do
48
+ u = User.new
49
+ u.items << Item.new
50
+ u.items[0].user.should == u
51
+ end
52
+
53
+ it "should return self" do
54
+ u = User.new.save
55
+ u.items << Item.new << Item.new
56
+ u.items[0].user.should == u
57
+ u.items[1].user.should == u
58
+ end
59
+
60
+ it "should not created duplicates when invoked with same object more than once" do
61
+ u = User.new.save
62
+ i = Item.new
63
+ u.items << i << i
64
+ u.items.size.should == 1
65
+ end
66
+
67
+ it "should return false when the child fails validation" do
68
+ d = Dysfunctional.new
69
+ r = (d.failures << Failure.new)
70
+ r.should be_false
71
+ d.failures.should be_empty
72
+ end
73
+
74
+ end
75
+
76
+ describe "#=" do
77
+
78
+ before(:each) do
79
+ # Create the underlying views
80
+ User.new(:items => [], :invites_received => [], :invites_sent => [])
81
+ end
82
+
83
+ it "should not attempt to save the child objects when the relationship is established" do
84
+ RelaxDB.db.put_count = 0
85
+ i1, i2 = Item.new(:name => "i1"), Item.new(:name => "i2")
86
+ User.new(:items => [i1, i2])
87
+ RelaxDB.db.put_count.should == 0
88
+ end
89
+
90
+ it "should preserve given relationships across save/load boundary" do
91
+ i1, i2 = Item.new(:name => "i1"), Item.new(:name => "i2")
92
+ u = User.new(:items => [i1, i2])
93
+ u.save_all!
94
+ u = RelaxDB.load u._id
95
+ u.items.map { |i| i.name }.sort.join.should == "i1i2"
96
+ end
97
+
98
+ it "should invoke the derived properties writer" do
99
+ P = Class.new(RelaxDB::Document) do
100
+ property :foo, :derived => [:zongs, lambda {|f, o| o.zongs.first.z / 2 }]
101
+ has_many :zongs, :class => "Zong"
102
+ end
103
+ Zong = Class.new(RelaxDB::Document) do
104
+ property :z
105
+ belongs_to :p
106
+ end
107
+ oz = Zong.new(:z => 10)
108
+ op = P.new(:zongs => [oz])
109
+ op.foo.should == 5
110
+ end
111
+
112
+ end
113
+
114
+ describe "#delete" do
115
+
116
+ it "should nullify the belongs_to relationship" do
117
+ u = User.new.save
118
+ i = Item.new
119
+ u.items << i
120
+ u.items.delete i
121
+ i.user.should be_nil
122
+ RelaxDB.load(i._id).user.should be_nil
123
+ end
124
+
125
+ end
126
+
127
+ describe "#clear" do
128
+
129
+ it "should result in an empty collection" do
130
+ u = User.new.save
131
+ u.items << Item.new << Item.new
132
+ u.items.clear
133
+ u.items.should be_empty
134
+ end
135
+
136
+ it "should nullify all child relationships" do
137
+ u = User.new.save
138
+ i1, i2 = Item.new, Item.new
139
+ u.items << i1
140
+ u.items << i2
141
+ u.items.clear
142
+
143
+ i1.user.should be_nil
144
+ i2.user.should be_nil
145
+ RelaxDB.load(i1._id).user.should be_nil
146
+ RelaxDB.load(i2._id).user.should be_nil
147
+ end
148
+
149
+ end
150
+
151
+ describe "owner" do
152
+
153
+ it "should be able to form multiple relationships with the same class of child" do
154
+ u1, u2 = User.new.save, User.new.save
155
+ i = Invite.new(:recipient => u2)
156
+ u1.invites_sent << Invite.new
157
+ RelaxDB.load(u1._id).invites_sent[0] == i
158
+ RelaxDB.load(u2._id).invites_received[0] == i
159
+ end
160
+
161
+ describe "#destroy" do
162
+
163
+ it "should nullify its child relationships" do
164
+ u = User.new.save
165
+ u.items << Item.new << Item.new
166
+ u.destroy!
167
+ Item.all.sorted_by(:user_id) { |q| q.key(u._id) }.should be_empty
168
+ end
169
+
170
+ end
171
+
172
+ end
173
+
174
+ end
175
+
176
+ end
@@ -0,0 +1,128 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/spec_models.rb'
3
+
4
+ describe RelaxDB::HasOneProxy 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 "has_one" do
16
+
17
+ it "should return nil when accessed before assignment" do
18
+ p = Photo.new
19
+ p.rating.should == nil
20
+ end
21
+
22
+ it "should be establishable via a constructor attribute" do
23
+ r = Rating.new
24
+ p = Photo.new :rating => r
25
+ p.rating.should == r
26
+ end
27
+
28
+ it "should be establishable via assignment" do
29
+ p = Photo.new
30
+ r = Rating.new
31
+ p.rating = r
32
+ p.rating.should == r
33
+ end
34
+
35
+ it "should return the same object on repeated invocations" do
36
+ p = Photo.new.save
37
+ p.rating = Rating.new
38
+ p = RelaxDB.load(p._id)
39
+ p.rating.object_id.should == p.rating.object_id
40
+ end
41
+
42
+ it "should be preserved across load / save boundary" do
43
+ r = Rating.new
44
+ p = Photo.new(:rating => r).save
45
+ p = RelaxDB.load p._id
46
+ p.rating.should == r
47
+ end
48
+
49
+ it "should be able reference itself via its child" do
50
+ r = Rating.new
51
+ p = Photo.new(:rating => r).save
52
+ p = RelaxDB.load p._id
53
+ p.rating.photo.should == p
54
+ end
55
+
56
+ it "should work with MultiWordClassNames" do
57
+ c = MultiWordChild.new
58
+ m = MultiWordClass.new(:multi_word_child => c).save
59
+ m = RelaxDB.load m._id
60
+ m.multi_word_child.should == c
61
+ end
62
+
63
+ describe "#=" do
64
+
65
+ it "should create a reference from the child to the parent" do
66
+ p = Photo.new
67
+ r = Rating.new
68
+ p.rating = r
69
+ r.photo.should == p
70
+ end
71
+
72
+ it "should save the assigned object" do
73
+ p = Photo.new
74
+ r = Rating.new
75
+ p.rating = r
76
+ r.should_not be_unsaved
77
+ end
78
+
79
+ it "will not save the parent" do
80
+ p = Photo.new
81
+ r = Rating.new
82
+ p.rating = r
83
+ p.should be_unsaved
84
+ end
85
+
86
+ it "should set the target to nil when nil is assigned" do
87
+ p = Photo.new
88
+ p.rating = nil
89
+ p.rating.should be_nil
90
+ end
91
+
92
+ it "should nullify any existing relationship in the database" do
93
+ p = Photo.new
94
+ r = Rating.new
95
+ p.rating = r
96
+ p.rating = nil
97
+ RelaxDB.load(r._id).photo.should be_nil
98
+ end
99
+
100
+ it "should nullify any existing relationship on a known in-memory object" do
101
+ p = Photo.new
102
+ r = Rating.new
103
+ p.rating = r
104
+ p.rating = nil
105
+ r.photo.should be_nil
106
+ end
107
+
108
+ it "will not nullify any existing relationship on unknown in-memory objects" do
109
+ p = Photo.new.save
110
+ r = Rating.new
111
+ p.rating = r
112
+ r_copy = RelaxDB.load(r._id)
113
+ p.rating = nil
114
+ r_copy.photo.should_not be_nil
115
+ end
116
+
117
+ it "will not throw an error when the rhs fails validation" do
118
+ d = Dysfunctional.new.save
119
+ f = Failure.new
120
+ d.failure = f
121
+ d.failure.should == f
122
+ end
123
+
124
+ end
125
+
126
+ end
127
+
128
+ end
@@ -0,0 +1,80 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/spec_models.rb'
3
+
4
+ describe RelaxDB::Query do
5
+
6
+ describe "#view_name" do
7
+
8
+ it "should match a single key attribute" do
9
+ q = RelaxDB::SortedByView.new("", :foo)
10
+ q.view_name.should == "all_sorted_by_foo"
11
+ end
12
+
13
+ it "should match key attributes" do
14
+ q = RelaxDB::SortedByView.new("", :foo, :bar)
15
+ q.view_name.should == "all_sorted_by_foo_and_bar"
16
+ end
17
+ end
18
+
19
+ describe "#view_path" do
20
+
21
+ it "should list design document and view name" do
22
+ q = RelaxDB::Query.new("Zenith", "mount")
23
+ q.view_path.should == "_design/Zenith/_view/mount"
24
+ end
25
+
26
+ it "should contain URL and JSON encoded key when the key has been set" do
27
+ q = RelaxDB::Query.new("", "")
28
+ q.key("olympus")
29
+ q.view_path.should == "_view//?key=%22olympus%22"
30
+ end
31
+
32
+ it "should honour startkey, endkey and limit" do
33
+ q = RelaxDB::Query.new("", "")
34
+ q.startkey(["olympus"]).endkey(["vesuvius", 3600]).limit(100)
35
+ q.view_path.should == "_view//?startkey=%5B%22olympus%22%5D&endkey=%5B%22vesuvius%22%2C3600%5D&limit=100"
36
+ end
37
+
38
+ it "should specify a null key if key was set to nil" do
39
+ q = RelaxDB::Query.new("", "")
40
+ q.key(nil)
41
+ q.view_path.should == "_view//?key=null"
42
+ end
43
+
44
+ it "should specify a null startkey if startkey was set to nil" do
45
+ q = RelaxDB::Query.new("", "")
46
+ q.startkey(nil)
47
+ q.view_path.should == "_view//?startkey=null"
48
+ end
49
+
50
+ it "should specify a null endkey if endkey was set to nil" do
51
+ q = RelaxDB::Query.new("", "")
52
+ q.endkey(nil)
53
+ q.view_path.should == "_view//?endkey=null"
54
+ end
55
+
56
+ it "should not JSON encode the startkey_docid" do
57
+ q = RelaxDB::Query.new("", "")
58
+ q.startkey_docid("foo")
59
+ q.view_path.should == "_view//?startkey_docid=foo"
60
+ end
61
+
62
+ it "should not JSON encode the endkey_docid" do
63
+ q = RelaxDB::Query.new("", "")
64
+ q.endkey_docid("foo")
65
+ q.view_path.should == "_view//?endkey_docid=foo"
66
+ end
67
+
68
+ end
69
+
70
+ describe "#keys" do
71
+
72
+ it "should return a JSON encoded hash" do
73
+ q = RelaxDB::Query.new("", "")
74
+ q.keys(["a", "b"])
75
+ q.keys.should == '{"keys":["a","b"]}'
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,178 @@
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
+ 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 "references_many" do
16
+
17
+ it "should preserve the relationships across the save / load boundary" do
18
+ p = Photo.new
19
+ t = Tag.new
20
+ t.photos << p
21
+
22
+ p = RelaxDB.load p._id
23
+ p.tags.size.should == 1
24
+ p.tags[0].should == t
25
+
26
+ t = RelaxDB.load t._id
27
+ t.photos.size.should == 1
28
+ t.photos[0].should == p
29
+ end
30
+
31
+ it "should issue only a single request to resolve the relationship" do
32
+ p, t = Photo.new, Tag.new
33
+ p.tags << t
34
+
35
+ # Create the views
36
+ p.tags.map { |t| t._id }
37
+
38
+ p = RelaxDB.load p._id
39
+ RelaxDB.db.get_count = 0
40
+ p.tags.map { |t| t._id }
41
+ p.tags.map { |t| t._id }
42
+ RelaxDB.db.get_count.should == 1
43
+ end
44
+
45
+ it "should not resolve the relationship when an object is instantiated" do
46
+ p, t = Photo.new, Tag.new
47
+ p.tags << t
48
+
49
+ RelaxDB.db.get_count = 0
50
+ p = RelaxDB.load p._id
51
+ RelaxDB.db.get_count.should == 1
52
+ end
53
+
54
+ it "should make the ids available as a property" do
55
+ p, t = Photo.new, Tag.new
56
+ p.tags << t
57
+
58
+ p.tags_ids.should == [t._id]
59
+ end
60
+
61
+ describe "#=" do
62
+ it "should not be invoked" do
63
+ end
64
+ end
65
+
66
+ describe "#<<" do
67
+
68
+ it "should set the relationship on both sides" do
69
+ p = Photo.new(:name => "photo")
70
+ t = Tag.new(:name => "tag")
71
+ p.tags << t
72
+
73
+ p.tags.size.should == 1
74
+ p.tags[0].name.should == "tag"
75
+
76
+ t.photos.size.should == 1
77
+ t.photos[0].name.should == "photo"
78
+ end
79
+
80
+ it "should not create duplicates when the same object is added more than once" do
81
+ p = Photo.new
82
+ t = Tag.new
83
+ p.tags << t << t
84
+ p.tags.size.should == 1
85
+ end
86
+
87
+ it "should not create duplicates when reciprocal objects are added from opposite sides" do
88
+ p = Photo.new
89
+ t = Tag.new
90
+ p.tags << t
91
+ t.photos << p
92
+ p.tags.size.should == 1
93
+ t.photos.size.should == 1
94
+ end
95
+
96
+ it "will resolve the reciprocal relationship" do
97
+ # Create the views
98
+ p, t = Photo.new, Tag.new
99
+ p.tags << t
100
+
101
+ p, t = Photo.new, Tag.new
102
+ RelaxDB.db.get_count = 0
103
+ p.tags << t
104
+ RelaxDB.db.get_count.should == 2
105
+ end
106
+
107
+ end
108
+
109
+ describe "#delete" do
110
+
111
+ it "should nullify relationship on both sides" do
112
+ p = Photo.new
113
+ t = Tag.new
114
+ p.tags << t
115
+
116
+ p.tags.delete(t)
117
+ p.tags.should be_empty
118
+ t.photos.should be_empty
119
+ end
120
+
121
+ end
122
+
123
+ describe "owner#destroy" do
124
+
125
+ it "should remove its membership from its peers in memory" do
126
+ p = Photo.new
127
+ t = Tag.new
128
+ p.tags << t
129
+
130
+ p.destroy!
131
+ t.photos.size.should == 0
132
+ end
133
+
134
+ it "should remove its membership from its peers in CouchDB" do
135
+ p = Photo.new
136
+ t = Tag.new
137
+ p.tags << t
138
+
139
+ p.destroy!
140
+ RelaxDB.load(t._id).photos.should be_empty
141
+ end
142
+
143
+ end
144
+
145
+ # Leaving this test as a reminder of problems with all.destroy and a self referential
146
+ # references_many
147
+ #
148
+ # This test more complex than it needs to be to prove the point
149
+ # It also serves as a proof of a self referential references_many, but there are better places for that
150
+ # it "all.destroy should play nice with self referential references_many" do
151
+ # u1 = TwitterUser.new(:name => "u1")
152
+ # u2 = TwitterUser.new(:name => "u2")
153
+ # u3 = TwitterUser.new(:name => "u3")
154
+ #
155
+ # u1.followers << u2
156
+ # u1.followers << u3
157
+ # u3.leaders << u2
158
+ #
159
+ # u1f = u1.followers.map { |u| u.name }
160
+ # u1f.sort.should == ["u2", "u3"]
161
+ # u1.leaders.should be_empty
162
+ #
163
+ # u2.leaders.size.should == 1
164
+ # u2.leaders[0].name.should == "u1"
165
+ # u2.followers.size.should == 1
166
+ # u2.followers[0].name.should == "u3"
167
+ #
168
+ # u3l = u3.leaders.map { |u| u.name }
169
+ # u3l.sort.should == ["u1", "u2"]
170
+ # u3.followers.should be_empty
171
+ #
172
+ # TwitterUser.all.destroy!
173
+ # TwitterUser.all.should be_empty
174
+ # end
175
+
176
+ end
177
+
178
+ end