resource 0.1.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 (39) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +26 -0
  4. data/Guardfile +22 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +19 -0
  7. data/Rakefile +49 -0
  8. data/VERSION +1 -0
  9. data/lib/api_resource.rb +51 -0
  10. data/lib/api_resource/associations.rb +472 -0
  11. data/lib/api_resource/attributes.rb +154 -0
  12. data/lib/api_resource/base.rb +517 -0
  13. data/lib/api_resource/callbacks.rb +49 -0
  14. data/lib/api_resource/connection.rb +162 -0
  15. data/lib/api_resource/core_extensions.rb +7 -0
  16. data/lib/api_resource/custom_methods.rb +119 -0
  17. data/lib/api_resource/exceptions.rb +74 -0
  18. data/lib/api_resource/formats.rb +14 -0
  19. data/lib/api_resource/formats/json_format.rb +25 -0
  20. data/lib/api_resource/formats/xml_format.rb +36 -0
  21. data/lib/api_resource/log_subscriber.rb +15 -0
  22. data/lib/api_resource/mocks.rb +249 -0
  23. data/lib/api_resource/model_errors.rb +86 -0
  24. data/lib/api_resource/observing.rb +29 -0
  25. data/resource.gemspec +125 -0
  26. data/spec/lib/associations_spec.rb +412 -0
  27. data/spec/lib/attributes_spec.rb +109 -0
  28. data/spec/lib/base_spec.rb +454 -0
  29. data/spec/lib/callbacks_spec.rb +68 -0
  30. data/spec/lib/model_errors_spec.rb +29 -0
  31. data/spec/spec_helper.rb +32 -0
  32. data/spec/support/mocks/association_mocks.rb +18 -0
  33. data/spec/support/mocks/error_resource_mocks.rb +21 -0
  34. data/spec/support/mocks/test_resource_mocks.rb +23 -0
  35. data/spec/support/requests/association_requests.rb +14 -0
  36. data/spec/support/requests/error_resource_requests.rb +25 -0
  37. data/spec/support/requests/test_resource_requests.rb +31 -0
  38. data/spec/support/test_resource.rb +19 -0
  39. metadata +277 -0
