couchobject 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/History.txt +10 -0
  2. data/Manifest.txt +30 -6
  3. data/README.txt +580 -42
  4. data/TODO +2 -2
  5. data/config/hoe.rb +1 -1
  6. data/lib/couch_object.rb +7 -2
  7. data/lib/couch_object/database.rb +19 -34
  8. data/lib/couch_object/document.rb +13 -6
  9. data/lib/couch_object/error_classes.rb +110 -0
  10. data/lib/couch_object/persistable.rb +954 -36
  11. data/lib/couch_object/persistable/has_many_relations_array.rb +91 -0
  12. data/lib/couch_object/persistable/meta_classes.rb +568 -0
  13. data/lib/couch_object/persistable/overloaded_methods.rb +209 -0
  14. data/lib/couch_object/server.rb +1 -1
  15. data/lib/couch_object/utils.rb +44 -0
  16. data/lib/couch_object/version.rb +1 -1
  17. data/lib/couch_object/view.rb +129 -6
  18. data/script/console +0 -0
  19. data/script/destroy +0 -0
  20. data/script/generate +0 -0
  21. data/script/txt2html +0 -0
  22. data/spec/database_spec.rb +23 -31
  23. data/spec/database_spec.rb.orig +173 -0
  24. data/spec/document_spec.rb +21 -3
  25. data/spec/integration/database_integration_spec.rb +46 -15
  26. data/spec/integration/integration_helper.rb +3 -3
  27. data/spec/persistable/callback.rb +44 -0
  28. data/spec/persistable/callback_spec.rb +44 -0
  29. data/spec/persistable/cloning.rb +77 -0
  30. data/spec/persistable/cloning_spec.rb +77 -0
  31. data/spec/persistable/comparing_objects.rb +350 -0
  32. data/spec/persistable/comparing_objects_spec.rb +350 -0
  33. data/spec/persistable/deleting.rb +113 -0
  34. data/spec/persistable/deleting_spec.rb +113 -0
  35. data/spec/persistable/error_messages.rb +32 -0
  36. data/spec/persistable/error_messages_spec.rb +32 -0
  37. data/spec/persistable/loading.rb +339 -0
  38. data/spec/persistable/loading_spec.rb +339 -0
  39. data/spec/persistable/new_methods.rb +70 -0
  40. data/spec/persistable/new_methods_spec.rb +70 -0
  41. data/spec/persistable/persistable_helper.rb +194 -0
  42. data/spec/persistable/relations.rb +470 -0
  43. data/spec/persistable/relations_spec.rb +470 -0
  44. data/spec/persistable/saving.rb +137 -0
  45. data/spec/persistable/saving_spec.rb +137 -0
  46. data/spec/persistable/setting_storage_location.rb +65 -0
  47. data/spec/persistable/setting_storage_location_spec.rb +65 -0
  48. data/spec/persistable/timestamps.rb +76 -0
  49. data/spec/persistable/timestamps_spec.rb +76 -0
  50. data/spec/persistable/unsaved_changes.rb +211 -0
  51. data/spec/persistable/unsaved_changes_spec.rb +211 -0
  52. data/spec/server_spec.rb +5 -5
  53. data/spec/utils_spec.rb +60 -0
  54. data/spec/view_spec.rb +40 -7
  55. data/website/index.html +22 -7
  56. data/website/index.txt +13 -5
  57. metadata +93 -61
  58. data/bin/couch_ruby_view_requestor +0 -81
  59. data/lib/couch_object/model.rb +0 -5
  60. data/lib/couch_object/proc_condition.rb +0 -14
  61. data/spec/model_spec.rb +0 -5
  62. data/spec/persistable_spec.rb +0 -91
  63. data/spec/proc_condition_spec.rb +0 -26
