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