@@ -0,0 +1,412 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ include ApiResource
4
+
5
+ describe "Associations" do
6
+
7
+ after(:all) do
8
+ TestResource.reload_class_attributes
9
+ end
10
+
11
+ context "creating and testing for associations of various types" do
12
+
13
+ it "should be be able to define an asociation using a method named after that association type" do
14
+ TestResource.has_many :has_many_objects
15
+ TestResource.has_many?(:has_many_objects).should be_true
16
+ end
17
+
18
+ it "should be able to define associations with different class names" do
19
+ TestResource.has_many :test_name, :class_name => :has_many_objects
20
+ TestResource.has_many?(:test_name).should be_true
21
+ TestResource.has_many_class_name(:test_name).should eql("HasManyObject")
22
+ end
23
+
24
+ it "should be able to define multiple associations at the same time" do
25
+ TestResource.has_many :has_many_objects, :other_has_many_objects
26
+ TestResource.has_many?(:has_many_objects).should be_true
27
+ TestResource.has_many?(:other_has_many_objects).should be_true
28
+ end
29
+
30
+ it "should be able to tell if something is an association via the association? method" do
31
+ TestResource.belongs_to :belongs_to_object
32
+ TestResource.association?(:belongs_to_object).should be_true
33
+ end
34
+
35
+ it "should be able to get the class name of an association via the association_class_name method" do
36
+ TestResource.belongs_to :belongs_to_object
37
+ TestResource.association_class_name(:belongs_to_object).should eql("BelongsToObject")
38
+ TestResource.belongs_to :strange_name, :class_name => :belongs_to_object
39
+ TestResource.association_class_name(:strange_name).should eql("BelongsToObject")
40
+ end
41
+ end
42
+
43
+ context "creating and testing for scopes" do
44
+
45
+ it "should be able to define scopes which require class names" do
46
+ lambda {
47
+ TestResource.scope :test_scope
48
+ }.should raise_error
49
+ TestResource.scope :test_scope, {:has_many_objects => "test"}
50
+ end
51
+
52
+ it "should be able to test if a scope exists" do
53
+ TestResource.scope :test_scope, {:item => "test"}
54
+ TestResource.scope?(:test_scope).should be_true
55
+ TestResource.scope_attributes(:test_scope).should eql({"item" => "test"})
56
+ end
57
+
58
+ end
59
+
60
+ context "testing for scopes and associations on an instance" do
61
+
62
+ it "should be able to define associations on a class and test for them on an instance" do
63
+ TestResource.has_many :has_many_objects, :class_name => :other_has_many_objects
64
+ tst = TestResource.new
65
+ tst.has_many?(:has_many_objects).should be_true
66
+ tst.has_many_class_name(:has_many_objects).should eql("OtherHasManyObject")
67
+ end
68
+
69
+ it "should be able to define scopes on a class and test for them on an instance" do
70
+ TestResource.scope :has_many_objects, {:item => "test"}
71
+ tst = TestResource.new
72
+ tst.scope?(:has_many_objects).should be_true
73
+ tst.scope_attributes(:has_many_objects).should eql({"item" => "test"})
74
+ end
75
+
76
+ end
77
+
78
+ describe "Single Object Associations" do
79
+
80
+ before(:all) do
81
+ TestResource.reload_class_attributes
82
+ end
83
+
84
+ after(:all) do
85
+ TestResource.reload_class_attributes
86
+ end
87
+
88
+ it "should be able to create a SingleObjectProxy around a blank hash" do
89
+ ap = Associations::SingleObjectProxy.new("TestResource", {})
90
+ ap.remote_path.should be_blank
91
+ end
92
+
93
+ it "should be able to extract a service uri from the contents hash" do
94
+ ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => "/path"})
95
+ ap.remote_path.should eql("/path")
96
+ end
97
+
98
+ it "should be able to recognize the attributes of an object and not make them scopes" do
99
+ TestResource.define_attributes :test
100
+ ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => "/path", :test => "testval"})
101
+ ap.scope?("test").should be_false
102
+ ap.remote_path.should eql("/path")
103
+ end
104
+
105
+ it "should make all attributes except the service uri into scopes given the scopes_only option" do
106
+ ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => "/path", :test_scope => {"testval" => true}, :scopes_only => true})
107
+ ap.scope?("test_scope").should be_true
108
+ ap.remote_path.should eql("/path")
109
+ end
110
+
111
+ it "should pass the attributes that are not scopes and make them attributes of the object" do
112
+ TestResource.define_attributes :test
113
+ ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => "/path", :test => "testval"})
114
+ ap.internal_object.attributes.keys.should include("test")
115
+ end
116
+ end
117
+
118
+ describe "Multi Object Associations" do
119
+
120
+ before(:all) do
121
+ TestResource.related_objects[:scope].clear
122
+ end
123
+
124
+ after(:each) do
125
+ Associations::MultiObjectProxy.remote_path_element = :service_uri
126
+ end
127
+
128
+ describe "Loading settings" do
129
+
130
+ context "Loading array contents" do
131
+
132
+ it "should be able to load a blank array" do
133
+ ap = Associations::MultiObjectProxy.new("TestResource",[])
134
+ ap.remote_path.should be_nil
135
+ ap.scopes.keys.should eql([])
136
+ end
137
+
138
+ it "should be able to recognize a settings hash if it has a service_uri" do
139
+ ap = Associations::MultiObjectProxy.new("TestResource",[{:service_uri => "/route"}])
140
+ ap.remote_path.should eql("/route")
141
+ end
142
+
143
+ it "should be able to recognize a settings hash if it has a 'service_uri' with another preset name" do
144
+ Associations::MultiObjectProxy.remote_path_element = :the_element
145
+ ap = Associations::MultiObjectProxy.new("TestResource",[{:the_element => "/route"}])
146
+ ap.remote_path.should eql("/route")
147
+ end
148
+
149
+ end
150
+
151
+ context "Loading hash contents" do
152
+
153
+ it "should not be able to load a hash without a 'service_uri'" do
154
+ lambda {
155
+ Associations::MultiObjectProxy.new("TestResource", {})
156
+ }.should raise_error
157
+ end
158
+
159
+ it "should be able to recognize settings from a hash" do
160
+ ap = Associations::MultiObjectProxy.new("TestResource", {:service_uri => "/route"})
161
+ ap.remote_path.should eql("/route")
162
+ end
163
+
164
+ it "should recognize settings with differing 'service_uri' names" do
165
+ Associations::MultiObjectProxy.remote_path_element = :the_element
166
+ ap = Associations::MultiObjectProxy.new("TestResource",{:the_element => "/route"})
167
+ ap.remote_path.should eql("/route")
168
+ end
169
+ end
170
+
171
+ context "Defining scopes" do
172
+
173
+ it "should define scopes based on the other keys in a settings hash" do
174
+ ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope1 => {"scope1" => true}, :scope2 => {"scope2" => true}}])
175
+ [:scope1, :scope2].each{|s| ap.scopes.include?(s).should be_true }
176
+ end
177
+
178
+ it "should identify known scopes based on the scopes defined on the object it is a proxy to" do
179
+ TestResource.scope :class_scope, "class_scope" => "true"
180
+ ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope1 => {"scope1" => true}, :scope2 => {"scope2" => true}}])
181
+ [:scope1, :scope2, :class_scope].each{|s| ap.scopes.include?(s).should be_true}
182
+ end
183
+
184
+ it "scopes in the response should shadow class defined scopes" do
185
+ TestResource.scope :scope1, "scope1" => "true"
186
+ ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope1 => {"scope1" => true}, :scope2 => {"scope2" => true}}])
187
+ ap.scopes[:scope1].should eql({"scope1" => true})
188
+ end
189
+ end
190
+
191
+ end
192
+
193
+ describe "Selecting scopes" do
194
+
195
+ it "should be able to change scopes" do
196
+ ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope1 => {"scope1" => true}, :scope2 => {"scope2" => true}}])
197
+ ap.scope1.should be_a(Associations::RelationScope)
198
+ ap.scope1.current_scope.scope1_scope?.should be_true
199
+ end
200
+
201
+ it "should be able to chain scope calls together" do
202
+ ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope1 => {"scope1" => true}, :scope2 => {"scope2" => true}}])
203
+ ap.scope1.scope2.current_scope.scope1_and_scope2_scope?.should be_true
204
+ ap.scope1.scope2.to_query.should eql("scope1=true&scope2=true")
205
+ end
206
+
207
+ it "should support scopes that contain underscores" do
208
+ ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope_1 => {"scope_1" => true}, :scope_2 => {"scope_2" => true}}])
209
+ ap.scope_1.scope_2.current_scope.scope_1_and_scope_2_scope?.should be_true
210
+ end
211
+
212
+ it "should be able to return the current query string" do
213
+ ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope_1 => {"scope_1" => true}, :scope_2 => {"scope_2" => true}}])
214
+ ap.scope_1.scope_2.to_query.should eql("scope_1=true&scope_2=true")
215
+ end
216
+
217
+ it "should be able to substitute values into the scope query strings by passing a hash to the methods" do
218
+ ap = Associations::MultiObjectProxy.new("TestResource", [{:service_uri => "/route", :scope_1 => {"scope_1" => true, :test_sub => false}, :scope_2 => {"scope_2" => true}}])
219
+ obj = ap.scope_1(:test_sub => true).scope_2
220
+ obj.to_query.should eql("scope_1=true&scope_2=true&test_sub=true")
221
+ end
222
+ end
223
+
224
+
225
+ end
226
+
227
+ describe "Loading and Caching loaded data" do
228
+
229
+ context "Single Object" do
230
+
231
+ before(:all) do
232
+ TestResource.reload_class_attributes
233
+ end
234
+
235
+ it "should be able to force load an object" do
236
+ ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => true}, :scopes_only => true})
237
+ ap.loaded.should be_blank
238
+ name = ap.name
239
+ name.should_not be_blank
240
+ ap.loaded.should_not be_blank
241
+ # Make sure it isn't reloaded
242
+ ap.name.should eql(name)
243
+ end
244
+
245
+ it "should be able to load a scope" do
246
+ ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => true}, :scopes_only => true})
247
+ ap.internal_object.active.should be_false
248
+ ap.times_loaded.should eql(1)
249
+ ap.active.should be_a Associations::RelationScope
250
+ ap.active.name.should_not be_blank
251
+ ap.times_loaded.should eql(2)
252
+ ap.active.internal_object.active.should be_true
253
+ # another check that the resource wasn't reloaded
254
+ ap.times_loaded.should eql(2)
255
+ end
256
+
257
+ it "should be able to load a chain of scopes" do
258
+ ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => true}, :with_birthday => {:birthday => true}, :scopes_only => true})
259
+ first = ap.active.with_birthday.id
260
+ ap.with_birthday.active.id.should eql(first)
261
+ ap.times_loaded.should eql(1)
262
+ ap.active.with_birthday.birthday.should_not be_blank
263
+ end
264
+
265
+ it "should be able to load a chain of scopes with substitution" do
266
+ ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => false}, :with_birthday => {:birthday => true}, :scopes_only => true})
267
+ item = ap.active(:active => true).internal_object
268
+ item.active.should be_true
269
+ ap.times_loaded.should eql(1)
270
+ # This error and incrementing times_loaded means it tried to load again
271
+ lambda {
272
+ ap.active.internal_object
273
+ }.should raise_error
274
+ ap.times_loaded.should eql(2)
275
+ end
276
+
277
+ it "should proxy unknown methods to the object loading if it hasn't already" do
278
+ ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => false}, :with_birthday => {:birthday => true}, :scopes_only => true})
279
+ ap.times_loaded.should eql(0)
280
+ ap.id.should_not be_blank
281
+ ap.times_loaded.should eql(1)
282
+ end
283
+
284
+ it "should only load each distinct set of scopes once" do
285
+ ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => false}, :with_birthday => {:birthday => true}, :scopes_only => true})
286
+ ap.times_loaded.should eql(0)
287
+ ap.active(:active => true).with_birthday.internal_object
288
+ ap.with_birthday.active(:active => true).internal_object
289
+ ap.times_loaded.should eql(1)
290
+ end
291
+
292
+ it "should be able to clear it's loading cache" do
293
+ ap = Associations::SingleObjectProxy.new("TestResource",{:service_uri => '/single_object_association', :active => {:active => true}, :with_birthday => {:birthday => true}, :scopes_only => true})
294
+ ap.active.internal_object
295
+ ap.times_loaded.should eql(1)
296
+ ap.reload
297
+ ap.active.internal_object
298
+ ap.times_loaded.should eql(1)
299
+ end
300
+
301
+ end
302
+
303
+ context "Multi Object" do
304
+
305
+ it "should be able to load 'all'" do
306
+ ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => false}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
307
+ results = ap.all
308
+ results.size.should eql(5)
309
+ results.first.active.should be_false
310
+ end
311
+
312
+ it "should be able to load a scope" do
313
+ ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => false}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
314
+ results = ap.active.internal_object
315
+ results.size.should eql(5)
316
+ results.first.active.should be_true
317
+ end
318
+
319
+ it "should be able to load a chain of scopes" do
320
+ ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
321
+ results = ap.active.with_birthday.internal_object
322
+ results.first.active.should be_true
323
+ results.first.birthday.should_not be_blank
324
+ end
325
+
326
+ it "should be able to load a chain of scopes with substitution" do
327
+ ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
328
+ results = ap.inactive(:active => true).with_birthday.internal_object
329
+ results.first.active.should be_true
330
+ results.first.birthday.should_not be_blank
331
+ end
332
+
333
+ it "should proxy unknown methods to the object array loading if it hasn't already" do
334
+ ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
335
+ ap.first.should be_a TestResource
336
+ ap.active.first.should be_a TestResource
337
+ ap.times_loaded.should eql(2)
338
+ end
339
+
340
+ it "should only load each distinct set of scopes once" do
341
+ ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
342
+ ap.first
343
+ ap.active.first
344
+ ap.times_loaded.should eql(2)
345
+ ap.active.first
346
+ ap.times_loaded.should eql(2)
347
+ ap.active.with_birthday.first
348
+ ap.with_birthday.active.first
349
+ ap.times_loaded.should eql(3)
350
+ end
351
+
352
+ it "should be able to clear it's loading cache" do
353
+ ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
354
+ ap.active.first
355
+ ap.times_loaded.should eql(1)
356
+ ap.reload
357
+ ap.active.first
358
+ ap.times_loaded.should eql(1)
359
+ end
360
+
361
+ it "should be enumerable" do
362
+ ap = Associations::MultiObjectProxy.new("TestResource",{:service_uri => '/multi_object_association', :active => {:active => true}, :inactive => {:active => false}, :with_birthday => {:birthday => true}})
363
+ ap.each do |tr|
364
+ tr.name.should_not be_blank
365
+ end
366
+ end
367
+
368
+ end
369
+
370
+ context "Scopes" do
371
+
372
+ before(:all) do
373
+ TestResource.reload_class_attributes
374
+ end
375
+
376
+ it "should define class methods for the known scopes" do
377
+ TestResource.scopes.each do |key, _|
378
+ TestResource.should respond_to key
379
+ end
380
+ end
381
+
382
+ it "should return a ResourceScope when calling any scope on a class" do
383
+ TestResource.send(TestResource.scopes.first.first.to_sym).should be_a Associations::ResourceScope
384
+ end
385
+
386
+ it "should be able to chain scopes" do
387
+ scp = TestResource.active.paginate
388
+ scp.should be_a Associations::ResourceScope
389
+ scp.to_query.should eql("active=true&current_page=current_page&paginate=true&per_page=per_page")
390
+ end
391
+
392
+ it "should load when calling all" do
393
+ TestResource.active.should respond_to :all
394
+ TestResource.active.should respond_to :internal_object
395
+ results = TestResource.active.all
396
+ results.should be_a Array
397
+ results.size.should eql(5)
398
+ results.each do |res|
399
+ res.should be_a TestResource
400
+ end
401
+ end
402
+
403
+ it "should load when calling an enumerable method or an array method" do
404
+ TestResource.active.each do |result|
405
+ result.should be_a TestResource
406
+ end
407
+ end
408
+ end
409
+
410
+ end
411
+
412
+ end
@@ -0,0 +1,109 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ include ApiResource
4
+
5
+ describe "Attributes" do
6
+
7
+ after(:all) do
8
+ TestResource.reload_class_attributes
9
+ end
10
+
11
+ context "Defining, getting, and setting attributes" do
12
+ it "should be able to define known attributes" do
13
+ TestResource.define_attributes :attr1, :attr2
14
+ TestResource.attribute?(:attr1).should be_true
15
+ TestResource.attribute?(:attr2).should be_true
16
+ end
17
+
18
+ it "should define methods for testing for reading and writing known attributes" do
19
+ TestResource.define_attributes :attr1, :attr2
20
+ tst = TestResource.new
21
+ tst.respond_to?(:attr1).should be_true
22
+ tst.respond_to?(:attr1=).should be_true
23
+ tst.respond_to?(:attr1?).should be_true
24
+ end
25
+
26
+ it "should be able to set and change attributes" do
27
+ TestResource.define_attributes :attr1, :attr2
28
+ tst = TestResource.new
29
+ tst.attr1.should be_nil
30
+ tst.attr1?.should be_false
31
+ tst.attr1 = "test"
32
+ tst.attr1.should eql("test")
33
+ tst.attr1?.should be_true
34
+ end
35
+ end
36
+
37
+ context "Protected attributes" do
38
+ it "should allow protected attributes that cannot be changed" do
39
+ TestResource.define_protected_attributes :pattr3
40
+ lambda {
41
+ tst = TestResource.new
42
+ tst.attr3 = "test"
43
+ }.should raise_error
44
+ end
45
+ end
46
+
47
+ context "Dirty tracking" do
48
+ context "Changes to attributes" do
49
+ it "should implement dirty tracking for attributes" do
50
+ TestResource.define_attributes :attr1, :attr2
51
+ tst = TestResource.new
52
+ tst.changed.should be_blank
53
+ tst.attr1 = "Hello"
54
+ tst.changed.include?("attr1").should be_true
55
+ tst.changes.should_not be_blank
56
+
57
+ # Set an attribute equal to itself
58
+ tst.attr2 = tst.attr2
59
+ tst.changes.include?("attr2").should be_false
60
+ end
61
+
62
+ end
63
+
64
+ context "Resetting and marking attributes current" do
65
+
66
+ before(:each) do
67
+ TestResource.define_attributes :attr1, :attr2
68
+ end
69
+
70
+ it "should be able to mark any list of attributes as current (unchanged)" do
71
+ tst = TestResource.new
72
+ tst.attr1 = "Hello"
73
+ tst.changed.should_not be_blank
74
+ tst.set_attributes_as_current :attr1, :attr2
75
+ tst.changed.should be_blank
76
+ end
77
+
78
+ it "should be able to mark all the attributes as current if none are given" do
79
+ tst = TestResource.new
80
+ tst.attr1 = "attr1"
81
+ tst.attr2 = "attr2"
82
+ tst.changed.should_not be_blank
83
+ tst.set_attributes_as_current
84
+ tst.changed.should be_blank
85
+ end
86
+
87
+ it "should be able to reset any list of attributes" do
88
+ tst = TestResource.new
89
+ tst.attr1 = "attr1"
90
+ tst.reset_attribute_changes :attr1
91
+ tst.attr1.should be_nil
92
+ tst.changed.should be_blank
93
+ end
94
+
95
+ it "should be able to reset all the attributes if none are given" do
96
+ tst = TestResource.new
97
+ tst.attr1 = "attr1"
98
+ tst.attr2 = "attr2"
99
+
100
+ tst.reset_attribute_changes
101
+ tst.attr1.should be_nil
102
+ tst.attr2.should be_nil
103
+ tst.changed.should be_blank
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ end