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,76 @@
1
+ require File.dirname(__FILE__) + '/persistable_helper.rb'
2
+
3
+ describe CouchObject::Persistable, "timestamp related actions:" do
4
+ before(:each) do
5
+ @bike = Bike.new
6
+ @timestampclass = WithTimeStamp.new
7
+
8
+ @db = mock("mock db")
9
+
10
+ content = HTTPResponse.new(JSON.unparse({
11
+ "_id" => "123BAC",
12
+ "_rev" => "946B7D1C",
13
+ "created_at" => Time.new,
14
+ "updated_at" => Time.new,
15
+ "class" => "WithTimeStamp",
16
+ "attributes" => {
17
+ "wheels" => 3
18
+ }
19
+ }))
20
+
21
+ CouchObject::Response.stub!(:body).and_return(content)
22
+
23
+ @empty_response = {}
24
+ @ok_response = {"ok" => true}
25
+ @document_response = CouchObject::Response.new(content)
26
+ end
27
+
28
+ it "timestamps should be nil for classes that don't include them" do
29
+ @bike.created_at.should == nil
30
+ @bike.updated_at.should == nil
31
+ end
32
+
33
+ it "timestamps should be set when saving an object" do
34
+ CouchObject::Database.should_receive(:open).and_return(@db)
35
+ timestampclass_json = @timestampclass.to_json
36
+
37
+ @timestampclass.stub!(:to_json).and_return(timestampclass_json)
38
+ @db.should_receive(:post).
39
+ with("", timestampclass_json).and_return(@document_response)
40
+
41
+ @timestampclass.save("foo")
42
+
43
+ @timestampclass.created_at.should_not == nil
44
+ @timestampclass.updated_at.should_not == nil
45
+ end
46
+
47
+ it "should update updated_at when saving an object again" do
48
+ CouchObject::Database.stub!(:open).and_return(@db)
49
+ timestampclass_json = @timestampclass.to_json
50
+
51
+ @timestampclass.stub!(:to_json).and_return(timestampclass_json)
52
+ @db.should_receive(:post).
53
+ with("", timestampclass_json).and_return(@document_response)
54
+ @db.should_receive(:put).
55
+ with("123BAC", timestampclass_json).and_return(@document_response)
56
+
57
+ @timestampclass.save("foo")
58
+ updated_at = @timestampclass.updated_at
59
+
60
+ @timestampclass.save
61
+
62
+ @timestampclass.updated_at.should_not == updated_at
63
+ end
64
+
65
+ it "loaded object should have their timestamps set" do
66
+ CouchObject::Database.should_receive(:open).and_return(@db)
67
+ @db.should_receive(:get).with("123BAC").and_return(@document_response)
68
+
69
+ timestampclass = WithTimeStamp.get_by_id("123BAC", "foo")
70
+
71
+ timestampclass.class.should == WithTimeStamp
72
+ timestampclass.created_at.should_not == nil
73
+ timestampclass.updated_at.should_not == nil
74
+ end
75
+ end
76
+
@@ -0,0 +1,211 @@
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_motor_bike = %{
11
+ {
12
+ "_id":"123BAC",
13
+ "_rev":"946B7D1C",
14
+ "class":"OtherMotorBike",
15
+ "attributes":
16
+ {
17
+ "wheels":2
18
+ }
19
+ }
20
+ }
21
+
22
+ @normal_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 = HTTPResponse.new(JSON.unparse({
35
+ "_id" => "123BAC",
36
+ "_rev" => "946B7D1C",
37
+ }))
38
+ @document_response = CouchObject::Response.new(content)
39
+
40
+ @apartment_building_content = %{ {"_id":"A26EDC59D99D50C2CB2615404D357E49","_rev":"1756574922","class":"OtherApartmentBuilding","attributes":{}}
41
+ }
42
+
43
+ @apartment_building_inhabitants = %{{"total_rows":2,"offset":0,"rows":[{"id":"241757E4D7D2272C78048B4007399837","key":["12A64B5156002BFD66F23F559C5AD5FA","inhabitants"],"value":{"_id":"241757E4D7D2272C78048B4007399837","_rev":"2508041460","class":"OtherInhabitant","attributes":{},"belongs_to":{"inhabitants":"A26EDC59D99D50C2CB2615404D357E49"}}},{"id":"E94C93DEDB94BFC30B9AB396D4874D52","key":["12A64B5156002BFD66F23F559C5AD5FA","inhabitants"],"value":{"_id":"E94C93DEDB94BFC30B9AB396D4874D52","_rev":"960098874","class":"OtherInhabitant","attributes":{},"belongs_to":{"inhabitants":"A26EDC59D99D50C2CB2615404D357E49"}}}]}
44
+ }
45
+
46
+ CouchObject::Database.stub!(:open).and_return(@db)
47
+ end
48
+
49
+ it "should return true for new objects" do
50
+ mbike = OtherMotorBike.new
51
+ mbike.unsaved_changes?.should == true
52
+ mbike.use_smart_save.should == true
53
+ end
54
+
55
+ it "should return true for new objects that haven't activated the smart save feature" do
56
+ mbike = MotorBike.new
57
+ mbike.unsaved_changes?.should == true
58
+ mbike.use_smart_save.should == false
59
+ end
60
+
61
+ it "should know if an object has unsaved changes" do
62
+ response = HTTPResponse.new(@content_motor_bike)
63
+
64
+ @db.should_receive(:get).with("123BAC").and_return(response)
65
+
66
+ mbike = MotorBike.get_by_id("123BAC", "foo")
67
+ mbike.id.should == "123BAC"
68
+ mbike.use_smart_save.should == true
69
+
70
+ mbike.unsaved_changes?.should == false
71
+ mbike.wheels = 3
72
+ mbike.unsaved_changes?.should == true
73
+
74
+ end
75
+
76
+ it "should know if an object has unsaved for an object where the instance is forced into smart save mode" do
77
+ response = HTTPResponse.new(@normal_content_motor_bike)
78
+
79
+ @db.should_receive(:get).with("123BAC").and_return(response)
80
+
81
+ mbike = MotorBike.get_with_smart_save("123BAC", "foo")
82
+ mbike.id.should == "123BAC"
83
+ mbike.use_smart_save.should == true
84
+
85
+ mbike.unsaved_changes?.should == false
86
+ mbike.wheels = 3
87
+ mbike.unsaved_changes?.should == true
88
+
89
+ end
90
+
91
+ it "should save an object that does not unsaved changes if it hasn't activated smart_save" do
92
+ response = HTTPResponse.new(@normal_content_motor_bike)
93
+
94
+ @db.should_receive(:get).with("123BAC").and_return(response)
95
+ @db.should_receive(:put).and_return(@document_response)
96
+
97
+ mbike = MotorBike.get_by_id("123BAC", "foo")
98
+ mbike.use_smart_save.should == false
99
+
100
+ # unsaved_changes should always be true for classes that don't have
101
+ # smart_save activated
102
+ mbike.unsaved_changes?.should == true
103
+ mbike.save
104
+
105
+ end
106
+
107
+ it "should not save an object that does not have unsaved changes" do
108
+ response = HTTPResponse.new(@content_motor_bike)
109
+
110
+ @db.should_receive(:get).with("123BAC").and_return(response)
111
+ @db.should_not_receive(:put)
112
+
113
+ mbike = OtherMotorBike.get_by_id("123BAC", "foo")
114
+
115
+ mbike.unsaved_changes?.should == false
116
+ mbike.save
117
+
118
+ end
119
+
120
+ it "should not have unsaved_changes after having been saved" do
121
+ response = HTTPResponse.new(@content_motor_bike)
122
+
123
+ @db.should_receive(:get).with("123BAC").and_return(response)
124
+ @db.should_receive(:put).and_return(@document_response)
125
+
126
+ mbike = OtherMotorBike.get_by_id("123BAC", "foo")
127
+
128
+ mbike.unsaved_changes?.should == false
129
+ mbike.wheels = 3
130
+ mbike.unsaved_changes?.should == true
131
+ mbike.save
132
+ mbike.unsaved_changes?.should == false
133
+
134
+ end
135
+
136
+ it "should only save relations that need to be saved" do
137
+
138
+ response = HTTPResponse.new(@apartment_building_content)
139
+ response_relation = HTTPResponse.new(@apartment_building_inhabitants)
140
+
141
+ CouchObject::Database.should_receive(:open).exactly(3).and_return(@db)
142
+
143
+ @db.should_receive(:get).with("foo").and_return(response)
144
+
145
+ # has_many relations are loaded
146
+ @db.should_receive(:get).
147
+ with("_view/couch_object_has_many_relations/related_documents?" + \
148
+ "key=[%22A26EDC59D99D50C2CB2615404D357E49%22,%22inhabitants%22]").
149
+ and_return(response_relation)
150
+
151
+ @db.should_receive(:put).once.and_return(@document_response)
152
+
153
+
154
+ apartment_building = OtherApartmentBuilding.get_by_id("foo")
155
+ apartment_building.location.should_not == nil
156
+
157
+ # The content should be loaded on first request!
158
+ apartment_building.inhabitants.size.should == 2
159
+ apartment_building.inhabitants.first.change
160
+ apartment_building.inhabitants.first.unsaved_changes?.should == true
161
+ apartment_building.inhabitants.last.unsaved_changes?.should == false
162
+ apartment_building.unsaved_changes?.should == false
163
+ apartment_building.save
164
+
165
+ end
166
+
167
+ it "a class in a belongs_to relationship should notice if its parent changes" do
168
+
169
+ response = HTTPResponse.new(@apartment_building_content)
170
+ response_relation = HTTPResponse.new(@apartment_building_inhabitants)
171
+
172
+ CouchObject::Database.should_receive(:open).exactly(4).and_return(@db)
173
+
174
+ @db.should_receive(:get).with("foo").and_return(response)
175
+
176
+ # has_many relations are loaded
177
+ @db.should_receive(:get).
178
+ with("_view/couch_object_has_many_relations/related_documents?" + \
179
+ "key=[%22A26EDC59D99D50C2CB2615404D357E49%22,%22inhabitants%22]").
180
+ and_return(response_relation)
181
+
182
+ # For the second apartment building
183
+ @db.should_receive(:post).once.and_return(@document_response)
184
+ # And it's inhabitant which should be updated with the new parent
185
+ @db.should_receive(:put).once.and_return(@document_response)
186
+
187
+ apartment_building = OtherApartmentBuilding.get_by_id("foo")
188
+ second_apartment_building = OtherApartmentBuilding.new
189
+ second_apartment_building.unsaved_changes?.should == true
190
+
191
+ # The content should be loaded on first request!
192
+ apartment_building.inhabitants.size.should == 2
193
+ apartment_building.inhabitants.first.unsaved_changes?.should == false
194
+ apartment_building.inhabitants.last.unsaved_changes?.should == false
195
+ apartment_building.unsaved_changes?.should == false
196
+
197
+ second_apartment_building.inhabitants << apartment_building.inhabitants.first
198
+ second_apartment_building.inhabitants.size.should == 1
199
+ apartment_building.inhabitants.size.should == 1
200
+ apartment_building.inhabitants.first.unsaved_changes?.should == false
201
+ second_apartment_building.inhabitants.first.unsaved_changes?.should == true
202
+
203
+ # Should not save anything at all...
204
+ apartment_building.save
205
+
206
+ # Should save both documents
207
+ second_apartment_building.save
208
+
209
+ end
210
+
211
+ end
@@ -0,0 +1,211 @@
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_motor_bike = %{
11
+ {
12
+ "_id":"123BAC",
13
+ "_rev":"946B7D1C",
14
+ "class":"OtherMotorBike",
15
+ "attributes":
16
+ {
17
+ "wheels":2
18
+ }
19
+ }
20
+ }
21
+
22
+ @normal_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 = HTTPResponse.new(JSON.unparse({
35
+ "_id" => "123BAC",
36
+ "_rev" => "946B7D1C",
37
+ }))
38
+ @document_response = CouchObject::Response.new(content)
39
+
40
+ @apartment_building_content = %{ {"_id":"A26EDC59D99D50C2CB2615404D357E49","_rev":"1756574922","class":"OtherApartmentBuilding","attributes":{}}
41
+ }
42
+
43
+ @apartment_building_inhabitants = %{{"total_rows":2,"offset":0,"rows":[{"id":"241757E4D7D2272C78048B4007399837","key":["12A64B5156002BFD66F23F559C5AD5FA","inhabitants"],"value":{"_id":"241757E4D7D2272C78048B4007399837","_rev":"2508041460","class":"OtherInhabitant","attributes":{},"belongs_to":{"inhabitants":"A26EDC59D99D50C2CB2615404D357E49"}}},{"id":"E94C93DEDB94BFC30B9AB396D4874D52","key":["12A64B5156002BFD66F23F559C5AD5FA","inhabitants"],"value":{"_id":"E94C93DEDB94BFC30B9AB396D4874D52","_rev":"960098874","class":"OtherInhabitant","attributes":{},"belongs_to":{"inhabitants":"A26EDC59D99D50C2CB2615404D357E49"}}}]}
44
+ }
45
+
46
+ CouchObject::Database.stub!(:open).and_return(@db)
47
+ end
48
+
49
+ it "should return true for new objects" do
50
+ mbike = OtherMotorBike.new
51
+ mbike.unsaved_changes?.should == true
52
+ mbike.use_smart_save.should == true
53
+ end
54
+
55
+ it "should return true for new objects that haven't activated the smart save feature" do
56
+ mbike = MotorBike.new
57
+ mbike.unsaved_changes?.should == true
58
+ mbike.use_smart_save.should == false
59
+ end
60
+
61
+ it "should know if an object has unsaved changes" do
62
+ response = HTTPResponse.new(@content_motor_bike)
63
+
64
+ @db.should_receive(:get).with("123BAC").and_return(response)
65
+
66
+ mbike = MotorBike.get_by_id("123BAC", "foo")
67
+ mbike.id.should == "123BAC"
68
+ mbike.use_smart_save.should == true
69
+
70
+ mbike.unsaved_changes?.should == false
71
+ mbike.wheels = 3
72
+ mbike.unsaved_changes?.should == true
73
+
74
+ end
75
+
76
+ it "should know if an object has unsaved for an object where the instance is forced into smart save mode" do
77
+ response = HTTPResponse.new(@normal_content_motor_bike)
78
+
79
+ @db.should_receive(:get).with("123BAC").and_return(response)
80
+
81
+ mbike = MotorBike.get_with_smart_save("123BAC", "foo")
82
+ mbike.id.should == "123BAC"
83
+ mbike.use_smart_save.should == true
84
+
85
+ mbike.unsaved_changes?.should == false
86
+ mbike.wheels = 3
87
+ mbike.unsaved_changes?.should == true
88
+
89
+ end
90
+
91
+ it "should save an object that does not unsaved changes if it hasn't activated smart_save" do
92
+ response = HTTPResponse.new(@normal_content_motor_bike)
93
+
94
+ @db.should_receive(:get).with("123BAC").and_return(response)
95
+ @db.should_receive(:put).and_return(@document_response)
96
+
97
+ mbike = MotorBike.get_by_id("123BAC", "foo")
98
+ mbike.use_smart_save.should == false
99
+
100
+ # unsaved_changes should always be true for classes that don't have
101
+ # smart_save activated
102
+ mbike.unsaved_changes?.should == true
103
+ mbike.save
104
+
105
+ end
106
+
107
+ it "should not save an object that does not have unsaved changes" do
108
+ response = HTTPResponse.new(@content_motor_bike)
109
+
110
+ @db.should_receive(:get).with("123BAC").and_return(response)
111
+ @db.should_not_receive(:put)
112
+
113
+ mbike = OtherMotorBike.get_by_id("123BAC", "foo")
114
+
115
+ mbike.unsaved_changes?.should == false
116
+ mbike.save
117
+
118
+ end
119
+
120
+ it "should not have unsaved_changes after having been saved" do
121
+ response = HTTPResponse.new(@content_motor_bike)
122
+
123
+ @db.should_receive(:get).with("123BAC").and_return(response)
124
+ @db.should_receive(:put).and_return(@document_response)
125
+
126
+ mbike = OtherMotorBike.get_by_id("123BAC", "foo")
127
+
128
+ mbike.unsaved_changes?.should == false
129
+ mbike.wheels = 3
130
+ mbike.unsaved_changes?.should == true
131
+ mbike.save
132
+ mbike.unsaved_changes?.should == false
133
+
134
+ end
135
+
136
+ it "should only save relations that need to be saved" do
137
+
138
+ response = HTTPResponse.new(@apartment_building_content)
139
+ response_relation = HTTPResponse.new(@apartment_building_inhabitants)
140
+
141
+ CouchObject::Database.should_receive(:open).exactly(3).and_return(@db)
142
+
143
+ @db.should_receive(:get).with("foo").and_return(response)
144
+
145
+ # has_many relations are loaded
146
+ @db.should_receive(:get).
147
+ with("_view/couch_object_has_many_relations/related_documents?" + \
148
+ "key=[%22A26EDC59D99D50C2CB2615404D357E49%22,%22inhabitants%22]").
149
+ and_return(response_relation)
150
+
151
+ @db.should_receive(:put).once.and_return(@document_response)
152
+
153
+
154
+ apartment_building = OtherApartmentBuilding.get_by_id("foo")
155
+ apartment_building.location.should_not == nil
156
+
157
+ # The content should be loaded on first request!
158
+ apartment_building.inhabitants.size.should == 2
159
+ apartment_building.inhabitants.first.change
160
+ apartment_building.inhabitants.first.unsaved_changes?.should == true
161
+ apartment_building.inhabitants.last.unsaved_changes?.should == false
162
+ apartment_building.unsaved_changes?.should == false
163
+ apartment_building.save
164
+
165
+ end
166
+
167
+ it "a class in a belongs_to relationship should notice if its parent changes" do
168
+
169
+ response = HTTPResponse.new(@apartment_building_content)
170
+ response_relation = HTTPResponse.new(@apartment_building_inhabitants)
171
+
172
+ CouchObject::Database.should_receive(:open).exactly(4).and_return(@db)
173
+
174
+ @db.should_receive(:get).with("foo").and_return(response)
175
+
176
+ # has_many relations are loaded
177
+ @db.should_receive(:get).
178
+ with("_view/couch_object_has_many_relations/related_documents?" + \
179
+ "key=[%22A26EDC59D99D50C2CB2615404D357E49%22,%22inhabitants%22]").
180
+ and_return(response_relation)
181
+
182
+ # For the second apartment building
183
+ @db.should_receive(:post).once.and_return(@document_response)
184
+ # And it's inhabitant which should be updated with the new parent
185
+ @db.should_receive(:put).once.and_return(@document_response)
186
+
187
+ apartment_building = OtherApartmentBuilding.get_by_id("foo")
188
+ second_apartment_building = OtherApartmentBuilding.new
189
+ second_apartment_building.unsaved_changes?.should == true
190
+
191
+ # The content should be loaded on first request!
192
+ apartment_building.inhabitants.size.should == 2
193
+ apartment_building.inhabitants.first.unsaved_changes?.should == false
194
+ apartment_building.inhabitants.last.unsaved_changes?.should == false
195
+ apartment_building.unsaved_changes?.should == false
196
+
197
+ second_apartment_building.inhabitants << apartment_building.inhabitants.first
198
+ second_apartment_building.inhabitants.size.should == 1
199
+ apartment_building.inhabitants.size.should == 1
200
+ apartment_building.inhabitants.first.unsaved_changes?.should == false
201
+ second_apartment_building.inhabitants.first.unsaved_changes?.should == true
202
+
203
+ # Should not save anything at all...
204
+ apartment_building.save
205
+
206
+ # Should save both documents
207
+ second_apartment_building.save
208
+
209
+ end
210
+
211
+ end