rpbertp13-dm-core 0.9.11.1

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 (131) hide show
  1. data/.autotest +26 -0
  2. data/.gitignore +18 -0
  3. data/CONTRIBUTING +51 -0
  4. data/FAQ +92 -0
  5. data/History.txt +52 -0
  6. data/MIT-LICENSE +22 -0
  7. data/Manifest.txt +130 -0
  8. data/QUICKLINKS +11 -0
  9. data/README.txt +143 -0
  10. data/Rakefile +32 -0
  11. data/SPECS +62 -0
  12. data/TODO +1 -0
  13. data/dm-core.gemspec +40 -0
  14. data/lib/dm-core.rb +217 -0
  15. data/lib/dm-core/adapters.rb +16 -0
  16. data/lib/dm-core/adapters/abstract_adapter.rb +209 -0
  17. data/lib/dm-core/adapters/data_objects_adapter.rb +716 -0
  18. data/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
  19. data/lib/dm-core/adapters/mysql_adapter.rb +138 -0
  20. data/lib/dm-core/adapters/postgres_adapter.rb +189 -0
  21. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  22. data/lib/dm-core/associations.rb +207 -0
  23. data/lib/dm-core/associations/many_to_many.rb +147 -0
  24. data/lib/dm-core/associations/many_to_one.rb +107 -0
  25. data/lib/dm-core/associations/one_to_many.rb +315 -0
  26. data/lib/dm-core/associations/one_to_one.rb +61 -0
  27. data/lib/dm-core/associations/relationship.rb +221 -0
  28. data/lib/dm-core/associations/relationship_chain.rb +81 -0
  29. data/lib/dm-core/auto_migrations.rb +105 -0
  30. data/lib/dm-core/collection.rb +670 -0
  31. data/lib/dm-core/dependency_queue.rb +32 -0
  32. data/lib/dm-core/hook.rb +11 -0
  33. data/lib/dm-core/identity_map.rb +42 -0
  34. data/lib/dm-core/is.rb +16 -0
  35. data/lib/dm-core/logger.rb +232 -0
  36. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  37. data/lib/dm-core/migrator.rb +29 -0
  38. data/lib/dm-core/model.rb +526 -0
  39. data/lib/dm-core/naming_conventions.rb +84 -0
  40. data/lib/dm-core/property.rb +676 -0
  41. data/lib/dm-core/property_set.rb +169 -0
  42. data/lib/dm-core/query.rb +676 -0
  43. data/lib/dm-core/repository.rb +167 -0
  44. data/lib/dm-core/resource.rb +671 -0
  45. data/lib/dm-core/scope.rb +58 -0
  46. data/lib/dm-core/support.rb +7 -0
  47. data/lib/dm-core/support/array.rb +13 -0
  48. data/lib/dm-core/support/assertions.rb +8 -0
  49. data/lib/dm-core/support/errors.rb +23 -0
  50. data/lib/dm-core/support/kernel.rb +11 -0
  51. data/lib/dm-core/support/symbol.rb +41 -0
  52. data/lib/dm-core/transaction.rb +252 -0
  53. data/lib/dm-core/type.rb +160 -0
  54. data/lib/dm-core/type_map.rb +80 -0
  55. data/lib/dm-core/types.rb +19 -0
  56. data/lib/dm-core/types/boolean.rb +7 -0
  57. data/lib/dm-core/types/discriminator.rb +34 -0
  58. data/lib/dm-core/types/object.rb +24 -0
  59. data/lib/dm-core/types/paranoid_boolean.rb +34 -0
  60. data/lib/dm-core/types/paranoid_datetime.rb +33 -0
  61. data/lib/dm-core/types/serial.rb +9 -0
  62. data/lib/dm-core/types/text.rb +10 -0
  63. data/lib/dm-core/version.rb +3 -0
  64. data/script/all +4 -0
  65. data/script/performance.rb +282 -0
  66. data/script/profile.rb +87 -0
  67. data/spec/integration/association_spec.rb +1382 -0
  68. data/spec/integration/association_through_spec.rb +203 -0
  69. data/spec/integration/associations/many_to_many_spec.rb +449 -0
  70. data/spec/integration/associations/many_to_one_spec.rb +163 -0
  71. data/spec/integration/associations/one_to_many_spec.rb +188 -0
  72. data/spec/integration/auto_migrations_spec.rb +413 -0
  73. data/spec/integration/collection_spec.rb +1073 -0
  74. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  75. data/spec/integration/dependency_queue_spec.rb +46 -0
  76. data/spec/integration/model_spec.rb +197 -0
  77. data/spec/integration/mysql_adapter_spec.rb +85 -0
  78. data/spec/integration/postgres_adapter_spec.rb +731 -0
  79. data/spec/integration/property_spec.rb +253 -0
  80. data/spec/integration/query_spec.rb +514 -0
  81. data/spec/integration/repository_spec.rb +61 -0
  82. data/spec/integration/resource_spec.rb +513 -0
  83. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  84. data/spec/integration/sti_spec.rb +273 -0
  85. data/spec/integration/strategic_eager_loading_spec.rb +156 -0
  86. data/spec/integration/transaction_spec.rb +60 -0
  87. data/spec/integration/type_spec.rb +275 -0
  88. data/spec/lib/logging_helper.rb +18 -0
  89. data/spec/lib/mock_adapter.rb +27 -0
  90. data/spec/lib/model_loader.rb +100 -0
  91. data/spec/lib/publicize_methods.rb +28 -0
  92. data/spec/models/content.rb +16 -0
  93. data/spec/models/vehicles.rb +34 -0
  94. data/spec/models/zoo.rb +48 -0
  95. data/spec/spec.opts +3 -0
  96. data/spec/spec_helper.rb +91 -0
  97. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  98. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  99. data/spec/unit/adapters/data_objects_adapter_spec.rb +632 -0
  100. data/spec/unit/adapters/in_memory_adapter_spec.rb +98 -0
  101. data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
  102. data/spec/unit/associations/many_to_many_spec.rb +32 -0
  103. data/spec/unit/associations/many_to_one_spec.rb +159 -0
  104. data/spec/unit/associations/one_to_many_spec.rb +393 -0
  105. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  106. data/spec/unit/associations/relationship_spec.rb +71 -0
  107. data/spec/unit/associations_spec.rb +242 -0
  108. data/spec/unit/auto_migrations_spec.rb +111 -0
  109. data/spec/unit/collection_spec.rb +182 -0
  110. data/spec/unit/data_mapper_spec.rb +35 -0
  111. data/spec/unit/identity_map_spec.rb +126 -0
  112. data/spec/unit/is_spec.rb +80 -0
  113. data/spec/unit/migrator_spec.rb +33 -0
  114. data/spec/unit/model_spec.rb +321 -0
  115. data/spec/unit/naming_conventions_spec.rb +36 -0
  116. data/spec/unit/property_set_spec.rb +90 -0
  117. data/spec/unit/property_spec.rb +753 -0
  118. data/spec/unit/query_spec.rb +571 -0
  119. data/spec/unit/repository_spec.rb +93 -0
  120. data/spec/unit/resource_spec.rb +649 -0
  121. data/spec/unit/scope_spec.rb +142 -0
  122. data/spec/unit/transaction_spec.rb +469 -0
  123. data/spec/unit/type_map_spec.rb +114 -0
  124. data/spec/unit/type_spec.rb +119 -0
  125. data/tasks/ci.rb +36 -0
  126. data/tasks/dm.rb +63 -0
  127. data/tasks/doc.rb +20 -0
  128. data/tasks/gemspec.rb +23 -0
  129. data/tasks/hoe.rb +46 -0
  130. data/tasks/install.rb +20 -0
  131. metadata +215 -0
