dm-core 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/CHANGELOG +144 -0
  2. data/FAQ +74 -0
  3. data/MIT-LICENSE +22 -0
  4. data/QUICKLINKS +12 -0
  5. data/README +143 -0
  6. data/lib/dm-core.rb +213 -0
  7. data/lib/dm-core/adapters.rb +4 -0
  8. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  9. data/lib/dm-core/adapters/data_objects_adapter.rb +701 -0
  10. data/lib/dm-core/adapters/mysql_adapter.rb +132 -0
  11. data/lib/dm-core/adapters/postgres_adapter.rb +179 -0
  12. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  13. data/lib/dm-core/associations.rb +172 -0
  14. data/lib/dm-core/associations/many_to_many.rb +138 -0
  15. data/lib/dm-core/associations/many_to_one.rb +101 -0
  16. data/lib/dm-core/associations/one_to_many.rb +275 -0
  17. data/lib/dm-core/associations/one_to_one.rb +61 -0
  18. data/lib/dm-core/associations/relationship.rb +116 -0
  19. data/lib/dm-core/associations/relationship_chain.rb +74 -0
  20. data/lib/dm-core/auto_migrations.rb +64 -0
  21. data/lib/dm-core/collection.rb +604 -0
  22. data/lib/dm-core/hook.rb +11 -0
  23. data/lib/dm-core/identity_map.rb +45 -0
  24. data/lib/dm-core/is.rb +16 -0
  25. data/lib/dm-core/logger.rb +233 -0
  26. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  27. data/lib/dm-core/migrator.rb +29 -0
  28. data/lib/dm-core/model.rb +399 -0
  29. data/lib/dm-core/naming_conventions.rb +52 -0
  30. data/lib/dm-core/property.rb +611 -0
  31. data/lib/dm-core/property_set.rb +158 -0
  32. data/lib/dm-core/query.rb +590 -0
  33. data/lib/dm-core/repository.rb +159 -0
  34. data/lib/dm-core/resource.rb +618 -0
  35. data/lib/dm-core/scope.rb +35 -0
  36. data/lib/dm-core/support.rb +7 -0
  37. data/lib/dm-core/support/array.rb +13 -0
  38. data/lib/dm-core/support/assertions.rb +8 -0
  39. data/lib/dm-core/support/errors.rb +23 -0
  40. data/lib/dm-core/support/kernel.rb +7 -0
  41. data/lib/dm-core/support/symbol.rb +41 -0
  42. data/lib/dm-core/transaction.rb +267 -0
  43. data/lib/dm-core/type.rb +160 -0
  44. data/lib/dm-core/type_map.rb +80 -0
  45. data/lib/dm-core/types.rb +19 -0
  46. data/lib/dm-core/types/boolean.rb +7 -0
  47. data/lib/dm-core/types/discriminator.rb +32 -0
  48. data/lib/dm-core/types/object.rb +20 -0
  49. data/lib/dm-core/types/paranoid_boolean.rb +23 -0
  50. data/lib/dm-core/types/paranoid_datetime.rb +22 -0
  51. data/lib/dm-core/types/serial.rb +9 -0
  52. data/lib/dm-core/types/text.rb +10 -0
  53. data/spec/integration/association_spec.rb +1215 -0
  54. data/spec/integration/association_through_spec.rb +150 -0
  55. data/spec/integration/associations/many_to_many_spec.rb +171 -0
  56. data/spec/integration/associations/many_to_one_spec.rb +123 -0
  57. data/spec/integration/associations/one_to_many_spec.rb +66 -0
  58. data/spec/integration/auto_migrations_spec.rb +398 -0
  59. data/spec/integration/collection_spec.rb +1015 -0
  60. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  61. data/spec/integration/model_spec.rb +68 -0
  62. data/spec/integration/mysql_adapter_spec.rb +85 -0
  63. data/spec/integration/postgres_adapter_spec.rb +732 -0
  64. data/spec/integration/property_spec.rb +224 -0
  65. data/spec/integration/query_spec.rb +376 -0
  66. data/spec/integration/repository_spec.rb +57 -0
  67. data/spec/integration/resource_spec.rb +324 -0
  68. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  69. data/spec/integration/sti_spec.rb +185 -0
  70. data/spec/integration/transaction_spec.rb +75 -0
  71. data/spec/integration/type_spec.rb +149 -0
  72. data/spec/lib/mock_adapter.rb +27 -0
  73. data/spec/spec_helper.rb +112 -0
  74. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  75. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  76. data/spec/unit/adapters/data_objects_adapter_spec.rb +627 -0
  77. data/spec/unit/adapters/postgres_adapter_spec.rb +125 -0
  78. data/spec/unit/associations/many_to_many_spec.rb +14 -0
  79. data/spec/unit/associations/many_to_one_spec.rb +138 -0
  80. data/spec/unit/associations/one_to_many_spec.rb +385 -0
  81. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  82. data/spec/unit/associations/relationship_spec.rb +67 -0
  83. data/spec/unit/associations_spec.rb +205 -0
  84. data/spec/unit/auto_migrations_spec.rb +110 -0
  85. data/spec/unit/collection_spec.rb +174 -0
  86. data/spec/unit/data_mapper_spec.rb +21 -0
  87. data/spec/unit/identity_map_spec.rb +126 -0
  88. data/spec/unit/is_spec.rb +80 -0
  89. data/spec/unit/migrator_spec.rb +33 -0
  90. data/spec/unit/model_spec.rb +339 -0
  91. data/spec/unit/naming_conventions_spec.rb +28 -0
  92. data/spec/unit/property_set_spec.rb +96 -0
  93. data/spec/unit/property_spec.rb +447 -0
  94. data/spec/unit/query_spec.rb +485 -0
  95. data/spec/unit/repository_spec.rb +93 -0
  96. data/spec/unit/resource_spec.rb +557 -0
  97. data/spec/unit/scope_spec.rb +131 -0
  98. data/spec/unit/transaction_spec.rb +493 -0
  99. data/spec/unit/type_map_spec.rb +114 -0
  100. data/spec/unit/type_spec.rb +119 -0
  101. metadata +187 -0
