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,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