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