@@ -0,0 +1,339 @@
1
+ require File.dirname(__FILE__) + '/persistable_helper.rb'
2
+
3
+ describe CouchObject::Persistable, "for loading objects:" do
4
+ before(:each) do
5
+ @bike = Bike.new
6
+ @with_location = WithStorageLocation.new
7
+
8
+ @db = mock("mock db")
9
+
10
+ @content_bike = %{
11
+ {
12
+ "_id":"123BAC",
13
+ "_rev":"946B7D1C",
14
+ "class":"Bike",
15
+ "attributes":
16
+ {
17
+ "wheels":2
18
+ }
19
+ }
20
+ }
21
+
22
+ @content_motor_bike = %{
23
+ {
24
+ "_id":"123BAC",
25
+ "_rev":"946B7D1C",
26
+ "class":"MotorBike",
27
+ "attributes":
28
+ {
29
+ "wheels":2
30
+ }
31
+ }
32
+ }
33
+
34
+ @content_with_location = %{
35
+ {
36
+ "_id":"123BAC",
37
+ "_rev":"946B7D1C",
38
+ "class":"WithStorageLocation",
39
+ "attributes":
40
+ {}
41
+ }
42
+ }
43
+
44
+ CouchObject::Database.stub!(:open).and_return(@db)
45
+ end
46
+
47
+ it "should get document by id" do
48
+ response = HTTPResponse.new(@content_bike)
49
+
50
+ @db.should_receive(:get).with("123BAC").and_return(response)
51
+
52
+ bike = Bike.get_by_id("123BAC", "foo")
53
+ bike.id.should == "123BAC"
54
+ end
55
+
56
+ it "should instantiate a new object WITH a :from_couch method when loaded from the document store" do
57
+ response = HTTPResponse.new(@content_bike)
58
+
59
+ @db.should_receive(:get).with("123BAC").and_return(response)
60
+
61
+ bike = Bike.get_by_id("123BAC", "foo")
62
+ bike.class.should == Bike
63
+ bike.wheels.should == 2
64
+ end
65
+
66
+ it "should instantiate a new object WITHOUT a :from_couch method when loaded from the document store" do
67
+ response = HTTPResponse.new(@content_motor_bike)
68
+ @db.should_receive(:get).with("123BAC").and_return(response)
69
+
70
+ motorbike = MotorBike.get_by_id("123BAC", "foo")
71
+ motorbike.class.should == MotorBike
72
+ motorbike.wheels.should == 2
73
+ end
74
+
75
+ it "classes with the storage location preset should be able to load documents by only supplying the ID" do
76
+ response = HTTPResponse.new(@content_with_location)
77
+ @db.should_receive(:get).with("123BAC").and_return(response)
78
+
79
+ with_content = WithStorageLocation.get_by_id("123BAC")
80
+ with_content.id.should == "123BAC"
81
+ end
82
+
83
+ it "should raise an error if a class that does not have the database uri set by default tries to laod a document only by ID" do
84
+ lambda{ bike = Bike.get_by_id("123BAC") }.
85
+ should raise_error(CouchObject::Errors::NoDatabaseLocationSet)
86
+ end
87
+
88
+ it "should raise an ID if the object doesn't exist in the database" do
89
+ response_missing = HTTPResponse.
90
+ new(%{{"error":"not_found","reason":"missing"}})
91
+ response_deleted = HTTPResponse.
92
+ new(%{{"error":"not_found","reason":"deleted"}})
93
+
94
+ @db.should_receive(:get).with("123BAC_1").and_return(response_missing)
95
+ @db.should_receive(:get).with("123BAC_2").and_return(response_deleted)
96
+
97
+ lambda{ bike = Bike.get_by_id("123BAC_1", "foo_db") }.
98
+ should raise_error(CouchObject::Errors::DocumentNotFound)
99
+
100
+ lambda{ bike = Bike.get_by_id("123BAC_2", "foo_db") }.
101
+ should raise_error(CouchObject::Errors::DocumentNotFound)
102
+
103
+ end
104
+
105
+ end
106
+
107
+ describe CouchObject::Persistable, "for loading object with subobject:" do
108
+ before(:each) do
109
+ @obj = WithSubobject.new
110
+
111
+ @db = mock("mock db")
112
+
113
+ @json_string = %{
114
+ {
115
+ "_id":"88F411C76D243294A8108DB25F2F8315",
116
+ "_rev":"4221814729",
117
+ "class":"WithSubobject",
118
+ "attributes":
119
+ {
120
+ "a_class":
121
+ {
122
+ "updated_at":
123
+ {
124
+ "json_class":"Time",
125
+ "s":1202756456,
126
+ "u":955933
127
+ },
128
+ "class":"Subobject",
129
+ "attributes":
130
+ {
131
+ "some_other_class":
132
+ {
133
+ "updated_at":
134
+ {
135
+ "json_class":"Time",
136
+ "s":1202756456,
137
+ "u":955986
138
+ },
139
+ "class":"SubSubobject",
140
+ "attributes":
141
+ {
142
+ "value":"New value"
143
+ },
144
+ "created_at":
145
+ {
146
+ "json_class":"Time",
147
+ "s":1202756456,
148
+ "u":955989
149
+ }
150
+ }
151
+ },
152
+ "created_at":
153
+ {
154
+ "json_class":"Time",
155
+ "s":1202756456,
156
+ "u":955936
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ CouchObject::Database.stub!(:open).and_return(@db)
164
+ end
165
+
166
+ it "should initialize all levels of subclasses" do
167
+ response = HTTPResponse.new(@json_string)
168
+ @db.should_receive(:get).with("123BAC").and_return(response)
169
+
170
+ with_subobject = WithSubobject.get_by_id("123BAC")
171
+ with_subobject.a_class.class.should == Subobject
172
+ with_subobject.a_class.some_other_class.class.should == SubSubobject
173
+ with_subobject.a_class.some_other_class.value.should == "New value"
174
+ with_subobject.a_class.some_other_class.created_at.class.should == Time
175
+ end
176
+ end
177
+
178
+ describe CouchObject::Persistable, "for loading objects from a view" do
179
+ before(:each) do
180
+ @bike = Bike.new
181
+ @with_location = WithStorageLocation.new
182
+
183
+ @db = mock("mock db")
184
+
185
+ @content_bike = %{
186
+ {
187
+ "_id":"123BAC",
188
+ "_rev":"946B7D1C",
189
+ "class":"Bike",
190
+ "attributes":
191
+ {
192
+ "wheels":2
193
+ }
194
+ }
195
+ }
196
+
197
+ @content_motor_bike = %{
198
+ {
199
+ "_id":"123BAC",
200
+ "_rev":"946B7D1C",
201
+ "class":"MotorBike",
202
+ "attributes":
203
+ {
204
+ "wheels":2
205
+ }
206
+ }
207
+ }
208
+
209
+ @content_with_location = %{
210
+ {
211
+ "_id":"123BAC",
212
+ "_rev":"946B7D1C",
213
+ "class":"WithStorageLocation",
214
+ "attributes":
215
+ {}
216
+ }
217
+ }
218
+
219
+ @view_content = %{
220
+ {"total_rows":4,"offset":0,"rows":[{"id":"1657A08F51BC66AEFD8630C27BD18E17","key":"1657A08F51BC66AEFD8630C27BD18E17","value":{"_id":"1657A08F51BC66AEFD8630C27BD18E17","_rev":"1626884106","class":"House","attributes":{}}},{"id":"1D7E2C45B946C1C4A7E76016FDAB104B","key":"1D7E2C45B946C1C4A7E76016FDAB104B","value":{"_id":"1D7E2C45B946C1C4A7E76016FDAB104B","_rev":"1069657208","class":"House","attributes":{}}},{"id":"D646E0796DF90D62AE402208E4043216","key":"D646E0796DF90D62AE402208E4043216","value":{"_id":"D646E0796DF90D62AE402208E4043216","_rev":"633260494","class":"House","attributes":{}}},{"id":"D770670E2219EC767BBB55FFB7763ADA","key":"D770670E2219EC767BBB55FFB7763ADA","value":{"_id":"D770670E2219EC767BBB55FFB7763ADA","_rev":"3666401817","class":"House","attributes":{}}}]}
221
+ }
222
+
223
+ @view_content_for_id = %{
224
+ {"total_rows":4,"offset":0,"rows":[{"id":"1657A08F51BC66AEFD8630C27BD18E17","key":"1657A08F51BC66AEFD8630C27BD18E17","value":{"_id":"1657A08F51BC66AEFD8630C27BD18E17","_rev":"1626884106","class":"House","attributes":{}}}]}
225
+ }
226
+
227
+ @view_content_mixed = %{
228
+ {"total_rows":2,"offset":0,"rows":[{"id":"5030848C1F2E31820ADF6B22832037DE","key":"5030848C1F2E31820ADF6B22832037DE","value":{"_id":"5030848C1F2E31820ADF6B22832037DE","_rev":"2247680957","class":"Bike","attributes":{}}},{"id":"D646E0796DF90D62AE402208E4043216","key":"D646E0796DF90D62AE402208E4043216","value":{"_id":"D646E0796DF90D62AE402208E4043216","_rev":"633260494","class":"House","attributes":{}}}]}
229
+ }
230
+
231
+ @view_content_subclasses = %{
232
+ {"total_rows":1,"offset":0,"rows":[{"id":"5A89FEF09C2E63E040B65909E277B604","key":"5A89FEF09C2E63E040B65909E277B604","value":{"_id":"5A89FEF09C2E63E040B65909E277B604","_rev":"4002434601","class":"WithSubobject","attributes":{"a_class":{"updated_at":{"json_class":"Time","s":1203104797,"u":954565},"class":"Subobject","attributes":{"some_other_class":{"updated_at":{"json_class":"Time","s":1203104797,"u":954624},"class":"SubSubobject","attributes":{"value":"Initialized"},"created_at":{"json_class":"Time","s":1203104797,"u":954627}}},"created_at":{"json_class":"Time","s":1203104797,"u":954569}}}}}]}
233
+ }
234
+
235
+ @view_content_no_rows = %{
236
+ {"total_rows":3,"rows":[]}
237
+ }
238
+
239
+ @view_content_missing_view = %{
240
+ {"error":"not_found","reason":"missing"}
241
+ }
242
+
243
+ CouchObject::Database.stub!(:open).and_return(@db)
244
+ end
245
+
246
+ it "should raise errors for missing arguments" do
247
+ lambda{bike = Bike.get_from_view()}.should \
248
+ raise_error(ArgumentError)
249
+
250
+ lambda{bike = Bike.get_from_view("foo_view")}.should \
251
+ raise_error(CouchObject::Errors::NoDatabaseLocationSet)
252
+ end
253
+
254
+ it "should return an array with four elements of class type House" do
255
+ response = HTTPResponse.new(@view_content)
256
+ @db.should_receive(:get).with("foo").and_return(response)
257
+
258
+ new_instances = Bike.get_from_view("foo", {:db_uri => "foo"})
259
+
260
+ new_instances.should be_a_kind_of(Array)
261
+ new_instances.size.should == 4
262
+ new_instances.each do |instance|
263
+ instance.class.should == House
264
+ instance.new?.should == false
265
+ end
266
+ end
267
+
268
+ it "should be able to get an element by key" do
269
+ response = HTTPResponse.new(@view_content_for_id)
270
+ @db.should_receive(:get).with("foo?key=%221657A08F51BC66AEFD8630C27BD18E17%22").and_return(response)
271
+
272
+ new_instances = Bike.get_from_view("foo", {:db_uri => "foo", :key => "1657A08F51BC66AEFD8630C27BD18E17"})
273
+
274
+ new_instances.should be_a_kind_of(Array)
275
+ new_instances.size.should == 1
276
+ new_instances.first.class.should == House
277
+ new_instances.first.new?.should == false
278
+
279
+ end
280
+
281
+ it "should return a blank array when sending in a non existent key" do
282
+ response = HTTPResponse.new(@view_content_no_rows)
283
+ @db.should_receive(:get).with("foo?key=%22D18E17%22").and_return(response)
284
+
285
+ new_instances = Bike.get_from_view("foo",
286
+ {:db_uri => "foo", :key => "D18E17"})
287
+
288
+ new_instances.should be_a_kind_of(Array)
289
+ new_instances.size.should == 0
290
+
291
+ end
292
+
293
+
294
+ it "should be able to return instances from a view with different objects" do
295
+ response = HTTPResponse.new(@view_content_mixed)
296
+ @db.should_receive(:get).with("foo").and_return(response)
297
+
298
+ new_instances = Bike.get_from_view("foo", {:db_uri => "foo"})
299
+
300
+ new_instances.should be_a_kind_of(Array)
301
+ new_instances.size.should == 2
302
+ new_instances[0].class.should == Bike
303
+ new_instances[0].new?.should == false
304
+
305
+ new_instances[1].class.should == House
306
+ new_instances[1].new?.should == false
307
+
308
+
309
+ end
310
+
311
+ it "should be able to load classes that have subclasses from a view" do
312
+ response = HTTPResponse.new(@view_content_subclasses)
313
+ @db.should_receive(:get).with("foo").and_return(response)
314
+
315
+ new_instances = WithSubobject.get_from_view("foo", {:db_uri => "foo"})
316
+
317
+ new_instances.should be_a_kind_of(Array)
318
+ new_instances.size.should == 1
319
+ new_instances.first.class.should == WithSubobject
320
+ new_instances.first.new?.should == false
321
+
322
+ new_instances.first.a_class.class.should == Subobject
323
+ new_instances.first.a_class.some_other_class.class.should == SubSubobject
324
+ new_instances.first.a_class.some_other_class.value.should == "Initialized"
325
+
326
+ end
327
+
328
+ it "should raise an error for non existing views" do
329
+ response = HTTPResponse.new(@view_content_missing_view)
330
+ @db.should_receive(:get).with("_view/doesnt_exist").and_return(response)
331
+
332
+ lambda{Bike.get_from_view("_view/doesnt_exist",
333
+ {:db_uri => "foo"})}.
334
+ should raise_error(CouchObject::Errors::MissingView)
335
+
336
+ end
337
+
338
+ end
339
+
@@ -0,0 +1,70 @@
1
+ require File.dirname(__FILE__) + '/persistable_helper.rb'
2
+
3
+ describe CouchObject::Persistable, "should give classes the following method:" do
4
+ before(:each) do
5
+ @bike = Bike.new
6
+ end
7
+
8
+ it "a save method" do
9
+ @bike.respond_to?(:save).should == true
10
+ end
11
+
12
+ it "a class method called get_by_id" do
13
+ Bike.respond_to?(:get_by_id).should == true
14
+ end
15
+
16
+ it "a id method" do
17
+ @bike.respond_to?(:id).should == true
18
+ end
19
+
20
+ it "a revision method" do
21
+ @bike.respond_to?(:revision).should == true
22
+ end
23
+
24
+ it "a location method (only getter)" do
25
+ @bike.respond_to?(:location).should == true
26
+ @bike.respond_to?(:location=).should_not == true
27
+ end
28
+
29
+ it "a self.location method (only getter)" do
30
+ Bike.respond_to?(:location).should == true
31
+ Bike.respond_to?(:location=).should_not == true
32
+ end
33
+
34
+ it "a storage_location alias (only getter)" do
35
+ @bike.respond_to?(:storage_location).should == true
36
+ @bike.respond_to?(:storage_location=).should_not == true
37
+ end
38
+
39
+ it "a set_location= method (only setter)" do
40
+ @bike.respond_to?(:set_location=).should == true
41
+ @bike.respond_to?(:set_location).should_not == true
42
+ end
43
+
44
+ it "a set_storage_location= alias (only setter)" do
45
+ @bike.respond_to?(:set_storage_location=).should == true
46
+ @bike.respond_to?(:set_storage_location).should_not == true
47
+ end
48
+
49
+ it "a self.couch_object_timestamp_on_update? method" do
50
+ Bike.respond_to?(:couch_object_timestamp_on_update?).should == true
51
+ end
52
+
53
+ it "a self.couch_object_timestamp_on_create? method" do
54
+ Bike.respond_to?(:couch_object_timestamp_on_create?).should == true
55
+ end
56
+
57
+ it "a == method" do
58
+ @bike.respond_to?(:==).should == true
59
+ end
60
+
61
+ it "a < and > method" do
62
+ @bike.respond_to?(:<).should == true
63
+ @bike.respond_to?(:>).should == true
64
+ end
65
+
66
+ it "a <= and a >= method" do
67
+ @bike.respond_to?(:<=).should == true
68
+ @bike.respond_to?(:>=).should == true
69
+ end
70
+ end
@@ -0,0 +1,70 @@
1
+ require File.dirname(__FILE__) + '/persistable_helper.rb'
2
+
3
+ describe CouchObject::Persistable, "should give classes the following method:" do
4
+ before(:each) do
5
+ @bike = Bike.new
6
+ end
7
+
8
+ it "a save method" do
9
+ @bike.respond_to?(:save).should == true
10
+ end
11
+
12
+ it "a class method called get_by_id" do
13
+ Bike.respond_to?(:get_by_id).should == true
14
+ end
15
+
16
+ it "a id method" do
17
+ @bike.respond_to?(:id).should == true
18
+ end
19
+
20
+ it "a revision method" do
21
+ @bike.respond_to?(:revision).should == true
22
+ end
23
+
24
+ it "a location method (only getter)" do
25
+ @bike.respond_to?(:location).should == true
26
+ @bike.respond_to?(:location=).should_not == true
27
+ end
28
+
29
+ it "a self.location method (only getter)" do
30
+ Bike.respond_to?(:location).should == true
31
+ Bike.respond_to?(:location=).should_not == true
32
+ end
33
+
34
+ it "a storage_location alias (only getter)" do
35
+ @bike.respond_to?(:storage_location).should == true
36
+ @bike.respond_to?(:storage_location=).should_not == true
37
+ end
38
+
39
+ it "a set_location= method (only setter)" do
40
+ @bike.respond_to?(:set_location=).should == true
41
+ @bike.respond_to?(:set_location).should_not == true
42
+ end
43
+
44
+ it "a set_storage_location= alias (only setter)" do
45
+ @bike.respond_to?(:set_storage_location=).should == true
46
+ @bike.respond_to?(:set_storage_location).should_not == true
47
+ end
48
+
49
+ it "a self.couch_object_timestamp_on_update? method" do
50
+ Bike.respond_to?(:couch_object_timestamp_on_update?).should == true
51
+ end
52
+
53
+ it "a self.couch_object_timestamp_on_create? method" do
54
+ Bike.respond_to?(:couch_object_timestamp_on_create?).should == true
55
+ end
56
+
57
+ it "a == method" do
58
+ @bike.respond_to?(:==).should == true
59
+ end
60
+
61
+ it "a < and > method" do
62
+ @bike.respond_to?(:<).should == true
63
+ @bike.respond_to?(:>).should == true
64
+ end
65
+
66
+ it "a <= and a >= method" do
67
+ @bike.respond_to?(:<=).should == true
68
+ @bike.respond_to?(:>=).should == true
69
+ end
70
+ end