@@ -0,0 +1,93 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe DataMapper::Repository do
4
+ before do
5
+ @adapter = mock('adapter')
6
+ @identity_map = mock('identity map', :[]= => nil)
7
+ @identity_maps = mock('identity maps', :[] => @identity_map)
8
+
9
+ @repository = repository(:mock)
10
+ @repository.stub!(:adapter).and_return(@adapter)
11
+
12
+ # TODO: stub out other external dependencies in repository
13
+ end
14
+
15
+ describe "managing transactions" do
16
+ it "should create a new Transaction with itself as argument when #transaction is called" do
17
+ transaction = mock('transaction')
18
+ DataMapper::Transaction.should_receive(:new).with(@repository).and_return(transaction)
19
+ @repository.transaction.should == transaction
20
+ end
21
+ end
22
+
23
+ it 'should provide .storage_exists?' do
24
+ @repository.should respond_to(:storage_exists?)
25
+ end
26
+
27
+ it '.storage_exists? should whether or not the storage exists' do
28
+ @adapter.should_receive(:storage_exists?).with(:vegetable).and_return(true)
29
+
30
+ @repository.storage_exists?(:vegetable).should == true
31
+ end
32
+
33
+ it "should provide persistance methods" do
34
+ @repository.should respond_to(:create)
35
+ @repository.should respond_to(:read_many)
36
+ @repository.should respond_to(:read_one)
37
+ @repository.should respond_to(:update)
38
+ @repository.should respond_to(:delete)
39
+ end
40
+
41
+ it "should be reused in inner scope" do
42
+ DataMapper.repository(:default) do |outer_repos|
43
+ DataMapper.repository(:default) do |inner_repos|
44
+ outer_repos.object_id.should == inner_repos.object_id
45
+ end
46
+ end
47
+ end
48
+
49
+ it 'should provide default_name' do
50
+ DataMapper::Repository.should respond_to(:default_name)
51
+ end
52
+
53
+ it 'should return :default for default_name' do
54
+ DataMapper::Repository.default_name.should == :default
55
+ end
56
+
57
+ describe "#migrate!" do
58
+ it "should call DataMapper::Migrator.migrate with itself as the repository argument" do
59
+ DataMapper::Migrator.should_receive(:migrate).with(@repository.name)
60
+
61
+ @repository.migrate!
62
+ end
63
+ end
64
+
65
+ describe "#auto_migrate!" do
66
+ it "should call DataMapper::AutoMigrator.auto_migrate with itself as the repository argument" do
67
+ DataMapper::AutoMigrator.should_receive(:auto_migrate).with(@repository.name)
68
+
69
+ @repository.auto_migrate!
70
+ end
71
+ end
72
+
73
+ describe "#auto_upgrade!" do
74
+ it "should call DataMapper::AutoMigrator.auto_upgrade with itself as the repository argument" do
75
+ DataMapper::AutoMigrator.should_receive(:auto_upgrade).with(@repository.name)
76
+
77
+ @repository.auto_upgrade!
78
+ end
79
+ end
80
+
81
+ describe "#map" do
82
+ it "should call type_map.map with the arguments" do
83
+ type_map = mock('type map')
84
+
85
+ @adapter.class.should_receive(:type_map).and_return(type_map)
86
+ DataMapper::TypeMap.should_receive(:new).with(type_map).and_return(type_map)
87
+
88
+ type_map.should_receive(:map).with(:type, :arg)
89
+
90
+ @repository.map(:type, :arg)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,557 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ class Planet
4
+ include DataMapper::Resource
5
+
6
+ storage_names[:legacy] = "dying_planets"
7
+
8
+ property :id, Integer, :key => true
9
+ property :name, String
10
+ property :age, Integer
11
+ property :core, String, :private => true
12
+ property :type, Discriminator
13
+ property :data, Object
14
+
15
+ repository(:legacy) do
16
+ property :cowabunga, String
17
+ end
18
+
19
+ def age
20
+ attribute_get(:age)
21
+ end
22
+
23
+ def to_s
24
+ name
25
+ end
26
+ end
27
+
28
+ class Moon
29
+ end
30
+
31
+ class BlackHole
32
+ include DataMapper::Resource
33
+
34
+ property :id, Integer, :key => true
35
+ property :data, Object, :reader => :private
36
+ end
37
+
38
+ class LegacyStar
39
+ include DataMapper::Resource
40
+ def self.default_repository_name
41
+ :legacy
42
+ end
43
+ end
44
+
45
+ class Phone
46
+ include DataMapper::Resource
47
+
48
+ property :name, String, :key => true
49
+ property :awesomeness, Integer
50
+ end
51
+
52
+ class Fruit
53
+ include DataMapper::Resource
54
+
55
+ property :id, Integer, :key => true
56
+ property :name, String
57
+ end
58
+
59
+ class Grain
60
+ include DataMapper::Resource
61
+
62
+ property :id, Serial
63
+ property :name, String, :default => 'wheat'
64
+ end
65
+
66
+ class Vegetable
67
+ include DataMapper::Resource
68
+
69
+ property :id, Serial
70
+ property :name, String
71
+ end
72
+
73
+ class Banana < Fruit
74
+ property :type, Discriminator
75
+ end
76
+
77
+ # rSpec completely FUBARs everything if you give it a Module here.
78
+ # So we give it a String of the module name instead.
79
+ # DO NOT CHANGE THIS!
80
+ describe "DataMapper::Resource" do
81
+ it 'should provide #attribute_get' do
82
+ Planet.new.should respond_to(:attribute_get)
83
+ end
84
+
85
+ describe '#attribute_get' do
86
+ it 'should delegate to Property#get' do
87
+ planet = Planet.new
88
+ Planet.properties[:age].should_receive(:get).with(planet).and_return(1)
89
+ planet.age.should == 1
90
+ end
91
+ end
92
+
93
+ it 'should provide #attribute_set' do
94
+ Planet.new.should respond_to(:attribute_set)
95
+ end
96
+
97
+ describe '#attribute_set' do
98
+ it 'should typecast the value' do
99
+ Planet.properties[:age].should_receive(:typecast).with('1').and_return(1)
100
+ planet = Planet.new
101
+ planet.age = '1'
102
+ planet.age.should == 1
103
+ end
104
+
105
+ it 'should delegate to Property#set' do
106
+ planet = Planet.new
107
+ Planet.properties[:age].should_receive(:set).with(planet, 1).and_return(1)
108
+ planet.age = 1
109
+ end
110
+ end
111
+
112
+ describe '#attributes' do
113
+ it 'should return a hash of attribute-names and values' do
114
+ vegetable = Vegetable.new
115
+ vegetable.attributes.should == {:name => nil, :id => nil}
116
+ vegetable.name = "carot"
117
+ vegetable.attributes.should == {:name => "carot", :id => nil}
118
+ end
119
+
120
+ it 'should not include private attributes' do
121
+ hole = BlackHole.new
122
+ hole.attributes.should == {:id => nil}
123
+ end
124
+ end
125
+
126
+ it 'should provide #save' do
127
+ Planet.new.should respond_to(:save)
128
+ end
129
+
130
+ describe '#save' do
131
+ before do
132
+ @adapter = repository(:default).adapter
133
+ end
134
+
135
+ describe 'with a new resource' do
136
+ it 'should set defaults before create' do
137
+ resource = Grain.new
138
+
139
+ resource.should_not be_dirty
140
+ resource.should be_new_record
141
+ resource.instance_variable_get('@name').should be_nil
142
+
143
+ @adapter.should_receive(:create).with([ resource ]).and_return(1)
144
+
145
+ resource.save.should be_true
146
+
147
+ resource.instance_variable_get('@name').should == 'wheat'
148
+ end
149
+
150
+ it 'should create when dirty' do
151
+ resource = Vegetable.new(:id => 1, :name => 'Potato')
152
+
153
+ resource.should be_dirty
154
+ resource.should be_new_record
155
+
156
+ @adapter.should_receive(:create).with([ resource ]).and_return(1)
157
+
158
+ resource.save.should be_true
159
+ end
160
+
161
+ it 'should create when non-dirty, and it has a serial key' do
162
+ resource = Vegetable.new
163
+
164
+ resource.should_not be_dirty
165
+ resource.should be_new_record
166
+ resource.model.key.any? { |p| p.serial? }.should be_true
167
+
168
+ @adapter.should_receive(:create).with([ resource ]).and_return(1)
169
+
170
+ resource.save.should be_true
171
+ end
172
+
173
+ it 'should not create when non-dirty, and is has a non-serial key' do
174
+ resource = Fruit.new
175
+
176
+ resource.should_not be_dirty
177
+ resource.should be_new_record
178
+ resource.model.key.any? { |p| p.serial? }.should be_false
179
+
180
+ @adapter.should_not_receive(:create)
181
+
182
+ resource.save.should be_false
183
+ end
184
+ end
185
+
186
+ describe 'with an existing resource' do
187
+ it 'should update' do
188
+ resource = Vegetable.new(:name => 'Potato')
189
+ resource.instance_variable_set('@new_record', false)
190
+
191
+ resource.should be_dirty
192
+ resource.should_not be_new_record
193
+
194
+ @adapter.should_receive(:update).with(resource.dirty_attributes, resource.to_query).and_return(1)
195
+
196
+ resource.save.should be_true
197
+ end
198
+ end
199
+ end
200
+
201
+ it "should be able to overwrite to_s" do
202
+ Planet.new(:name => 'Mercury').to_s.should == 'Mercury'
203
+ end
204
+
205
+ describe "storage names" do
206
+ it "should use its class name by default" do
207
+ Planet.storage_name.should == "planets"
208
+ end
209
+
210
+ it "should allow changing using #default_storage_name" do
211
+ Planet.class_eval <<-EOF.margin
212
+ @storage_names.clear
213
+ def self.default_storage_name
214
+ "Superplanet"
215
+ end
216
+ EOF
217
+
218
+ Planet.storage_name.should == "superplanets"
219
+ Planet.class_eval <<-EOF.margin
220
+ @storage_names.clear
221
+ def self.default_storage_name
222
+ self.name
223
+ end
224
+ EOF
225
+ end
226
+ end
227
+
228
+ it "should require a key" do
229
+ lambda do
230
+ DataMapper::Model.new("stuff") do
231
+ property :name, String
232
+ end.new
233
+ end.should raise_error(DataMapper::IncompleteResourceError)
234
+ end
235
+
236
+ it "should hold repository-specific properties" do
237
+ Planet.properties(:legacy).should have_property(:cowabunga)
238
+ Planet.properties.should_not have_property(:cowabunga)
239
+ end
240
+
241
+ it "should track the classes that include it" do
242
+ DataMapper::Resource.descendants.clear
243
+ klass = Class.new { include DataMapper::Resource }
244
+ DataMapper::Resource.descendants.should == Set.new([klass])
245
+ end
246
+
247
+ it "should return an instance of the created object" do
248
+ Planet.create!(:name => 'Venus', :age => 1_000_000, :core => nil, :id => 42).should be_a_kind_of(Planet)
249
+ end
250
+
251
+ it 'should provide persistance methods' do
252
+ planet = Planet.new
253
+ planet.should respond_to(:new_record?)
254
+ planet.should respond_to(:save)
255
+ planet.should respond_to(:destroy)
256
+ end
257
+
258
+ it "should have attributes" do
259
+ attributes = { :name => 'Jupiter', :age => 1_000_000, :core => nil, :id => 42, :type => Planet, :data => nil }
260
+ jupiter = Planet.new(attributes)
261
+ jupiter.attributes.should == attributes
262
+ end
263
+
264
+ it "should be able to set attributes" do
265
+ attributes = { :name => 'Jupiter', :age => 1_000_000, :core => nil, :id => 42, :type => Planet, :data => nil }
266
+ jupiter = Planet.new(attributes)
267
+ jupiter.attributes.should == attributes
268
+ jupiter.attributes = attributes.merge(:core => 'Magma')
269
+ jupiter.attributes.should == attributes
270
+
271
+ jupiter.update_attributes({ :core => "Toast", :type => "Bob" }, :core).should be_true
272
+ jupiter.core.should == "Toast"
273
+ jupiter.type.should_not == "Bob"
274
+ end
275
+
276
+ it "should not mark attributes dirty if there similar after update" do
277
+ jupiter = Planet.new(:name => 'Jupiter', :age => 1_000_000, :core => nil, :id => 42, :data => { :a => "Yeah!" })
278
+ jupiter.save.should be_true
279
+
280
+ # discriminator will be set automatically
281
+ jupiter.type.should == Planet
282
+
283
+ jupiter.attributes = { :name => 'Jupiter', :age => 1_000_000, :core => nil, :data => { :a => "Yeah!" } }
284
+
285
+ jupiter.attribute_dirty?(:name).should be_false
286
+ jupiter.attribute_dirty?(:age).should be_false
287
+ jupiter.attribute_dirty?(:core).should be_false
288
+ jupiter.attribute_dirty?(:data).should be_false
289
+
290
+ jupiter.dirty?.should be_false
291
+ end
292
+
293
+ it "should not mark attributes dirty if they are similar after typecasting" do
294
+ jupiter = Planet.new(:name => 'Jupiter', :age => 1_000_000, :core => nil, :id => 42, :type => nil)
295
+ jupiter.save.should be_true
296
+ jupiter.dirty?.should be_false
297
+
298
+ jupiter.age = '1_000_000'
299
+ jupiter.attribute_dirty?(:age).should be_false
300
+ jupiter.dirty?.should be_false
301
+ end
302
+
303
+ it "should track attributes" do
304
+
305
+ # So attribute tracking is a feature of the Resource,
306
+ # not the Property. Properties are class-level declarations.
307
+ # Instance-level operations like this happen in Resource with methods
308
+ # and ivars it sets up. Like a @dirty_attributes Array for example to
309
+ # track dirty attributes.
310
+
311
+ mars = Planet.new :name => 'Mars'
312
+ # #attribute_loaded? and #attribute_dirty? are a bit verbose,
313
+ # but I like the consistency and grouping of the methods.
314
+
315
+ # initialize-set values are dirty as well. DM sets ivars
316
+ # directly when materializing, so an ivar won't exist
317
+ # if the value wasn't loaded by DM initially. Touching that
318
+ # ivar at all will declare it, so at that point it's loaded.
319
+ # This means #attribute_loaded?'s implementation could be very
320
+ # similar (if not identical) to:
321
+ # def attribute_loaded?(name)
322
+ # instance_variable_defined?("@#{name}")
323
+ # end
324
+ mars.attribute_loaded?(:name).should be_true
325
+ mars.attribute_dirty?(:id).should be_false
326
+ mars.attribute_dirty?(:name).should be_true
327
+ mars.attribute_loaded?(:age).should be_false
328
+ mars.attribute_dirty?(:data).should be_false
329
+
330
+ mars.age.should be_nil
331
+
332
+ # So accessing a value should ensure it's loaded.
333
+ # XXX: why? if the @ivar isn't set, which it wouldn't be in this
334
+ # case because mars is a new_record?, then perhaps it should return
335
+ # false
336
+ # mars.attribute_loaded?(:age).should be_true
337
+
338
+ # A value should be able to be both loaded and nil.
339
+ mars.age.should be_nil
340
+
341
+ # Unless you call #[]= it's not dirty.
342
+ mars.attribute_dirty?(:age).should be_false
343
+
344
+ mars.age = 30
345
+ mars.data = { :a => "Yeah!" }
346
+
347
+ # Obviously. :-)
348
+ mars.attribute_dirty?(:age).should be_true
349
+ mars.attribute_dirty?(:data).should be_true
350
+ end
351
+
352
+ it "should mark the key as dirty, if it is a natural key and has been set" do
353
+ phone = Phone.new
354
+ phone.name = 'iPhone'
355
+ phone.attribute_dirty?(:name).should be_true
356
+ end
357
+
358
+ it 'should return the dirty attributes' do
359
+ pluto = Planet.new(:name => 'Pluto', :age => 500_000)
360
+ pluto.attribute_dirty?(:name).should be_true
361
+ pluto.attribute_dirty?(:age).should be_true
362
+ end
363
+
364
+ it 'should overwite old dirty attributes with new ones' do
365
+ pluto = Planet.new(:name => 'Pluto', :age => 500_000)
366
+ pluto.dirty_attributes.size.should == 2
367
+ pluto.attribute_dirty?(:name).should be_true
368
+ pluto.attribute_dirty?(:age).should be_true
369
+ pluto.name = "pluto"
370
+ pluto.dirty_attributes.size.should == 2
371
+ pluto.attribute_dirty?(:name).should be_true
372
+ pluto.attribute_dirty?(:age).should be_true
373
+ end
374
+
375
+ it 'should provide a key' do
376
+ Planet.new.should respond_to(:key)
377
+ end
378
+
379
+ it 'should store and retrieve default values' do
380
+ Planet.property(:satellite_count, Integer, :default => 0)
381
+ # stupid example but it's realiable and works
382
+ Planet.property(:orbit_period, Float, :default => lambda { |r,p| p.name.to_s.length })
383
+ earth = Planet.new(:name => 'Earth')
384
+ earth.satellite_count.should == 0
385
+ earth.orbit_period.should == 12
386
+ earth.satellite_count = 2
387
+ earth.satellite_count.should == 2
388
+ earth.orbit_period = 365.26
389
+ earth.orbit_period.should == 365.26
390
+ end
391
+
392
+ describe "#reload_attributes" do
393
+ it 'should call collection.reload if not a new record' do
394
+ planet = Planet.new(:name => 'Omicron Persei VIII')
395
+ planet.stub!(:new_record?).and_return(false)
396
+
397
+ collection = mock('collection')
398
+ collection.should_receive(:reload).with(:fields => [:name]).once
399
+
400
+ planet.stub!(:collection).and_return(collection)
401
+ planet.reload_attributes(:name)
402
+ end
403
+
404
+ it 'should not call collection.reload if no attributes are provided to reload' do
405
+ planet = Planet.new(:name => 'Omicron Persei VIII')
406
+ planet.stub!(:new_record?).and_return(false)
407
+
408
+ collection = mock('collection')
409
+ collection.should_not_receive(:reload)
410
+
411
+ planet.stub!(:collection).and_return(collection)
412
+ planet.reload_attributes
413
+ end
414
+
415
+ it 'should not call collection.reload if the record is new' do
416
+ lambda {
417
+ Planet.new(:name => 'Omicron Persei VIII').reload_attributes(:name)
418
+ }.should_not raise_error
419
+
420
+ planet = Planet.new(:name => 'Omicron Persei VIII')
421
+ planet.should_not_receive(:collection)
422
+ planet.reload_attributes(:name)
423
+ end
424
+ end
425
+
426
+ describe '#reload' do
427
+ it 'should call #reload_attributes with the currently loaded attributes' do
428
+ planet = Planet.new(:name => 'Omicron Persei VIII', :age => 1)
429
+ planet.stub!(:new_record?).and_return(false)
430
+
431
+ planet.should_receive(:reload_attributes).with(:name, :age).once
432
+
433
+ planet.reload
434
+ end
435
+
436
+ it 'should call #reload on the parent and child associations' do
437
+ planet = Planet.new(:name => 'Omicron Persei VIII', :age => 1)
438
+ planet.stub!(:new_record?).and_return(false)
439
+
440
+ child_association = mock('child assoc')
441
+ child_association.should_receive(:reload).once.and_return(true)
442
+
443
+ parent_association = mock('parent assoc')
444
+ parent_association.should_receive(:reload).once.and_return(true)
445
+
446
+ planet.stub!(:child_associations).and_return([child_association])
447
+ planet.stub!(:parent_associations).and_return([parent_association])
448
+ planet.stub!(:reload_attributes).and_return(planet)
449
+
450
+ planet.reload
451
+ end
452
+
453
+ it 'should not do anything if the record is new' do
454
+ planet = Planet.new(:name => 'Omicron Persei VIII', :age => 1)
455
+ planet.should_not_receive(:reload_attributes)
456
+ planet.reload
457
+ end
458
+ end
459
+
460
+ describe 'when retrieving by key' do
461
+ it 'should return the corresponding object' do
462
+ m = mock("planet")
463
+ Planet.should_receive(:get).with(1).and_return(m)
464
+
465
+ Planet.get!(1).should == m
466
+ end
467
+
468
+ it 'should raise an error if not found' do
469
+ Planet.should_receive(:get).and_return(nil)
470
+
471
+ lambda do
472
+ Planet.get!(1)
473
+ end.should raise_error(DataMapper::ObjectNotFoundError)
474
+ end
475
+ end
476
+
477
+ describe "inheritance" do
478
+ before(:all) do
479
+ class Media
480
+ include DataMapper::Resource
481
+
482
+ storage_names[:default] = 'media'
483
+ storage_names[:west_coast] = 'm3d1a'
484
+
485
+ property :name, String, :key => true
486
+ end
487
+
488
+ class NewsPaper < Media
489
+
490
+ storage_names[:east_coast] = 'mother'
491
+
492
+ property :rating, Integer
493
+ end
494
+ end
495
+
496
+ it 'should inherit storage_names' do
497
+ NewsPaper.storage_name(:default).should == 'media'
498
+ NewsPaper.storage_name(:west_coast).should == 'm3d1a'
499
+ NewsPaper.storage_name(:east_coast).should == 'mother'
500
+ Media.storage_name(:east_coast).should == 'medium'
501
+ end
502
+
503
+ it 'should inherit properties' do
504
+ Media.properties.should have(1).entries
505
+ NewsPaper.properties.should have(2).entries
506
+ end
507
+ end
508
+
509
+ describe "Single-table Inheritance" do
510
+ before(:all) do
511
+ class Plant
512
+ include DataMapper::Resource
513
+
514
+ property :id, Integer, :key => true
515
+ property :length, Integer
516
+
517
+ def calculate(int)
518
+ int ** 2
519
+ end
520
+
521
+ def length=(len)
522
+ attribute_set(:length, calculate(len))
523
+ end
524
+ end
525
+
526
+ class HousePlant < Plant
527
+ def calculate(int)
528
+ int ** 3
529
+ end
530
+ end
531
+
532
+ class PoisonIvy < Plant
533
+ def length=(len)
534
+ attribute_set(:length, len - 1)
535
+ end
536
+ end
537
+ end
538
+
539
+ it "should be able to overwrite getters" do
540
+ @p = Plant.new
541
+ @p.length = 3
542
+ @p.length.should == 9
543
+ end
544
+
545
+ it "should pick overwritten methods" do
546
+ @hp = HousePlant.new
547
+ @hp.length = 3
548
+ @hp.length.should == 27
549
+ end
550
+
551
+ it "should pick overwritten setters" do
552
+ @pi = PoisonIvy.new
553
+ @pi.length = 3
554
+ @pi.length.should == 2
555
+ end
556
+ end
557
+ end