cohitre-relaxdb 0.2.2
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.
- data/LICENSE +20 -0
- data/README.textile +164 -0
- data/Rakefile +52 -0
- data/docs/spec_results.html +604 -0
- data/lib/more/grapher.rb +48 -0
- data/lib/relaxdb.rb +38 -0
- data/lib/relaxdb/all_delegator.rb +48 -0
- data/lib/relaxdb/belongs_to_proxy.rb +29 -0
- data/lib/relaxdb/design_doc.rb +50 -0
- data/lib/relaxdb/document.rb +386 -0
- data/lib/relaxdb/extlib.rb +3 -0
- data/lib/relaxdb/has_many_proxy.rb +81 -0
- data/lib/relaxdb/has_one_proxy.rb +45 -0
- data/lib/relaxdb/paginate_params.rb +54 -0
- data/lib/relaxdb/paginator.rb +78 -0
- data/lib/relaxdb/query.rb +74 -0
- data/lib/relaxdb/references_many_proxy.rb +99 -0
- data/lib/relaxdb/relaxdb.rb +157 -0
- data/lib/relaxdb/server.rb +132 -0
- data/lib/relaxdb/sorted_by_view.rb +62 -0
- data/lib/relaxdb/uuid_generator.rb +21 -0
- data/lib/relaxdb/view_object.rb +34 -0
- data/lib/relaxdb/view_result.rb +18 -0
- data/lib/relaxdb/view_uploader.rb +47 -0
- data/lib/relaxdb/views.rb +42 -0
- data/spec/belongs_to_spec.rb +80 -0
- data/spec/callbacks_spec.rb +64 -0
- data/spec/denormalisation_spec.rb +49 -0
- data/spec/design_doc_spec.rb +34 -0
- data/spec/document_spec.rb +364 -0
- data/spec/has_many_spec.rb +147 -0
- data/spec/has_one_spec.rb +128 -0
- data/spec/query_spec.rb +80 -0
- data/spec/references_many_spec.rb +141 -0
- data/spec/relaxdb_spec.rb +137 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/spec_models.rb +130 -0
- data/spec/view_object_spec.rb +47 -0
- metadata +119 -0
@@ -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
|
data/spec/query_spec.rb
ADDED
@@ -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 == "_view/Zenith/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 count" do
|
33
|
+
q = RelaxDB::Query.new("", "")
|
34
|
+
q.startkey(["olympus"]).endkey(["vesuvius", 3600]).count(100)
|
35
|
+
q.view_path.should == "_view//?startkey=%5B%22olympus%22%5D&endkey=%5B%22vesuvius%22%2C3600%5D&count=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,141 @@
|
|
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 "is now deprecated and will be removed in the near future" do
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should preserve the relationships across the save / load boundary" do
|
21
|
+
p = Photo.new
|
22
|
+
t = Tag.new
|
23
|
+
t.photos << p
|
24
|
+
|
25
|
+
p = RelaxDB.load p._id
|
26
|
+
p.tags.size.should == 1
|
27
|
+
p.tags[0].should == t
|
28
|
+
|
29
|
+
t = RelaxDB.load t._id
|
30
|
+
t.photos.size.should == 1
|
31
|
+
t.photos[0].should == p
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#=" do
|
35
|
+
it "should not be invoked" do
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#<<" do
|
40
|
+
|
41
|
+
it "should set the relationship on both sides" do
|
42
|
+
p = Photo.new(:name => "photo")
|
43
|
+
t = Tag.new(:name => "tag")
|
44
|
+
p.tags << t
|
45
|
+
|
46
|
+
p.tags.size.should == 1
|
47
|
+
p.tags[0].name.should == "tag"
|
48
|
+
|
49
|
+
t.photos.size.should == 1
|
50
|
+
t.photos[0].name.should == "photo"
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not create duplicates when the same object is added more than once" do
|
54
|
+
p = Photo.new
|
55
|
+
t = Tag.new
|
56
|
+
p.tags << t << t
|
57
|
+
p.tags.size.should == 1
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not create duplicates when reciprocal objects are added from opposite sides" do
|
61
|
+
p = Photo.new
|
62
|
+
t = Tag.new
|
63
|
+
p.tags << t
|
64
|
+
t.photos << p
|
65
|
+
p.tags.size.should == 1
|
66
|
+
t.photos.size.should == 1
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#delete" do
|
72
|
+
|
73
|
+
it "should nullify relationship on both sides" do
|
74
|
+
p = Photo.new
|
75
|
+
t = Tag.new
|
76
|
+
p.tags << t
|
77
|
+
|
78
|
+
p.tags.delete(t)
|
79
|
+
p.tags.should be_empty
|
80
|
+
t.photos.should be_empty
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "owner#destroy" do
|
86
|
+
|
87
|
+
it "will not remove its membership from its peers in memory" do
|
88
|
+
# Documentating behaviour, not stating that this behaviour is desired
|
89
|
+
p = Photo.new
|
90
|
+
t = Tag.new
|
91
|
+
p.tags << t
|
92
|
+
|
93
|
+
p.destroy!
|
94
|
+
t.photos.size.should == 1
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should remove its membership from its peers in CouchDB" do
|
98
|
+
p = Photo.new
|
99
|
+
t = Tag.new
|
100
|
+
p.tags << t
|
101
|
+
|
102
|
+
p.destroy!
|
103
|
+
RelaxDB.load(t._id).photos.should be_empty
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
# Leaving this test as a reminder of problems with all.destroy and a self referential
|
109
|
+
# references_many until references_many is removed
|
110
|
+
#
|
111
|
+
# This test more complex than it needs to be to prove the point
|
112
|
+
# It also serves as a proof of a self referential references_many, but there are better places for that
|
113
|
+
# it "all.destroy should play nice with self referential references_many" do
|
114
|
+
# u1 = TwitterUser.new(:name => "u1")
|
115
|
+
# u2 = TwitterUser.new(:name => "u2")
|
116
|
+
# u3 = TwitterUser.new(:name => "u3")
|
117
|
+
#
|
118
|
+
# u1.followers << u2
|
119
|
+
# u1.followers << u3
|
120
|
+
# u3.leaders << u2
|
121
|
+
#
|
122
|
+
# u1f = u1.followers.map { |u| u.name }
|
123
|
+
# u1f.sort.should == ["u2", "u3"]
|
124
|
+
# u1.leaders.should be_empty
|
125
|
+
#
|
126
|
+
# u2.leaders.size.should == 1
|
127
|
+
# u2.leaders[0].name.should == "u1"
|
128
|
+
# u2.followers.size.should == 1
|
129
|
+
# u2.followers[0].name.should == "u3"
|
130
|
+
#
|
131
|
+
# u3l = u3.leaders.map { |u| u.name }
|
132
|
+
# u3l.sort.should == ["u1", "u2"]
|
133
|
+
# u3.followers.should be_empty
|
134
|
+
#
|
135
|
+
# TwitterUser.all.destroy!
|
136
|
+
# TwitterUser.all.should be_empty
|
137
|
+
# end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
require File.dirname(__FILE__) + '/spec_models.rb'
|
3
|
+
|
4
|
+
describe RelaxDB 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 ".create_object" do
|
16
|
+
|
17
|
+
it "should return an instance of a known object if passed a hash with a class key" do
|
18
|
+
data = { "class" => "Item" }
|
19
|
+
obj = RelaxDB.create_object(data)
|
20
|
+
obj.should be_instance_of(Item)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return an instance of a dynamically created object if no class key is provided" do
|
24
|
+
data = { "name" => "tesla coil", "strength" => 5000 }
|
25
|
+
obj = RelaxDB.create_object(data)
|
26
|
+
obj.name.should == "tesla coil"
|
27
|
+
obj.strength.should == 5000
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
describe ".bulk_save" do
|
33
|
+
|
34
|
+
it "should be invokable multiple times" do
|
35
|
+
t1 = Tag.new(:name => "t1")
|
36
|
+
t2 = Tag.new(:name => "t2")
|
37
|
+
RelaxDB.bulk_save(t1, t2)
|
38
|
+
RelaxDB.bulk_save(t1, t2)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should succeed when passed no args" do
|
42
|
+
RelaxDB.bulk_save
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ".replicate_db" do
|
48
|
+
|
49
|
+
it "should replicate the named database" do
|
50
|
+
orig = "relaxdb_spec_db"
|
51
|
+
replica = "relaxdb_spec_db_replica"
|
52
|
+
RelaxDB.delete_db replica rescue "ok"
|
53
|
+
Atom.new.save # implicitly saved to orig
|
54
|
+
RelaxDB.replicate_db orig, replica
|
55
|
+
RelaxDB.use_db replica
|
56
|
+
Atom.all.size.should == 1
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
describe ".load" do
|
62
|
+
|
63
|
+
it "should load a single document" do
|
64
|
+
a = Atom.new.save
|
65
|
+
ar = RelaxDB.load a._id
|
66
|
+
ar.should == a
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should load an arbitrary number of documents" do
|
70
|
+
a1, a2 = Atom.new.save, Atom.new.save
|
71
|
+
ar1, ar2 = RelaxDB.load a1._id, a2._id
|
72
|
+
ar1.should == a1
|
73
|
+
ar2.should == a2
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
describe ".view" do
|
79
|
+
|
80
|
+
map_func = %Q<
|
81
|
+
function (doc) {
|
82
|
+
emit(doc._id, doc);
|
83
|
+
}
|
84
|
+
>
|
85
|
+
|
86
|
+
it "should request a view and return a hash" do
|
87
|
+
RelaxDB::DesignDocument.get("viewtest").add_view("simple", "map", map_func).save
|
88
|
+
data = RelaxDB.view("viewtest", "simple")
|
89
|
+
data.should be_instance_of(Hash)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "may accept a block" do
|
93
|
+
RelaxDB::DesignDocument.get("viewtest").add_view("simple", "map", map_func).save
|
94
|
+
RelaxDB.db.put("x", {}.to_json)
|
95
|
+
RelaxDB.db.put("y", {}.to_json)
|
96
|
+
data = RelaxDB.view("viewtest", "simple") { |q| q.key("x") }
|
97
|
+
data["rows"].size.should == 1
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should be queryable with a multi key post" do
|
101
|
+
5.times { |i| Primitives.new(:num => i).save }
|
102
|
+
# Create the view
|
103
|
+
Primitives.all.sorted_by(:num)
|
104
|
+
resp = RelaxDB.view("Primitives", "all_sorted_by_num") do |q|
|
105
|
+
q.keys([0, 4])
|
106
|
+
q.reduce(false).group(true) # group invocation should hopefully be temporary
|
107
|
+
end
|
108
|
+
RelaxDB.instantiate(resp).map{ |p| p.num }.should == [0, 4]
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
describe ".merge" do
|
114
|
+
|
115
|
+
it "should merge rows sharing a common merge key into a single ViewObject" do
|
116
|
+
rows = [
|
117
|
+
{"value" => {"sculptor_id" => 1, "sculpture_name" => "strandbeesten"} },
|
118
|
+
{"value" => {"sculptor_id" => 1, "sculptor_name" => "hans"} },
|
119
|
+
{"value" => {"sculptor_id" => 2, "sculpture_name" => "parading dogs"} },
|
120
|
+
{"value" => {"sculptor_id" => 2, "sculptor_name" => "holmes"} }
|
121
|
+
]
|
122
|
+
data = {"rows" => rows}
|
123
|
+
result = RelaxDB.merge(data, "sculptor_id")
|
124
|
+
result = result.sort { |a, b| a.sculptor_name <=> b.sculptor_name }
|
125
|
+
|
126
|
+
result[0].sculptor_name.should == "hans"
|
127
|
+
result[0].sculpture_name.should == "strandbeesten"
|
128
|
+
result[1].sculptor_name.should == "holmes"
|
129
|
+
result[1].sculpture_name.should == "parading dogs"
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
# if caching is added
|
135
|
+
# it "should offer an example where behaviour is different with caching enabled and caching disabled"
|
136
|
+
|
137
|
+
end
|