@@ -0,0 +1,156 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe "Strategic Eager Loading" do
4
+ include LoggingHelper
5
+
6
+ before :all do
7
+ class ::Zoo
8
+ include DataMapper::Resource
9
+ def self.default_repository_name; ADAPTER end
10
+
11
+ property :id, Serial
12
+ property :name, String
13
+
14
+ has n, :exhibits
15
+ end
16
+
17
+ class ::Exhibit
18
+ include DataMapper::Resource
19
+ def self.default_repository_name; ADAPTER end
20
+
21
+ property :id, Serial
22
+ property :name, String
23
+ property :zoo_id, Integer
24
+
25
+ belongs_to :zoo
26
+ has n, :animals
27
+ end
28
+
29
+ class ::Animal
30
+ include DataMapper::Resource
31
+ def self.default_repository_name; ADAPTER end
32
+
33
+ property :id, Serial
34
+ property :name, String
35
+ property :exhibit_id, Integer
36
+
37
+ belongs_to :exhibit
38
+ end
39
+
40
+ [Zoo, Exhibit, Animal].each { |k| k.auto_migrate!(ADAPTER) }
41
+
42
+ repository(ADAPTER) do
43
+ Zoo.create(:name => "Dallas Zoo")
44
+ Exhibit.create(:name => "Primates", :zoo_id => 1)
45
+ Animal.create(:name => "Chimpanzee", :exhibit_id => 1)
46
+ Animal.create(:name => "Orangutan", :exhibit_id => 1)
47
+
48
+ Zoo.create(:name => "San Diego")
49
+ Exhibit.create(:name => "Aviary", :zoo_id => 2)
50
+ Exhibit.create(:name => "Insectorium", :zoo_id => 2)
51
+ Exhibit.create(:name => "Bears", :zoo_id => 2)
52
+ Animal.create(:name => "Bald Eagle", :exhibit_id => 2)
53
+ Animal.create(:name => "Parakeet", :exhibit_id => 2)
54
+ Animal.create(:name => "Roach", :exhibit_id => 3)
55
+ Animal.create(:name => "Brown Bear", :exhibit_id => 4)
56
+ end
57
+ end
58
+
59
+ it "should eager load children" do
60
+ zoo_ids = Zoo.all.map { |z| z.key }
61
+ exhibit_ids = Exhibit.all.map { |e| e.key }
62
+
63
+ repository(ADAPTER) do
64
+ zoos = Zoo.all.entries # load all zoos
65
+ dallas = zoos.find { |z| z.name == 'Dallas Zoo' }
66
+
67
+ logger do |log|
68
+ dallas.exhibits.entries # load all exhibits for zoos in identity_map
69
+ dallas.exhibits.size.should == 1
70
+
71
+ log.readlines.size.should == 1
72
+
73
+ repository.identity_map(Zoo).keys.sort.should == zoo_ids
74
+ repository.identity_map(Exhibit).keys.sort.should == exhibit_ids
75
+ end
76
+
77
+ logger do |log|
78
+ zoos.each { |zoo| zoo.exhibits.entries } # issues no queries
79
+ log.readlines.should be_empty
80
+ end
81
+
82
+ dallas.exhibits << Exhibit.new(:name => "Reptiles")
83
+ dallas.exhibits.size.should == 2
84
+ dallas.save
85
+ end
86
+ repository(ADAPTER) do
87
+ Zoo.first.exhibits.size.should == 2
88
+ end
89
+ end
90
+
91
+ it "should not eager load children when a query is provided" do
92
+ repository(ADAPTER) do
93
+ dallas = Zoo.all.entries.find { |z| z.name == 'Dallas Zoo' }
94
+ exhibits = dallas.exhibits.entries # load all exhibits
95
+
96
+ reptiles, primates = nil, nil
97
+
98
+ logger do |log|
99
+ reptiles = dallas.exhibits(:name => 'Reptiles')
100
+ reptiles.size.should == 1
101
+
102
+ log.readlines.size.should == 1
103
+ end
104
+
105
+ logger do |log|
106
+ primates = dallas.exhibits(:name => 'Primates')
107
+ primates.size.should == 1
108
+ primates.should_not == reptiles
109
+
110
+ log.readlines.size.should == 1
111
+ end
112
+ end
113
+ end
114
+
115
+ it "should eager load parents" do
116
+ animal_ids = Animal.all.map { |a| a.key }
117
+ exhibit_ids = Exhibit.all.map { |e| e.key }.sort
118
+ exhibit_ids.pop # remove Reptile exhibit, which has no Animals
119
+
120
+ repository(ADAPTER) do
121
+ animals = Animal.all.entries
122
+ bear = animals.find { |a| a.name == 'Brown Bear' }
123
+
124
+ logger do |log|
125
+ bear.exhibit
126
+
127
+ repository.identity_map(Animal).keys.sort.should == animal_ids
128
+ repository.identity_map(Exhibit).keys.sort.should == exhibit_ids
129
+
130
+ log.readlines.size.should == 1
131
+ end
132
+ end
133
+ end
134
+
135
+ it "should not eager load parents when parent is in IM" do
136
+ repository(ADAPTER) do
137
+ animal = Animal.first
138
+ exhibit = Exhibit.get(1) # load exhibit into IM
139
+
140
+ logger do |log|
141
+ animal.exhibit # load exhibit from IM
142
+ log.readlines.should be_empty
143
+ end
144
+
145
+ repository.identity_map(Exhibit).keys.should == [exhibit.key]
146
+ end
147
+ end
148
+
149
+ it "should return a Collection when no children" do
150
+ Zoo.create(:name => 'Portland')
151
+
152
+ Zoo.all.each do |zoo|
153
+ zoo.exhibits.should be_kind_of(DataMapper::Collection)
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,60 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ # transaction capable adapters
4
+ ADAPTERS = []
5
+ ADAPTERS << :postgres if HAS_POSTGRES
6
+ ADAPTERS << :mysql if HAS_MYSQL
7
+ ADAPTERS << :sqlite3 if HAS_SQLITE3
8
+
9
+ if ADAPTERS.any?
10
+ class Sputnik
11
+ include DataMapper::Resource
12
+
13
+ property :id, Serial
14
+ property :name, DM::Text
15
+ end
16
+
17
+ describe DataMapper::Transaction do
18
+ before :all do
19
+ @repositories = []
20
+
21
+ ADAPTERS.each do |name|
22
+ @repositories << repository(name)
23
+ end
24
+ end
25
+
26
+ before :each do
27
+ ADAPTERS.each do |name|
28
+ Sputnik.auto_migrate!(name)
29
+ end
30
+ end
31
+
32
+ it "should commit changes to all involved adapters on a two phase commit" do
33
+ DataMapper::Transaction.new(*@repositories) do
34
+ ADAPTERS.each do |name|
35
+ repository(name) { Sputnik.create(:name => 'hepp') }
36
+ end
37
+ end
38
+
39
+ ADAPTERS.each do |name|
40
+ repository(name) { Sputnik.all.size.should == 1 }
41
+ end
42
+ end
43
+
44
+ it "should not commit any changes if the block raises an exception" do
45
+ lambda do
46
+ DataMapper::Transaction.new(*@repositories) do
47
+ ADAPTERS.each do |name|
48
+ repository(name) { Sputnik.create(:name => 'hepp') }
49
+ end
50
+ raise "plur"
51
+ end
52
+ end.should raise_error(Exception, /plur/)
53
+
54
+ ADAPTERS.each do |name|
55
+ repository(name) { Sputnik.all.size.should == 0 }
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,275 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ # if RUBY_VERSION >= '1.9.0'
4
+ # require 'csv'
5
+ # else
6
+ # gem 'fastercsv', '~>1.4.0'
7
+ # require 'fastercsv'
8
+ # end
9
+
10
+ if ADAPTER
11
+ module ::TypeTests
12
+ class Impostor < DataMapper::Type
13
+ primitive String
14
+ end
15
+
16
+ class Coconut
17
+ include DataMapper::Resource
18
+
19
+ storage_names[ADAPTER] = 'coconuts'
20
+
21
+ def self.default_repository_name
22
+ ADAPTER
23
+ end
24
+
25
+ property :id, Serial
26
+ property :faked, Impostor
27
+ property :active, Boolean
28
+ property :note, Text
29
+ end
30
+ end
31
+
32
+ class ::Lemon
33
+ include DataMapper::Resource
34
+
35
+ def self.default_repository_name
36
+ ADAPTER
37
+ end
38
+
39
+ property :id, Serial
40
+ property :color, String
41
+ property :deleted_at, DataMapper::Types::ParanoidDateTime
42
+ end
43
+
44
+ class ::Lime
45
+ include DataMapper::Resource
46
+
47
+ def self.default_repository_name
48
+ ADAPTER
49
+ end
50
+
51
+ property :id, Serial
52
+ property :color, String
53
+ property :deleted_at, DataMapper::Types::ParanoidBoolean
54
+ end
55
+
56
+ describe DataMapper::Type, "with #{ADAPTER}" do
57
+ before do
58
+ TypeTests::Coconut.auto_migrate!(ADAPTER)
59
+
60
+ @document = <<-EOS.margin
61
+ NAME, RATING, CONVENIENCE
62
+ Freebird's, 3, 3
63
+ Whataburger, 1, 5
64
+ Jimmy John's, 3, 4
65
+ Mignon, 5, 2
66
+ Fuzi Yao's, 5, 1
67
+ Blue Goose, 5, 1
68
+ EOS
69
+
70
+ @stuff = YAML::dump({ 'Happy Cow!' => true, 'Sad Cow!' => false })
71
+
72
+ @active = true
73
+ @note = "This is a note on our ol' guy bob"
74
+ end
75
+
76
+ it "should instantiate an object with custom types" do
77
+ coconut = TypeTests::Coconut.new(:faked => 'bob', :active => @active, :note => @note)
78
+ coconut.faked.should == 'bob'
79
+ coconut.active.should be_a_kind_of(TrueClass)
80
+ coconut.note.should be_a_kind_of(String)
81
+ end
82
+
83
+ it "should CRUD an object with custom types" do
84
+ repository(ADAPTER) do
85
+ coconut = TypeTests::Coconut.new(:faked => 'bob', :active => @active, :note => @note)
86
+ coconut.save.should be_true
87
+ coconut.id.should_not be_nil
88
+
89
+ fred = TypeTests::Coconut.get!(coconut.id)
90
+ fred.faked.should == 'bob'
91
+ fred.active.should be_a_kind_of(TrueClass)
92
+ fred.note.should be_a_kind_of(String)
93
+
94
+ note = "Seems like bob is just mockin' around"
95
+ fred.note = note
96
+
97
+ fred.save.should be_true
98
+
99
+ active = false
100
+ fred.active = active
101
+
102
+ fred.save.should be_true
103
+
104
+ # Can't call coconut.reload since coconut.collection isn't setup.
105
+ mac = TypeTests::Coconut.get!(fred.id)
106
+ mac.active.should == active
107
+ mac.note.should == note
108
+ end
109
+ end
110
+
111
+ it "should respect paranoia with a datetime" do
112
+ Lemon.auto_migrate!(ADAPTER)
113
+
114
+ lemon = nil
115
+
116
+ repository(ADAPTER) do |repository|
117
+ lemon = Lemon.new
118
+ lemon.color = 'green'
119
+
120
+ lemon.save
121
+ lemon.destroy
122
+
123
+ lemon.deleted_at.should be_kind_of(DateTime)
124
+ end
125
+
126
+ repository(ADAPTER) do |repository|
127
+ Lemon.all.should be_empty
128
+ Lemon.get(lemon.id).should be_nil
129
+ end
130
+ end
131
+
132
+ it "should provide access to paranoid items with DateTime" do
133
+ Lemon.auto_migrate!(ADAPTER)
134
+
135
+ lemon = nil
136
+
137
+ repository(ADAPTER) do |repository|
138
+ %w(red green yellow blue).each do |color|
139
+ Lemon.create(:color => color)
140
+ end
141
+
142
+ Lemon.all.size.should == 4
143
+ Lemon.first.destroy
144
+ Lemon.all.size.should == 3
145
+ Lemon.with_deleted{Lemon.all.size.should == 1}
146
+ end
147
+ end
148
+
149
+ it "should set paranoid datetime to a date time" do
150
+ tmp = (DateTime.now - 0.5)
151
+ dt = DateTime.now
152
+ DateTime.stub!(:now).and_return(tmp)
153
+
154
+ repository(ADAPTER) do |repository|
155
+ lemon = Lemon.new
156
+ lemon.color = 'green'
157
+ lemon.save
158
+ lemon.destroy
159
+ lemon.deleted_at.should == tmp
160
+ end
161
+ end
162
+
163
+ it "should respect paranoia with a boolean" do
164
+ Lime.auto_migrate!(ADAPTER)
165
+
166
+ lime = nil
167
+
168
+ repository(ADAPTER) do |repository|
169
+ lime = Lime.new
170
+ lime.color = 'green'
171
+
172
+ lime.save
173
+ lime.destroy
174
+
175
+ lime.deleted_at.should be_kind_of(TrueClass)
176
+ end
177
+
178
+ repository(ADAPTER) do |repository|
179
+ Lime.all.should be_empty
180
+ Lime.get(lime.id).should be_nil
181
+ end
182
+ end
183
+
184
+ it "should provide access to paranoid items with Boolean" do
185
+ Lime.auto_migrate!(ADAPTER)
186
+
187
+ lemon = nil
188
+
189
+ repository(ADAPTER) do |repository|
190
+ %w(red green yellow blue).each do |color|
191
+ Lime.create(:color => color)
192
+ end
193
+
194
+ Lime.all.size.should == 4
195
+ Lime.first.destroy
196
+ Lime.all.size.should == 3
197
+ Lime.with_deleted{Lime.all.size.should == 1}
198
+ end
199
+ end
200
+
201
+ describe "paranoid types across repositories" do
202
+ before(:all) do
203
+ DataMapper::Repository.adapters[:alternate_paranoid] = repository(ADAPTER).adapter.dup
204
+
205
+ Object.send(:remove_const, :Orange) if defined?(Orange)
206
+ class ::Orange
207
+ include DataMapper::Resource
208
+
209
+ def self.default_repository_name
210
+ ADAPTER
211
+ end
212
+
213
+ property :id, Serial
214
+ property :color, String
215
+
216
+ repository(:alternate_paranoid) do
217
+ property :deleted, DataMapper::Types::ParanoidBoolean
218
+ property :deleted_at, DataMapper::Types::ParanoidDateTime
219
+ end
220
+ end
221
+
222
+ repository(:alternate_paranoid){Orange.auto_migrate!}
223
+ end
224
+
225
+ before(:each) do
226
+ %w(red orange blue green).each{|color| o = Orange.create(:color => color)}
227
+ end
228
+
229
+ after(:each) do
230
+ Orange.repository.adapter.execute("DELETE FROM oranges")
231
+ end
232
+
233
+ it "should setup the correct objects for the spec" do
234
+ repository(:alternate_paranoid){Orange.all.should have(4).items}
235
+ end
236
+
237
+ it "should allow access the the default repository" do
238
+ Orange.all.should have(4).items
239
+ end
240
+
241
+ it "should mark the objects as deleted in the alternate_paranoid repository" do
242
+ repository(:alternate_paranoid) do
243
+ Orange.first.destroy
244
+ Orange.all.should have(3).items
245
+ Orange.find_by_sql("SELECT * FROM oranges").should have(4).items
246
+ end
247
+ end
248
+
249
+ it "should mark the objects as deleted in the alternate_paranoid repository but ignore it in the #{ADAPTER} repository" do
250
+ repository(:alternate_paranoid) do
251
+ Orange.first.destroy
252
+ end
253
+ Orange.all.should have(4).items
254
+ end
255
+
256
+ it "should raise an error when trying to destroy from a repository that is not paranoid" do
257
+ lambda do
258
+ Orange.first.destroy
259
+ end.should raise_error(ArgumentError)
260
+ end
261
+
262
+ it "should set all paranoid attributes on delete" do
263
+ repository(:alternate_paranoid) do
264
+ orange = Orange.first
265
+ orange.deleted.should be_false
266
+ orange.deleted_at.should be_nil
267
+ orange.destroy
268
+
269
+ orange.deleted.should be_true
270
+ orange.deleted_at.should be_a_kind_of(DateTime)
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end