couchobject 0.5.0 → 0.6.0

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