resource 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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