dm-core 0.9.2

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