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,1073 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ if ADAPTER
4
+ class Zebra
5
+ include DataMapper::Resource
6
+
7
+ def self.default_repository_name
8
+ ADAPTER
9
+ end
10
+
11
+ property :id, Serial
12
+ property :name, String
13
+ property :age, Integer
14
+ property :notes, Text
15
+
16
+ has n, :stripes
17
+ end
18
+
19
+ class Stripe
20
+ include DataMapper::Resource
21
+
22
+ def self.default_repository_name
23
+ ADAPTER
24
+ end
25
+
26
+ property :id, Serial
27
+ property :name, String
28
+ property :age, Integer
29
+ property :zebra_id, Integer
30
+
31
+ belongs_to :zebra
32
+
33
+ def self.sort_by_name
34
+ all(:order => [ :name ])
35
+ end
36
+ end
37
+
38
+ class CollectionSpecParty
39
+ include DataMapper::Resource
40
+
41
+ def self.default_repository_name
42
+ ADAPTER
43
+ end
44
+
45
+ property :name, String, :key => true
46
+ property :type, Discriminator
47
+ end
48
+
49
+ class CollectionSpecUser < CollectionSpecParty
50
+ def self.default_repository_name
51
+ ADAPTER
52
+ end
53
+
54
+ property :username, String
55
+ property :password, String
56
+ end
57
+
58
+ module CollectionSpecHelper
59
+ def setup
60
+ Zebra.auto_migrate!(ADAPTER)
61
+ Stripe.auto_migrate!(ADAPTER)
62
+
63
+ repository(ADAPTER) do
64
+ @nancy = Zebra.create(:name => 'Nancy', :age => 11, :notes => 'Spotted!')
65
+ @bessie = Zebra.create(:name => 'Bessie', :age => 10, :notes => 'Striped!')
66
+ @steve = Zebra.create(:name => 'Steve', :age => 8, :notes => 'Bald!')
67
+
68
+ @babe = Stripe.create(:name => 'Babe')
69
+ @snowball = Stripe.create(:name => 'snowball')
70
+
71
+ @nancy.stripes << @babe
72
+ @nancy.stripes << @snowball
73
+ @nancy.save
74
+ end
75
+ end
76
+ end
77
+
78
+ describe DataMapper::Collection do
79
+ include CollectionSpecHelper
80
+
81
+ before do
82
+ setup
83
+ end
84
+
85
+ before do
86
+ @repository = repository(ADAPTER)
87
+ @model = Zebra
88
+ @query = DataMapper::Query.new(@repository, @model, :order => [ :id ])
89
+ @collection = @repository.read_many(@query)
90
+ @other = @repository.read_many(@query.merge(:limit => 2))
91
+ end
92
+
93
+ it "should return the correct repository" do
94
+ repository = repository(:legacy)
95
+ query = DataMapper::Query.new(repository, @model)
96
+ DataMapper::Collection.new(query){}.repository.object_id.should == repository.object_id
97
+ end
98
+
99
+ it "should be able to add arbitrary objects" do
100
+ properties = @model.properties(:default)
101
+
102
+ collection = DataMapper::Collection.new(@query) do |c|
103
+ c.load([ 4, 'Bob', 10 ])
104
+ c.load([ 5, 'Nancy', 11 ])
105
+ end
106
+
107
+ collection.should respond_to(:reload)
108
+
109
+ results = collection.entries
110
+ results.should have(2).entries
111
+
112
+ results.each do |cow|
113
+ cow.attribute_loaded?(:name).should == true
114
+ cow.attribute_loaded?(:age).should == true
115
+ end
116
+
117
+ bob, nancy = results[0], results[1]
118
+
119
+ bob.name.should eql('Bob')
120
+ bob.age.should eql(10)
121
+ bob.should_not be_a_new_record
122
+
123
+ nancy.name.should eql('Nancy')
124
+ nancy.age.should eql(11)
125
+ nancy.should_not be_a_new_record
126
+
127
+ results.first.should == bob
128
+ end
129
+
130
+ it 'should be serializable with Marshal' do
131
+ Marshal.load(Marshal.dump(Zebra.all)).should == Zebra.all
132
+ end
133
+
134
+ describe 'model proxying' do
135
+ it 'should delegate to a model method' do
136
+ stripes = @model.first.stripes
137
+ stripes.should respond_to(:sort_by_name)
138
+ stripes.sort_by_name.should == [ @babe, @snowball ]
139
+ end
140
+ end
141
+
142
+ describe 'association proxying' do
143
+ it "should provide a Query" do
144
+ repository(ADAPTER) do
145
+ zebras = Zebra.all(:order => [ :name ])
146
+ zebras.query.order.should == [DataMapper::Query::Direction.new(Zebra.properties(ADAPTER)[:name])]
147
+ end
148
+ end
149
+
150
+ it "should proxy the relationships of the model" do
151
+ repository(ADAPTER) do
152
+ zebras = Zebra.all
153
+ zebras.should have(3).entries
154
+ zebras.find { |zebra| zebra.name == 'Nancy' }.stripes.should have(2).entries
155
+ zebras.should respond_to(:stripes)
156
+ zebras.stripes.should == [@babe, @snowball]
157
+ end
158
+ end
159
+
160
+ it "should preserve it's order on reload" do
161
+ repository(ADAPTER) do |r|
162
+ zebras = Zebra.all(:order => [ :name ])
163
+
164
+ order = %w{ Bessie Nancy Steve }
165
+
166
+ zebras.map { |z| z.name }.should == order
167
+
168
+ # Force a lazy-load call:
169
+ zebras.first.notes
170
+
171
+ # The order should be unaffected.
172
+ zebras.map { |z| z.name }.should == order
173
+ end
174
+ end
175
+ end
176
+
177
+ describe '.new' do
178
+ describe 'with non-index keys' do
179
+ it 'should instantiate read-only resources' do
180
+ @collection = DataMapper::Collection.new(DataMapper::Query.new(@repository, @model, :fields => [ :age ])) do |c|
181
+ c.load([ 1 ])
182
+ end
183
+
184
+ @collection.size.should == 1
185
+
186
+ resource = @collection.entries[0]
187
+
188
+ resource.should be_kind_of(@model)
189
+ resource.collection.object_id.should == @collection.object_id
190
+ resource.should_not be_new_record
191
+ resource.should be_readonly
192
+ resource.age.should == 1
193
+ end
194
+ end
195
+
196
+ describe 'with inheritance property' do
197
+ before do
198
+ CollectionSpecUser.auto_migrate!
199
+ CollectionSpecUser.create(:name => 'John')
200
+
201
+ properties = CollectionSpecParty.properties(:default)
202
+ end
203
+
204
+ it 'should instantiate resources using the inheritance property class' do
205
+ query = DataMapper::Query.new(@repository, CollectionSpecParty)
206
+ collection = @repository.read_many(query)
207
+ collection.should have(1).entries
208
+ collection.first.model.should == CollectionSpecUser
209
+ end
210
+ end
211
+ end
212
+
213
+ [ true, false ].each do |loaded|
214
+ describe " (#{loaded ? '' : 'not '}loaded) " do
215
+ if loaded
216
+ before do
217
+ @collection.to_a
218
+ end
219
+ end
220
+
221
+ describe '#<<' do
222
+ it 'should relate each new resource to the collection' do
223
+ # resource is orphaned
224
+ @nancy.collection.object_id.should_not == @collection.object_id
225
+
226
+ @collection << @nancy
227
+
228
+ # resource is related
229
+ @nancy.collection.object_id.should == @collection.object_id
230
+ end
231
+
232
+ it 'should return self' do
233
+ @collection.<<(@steve).object_id.should == @collection.object_id
234
+ end
235
+ end
236
+
237
+ describe '#all' do
238
+ describe 'with no arguments' do
239
+ it 'should return self' do
240
+ @collection.all.object_id.should == @collection.object_id
241
+ end
242
+ end
243
+
244
+ describe 'with query arguments' do
245
+ describe 'should return a Collection' do
246
+ before do
247
+ @query.update(:offset => 10, :limit => 10)
248
+ query = DataMapper::Query.new(@repository, @model)
249
+ @unlimited = DataMapper::Collection.new(query) {}
250
+ end
251
+
252
+ it 'has an offset equal to 10' do
253
+ @collection.all.query.offset.should == 10
254
+ end
255
+
256
+ it 'has a cumulative offset equal to 11 when passed an offset of 1' do
257
+ @collection.all(:offset => 1).query.offset.should == 11
258
+ end
259
+
260
+ it 'has a cumulative offset equal to 19 when passed an offset of 9' do
261
+ @collection.all(:offset => 9).query.offset.should == 19
262
+ end
263
+
264
+ it 'is empty when passed an offset that is out of range' do
265
+ pending do
266
+ empty_collection = @collection.all(:offset => 10)
267
+ empty_collection.should == []
268
+ empty_collection.should be_loaded
269
+ end
270
+ end
271
+
272
+ it 'has an limit equal to 10' do
273
+ @collection.all.query.limit.should == 10
274
+ end
275
+
276
+ it 'has a limit equal to 5' do
277
+ @collection.all(:limit => 5).query.limit.should == 5
278
+ end
279
+
280
+ it 'has a limit equal to 10 if passed a limit greater than 10' do
281
+ @collection.all(:limit => 11).query.limit.should == 10
282
+ end
283
+
284
+ it 'has no limit' do
285
+ @unlimited.all.query.limit.should be_nil
286
+ end
287
+
288
+ it 'has a limit equal to 1000 when passed a limit of 1000' do
289
+ @unlimited.all(:limit => 1000).query.limit.should == 1000
290
+ end
291
+ end
292
+ end
293
+ end
294
+
295
+ describe '#at' do
296
+ it 'should return a Resource' do
297
+ resource_at = @collection.at(1)
298
+ resource_at.should be_kind_of(DataMapper::Resource)
299
+ resource_at.id.should == @bessie.id
300
+ end
301
+
302
+ it 'should return a Resource when using a negative index' do
303
+ resource_at = @collection.at(-1)
304
+ resource_at.should be_kind_of(DataMapper::Resource)
305
+ resource_at.id.should == @steve.id
306
+ end
307
+ end
308
+
309
+ describe '#build' do
310
+ it 'should build a new resource' do
311
+ resource = @collection.build(:name => 'John')
312
+ resource.should be_kind_of(@model)
313
+ resource.should be_new_record
314
+ end
315
+
316
+ it 'should append the new resource to the collection' do
317
+ resource = @collection.build(:name => 'John')
318
+ resource.should be_new_record
319
+ resource.collection.object_id.should == @collection.object_id
320
+ @collection.should include(resource)
321
+ end
322
+
323
+ it 'should use the query conditions to set default values' do
324
+ resource = @collection.build
325
+ resource.should be_new_record
326
+ resource.name.should be_nil
327
+
328
+ @collection.query.update(:name => 'John')
329
+
330
+ resource = @collection.build
331
+ resource.name.should == 'John'
332
+ end
333
+ end
334
+
335
+ describe '#clear' do
336
+ it 'should orphan the resource from the collection' do
337
+ entries = @collection.entries
338
+
339
+ # resources are related
340
+ entries.each { |r| r.collection.object_id.should == @collection.object_id }
341
+
342
+ @collection.should have(3).entries
343
+ @collection.clear
344
+ @collection.should be_empty
345
+
346
+ # resources are orphaned
347
+ entries.each { |r| r.collection.object_id.should_not == @collection.object_id }
348
+ end
349
+
350
+ it 'should return self' do
351
+ @collection.clear.object_id.should == @collection.object_id
352
+ end
353
+ end
354
+
355
+ describe '#collect!' do
356
+ it 'should return self' do
357
+ @collection.collect! { |resource| resource }.object_id.should == @collection.object_id
358
+ end
359
+ end
360
+
361
+ describe '#concat' do
362
+ it 'should return self' do
363
+ @collection.concat(@other).object_id.should == @collection.object_id
364
+ end
365
+ end
366
+
367
+ describe '#create' do
368
+ it 'should create a new resource' do
369
+ resource = @collection.create(:name => 'John')
370
+ resource.should be_kind_of(@model)
371
+ resource.should_not be_new_record
372
+ end
373
+
374
+ it 'should append the new resource to the collection' do
375
+ resource = @collection.create(:name => 'John')
376
+ resource.should_not be_new_record
377
+ resource.collection.object_id.should == @collection.object_id
378
+ @collection.should include(resource)
379
+ end
380
+
381
+ it 'should not append the resource if it was not saved' do
382
+ @repository.should_receive(:create).and_return(false)
383
+ Zebra.should_receive(:repository).at_least(:once).and_return(@repository)
384
+
385
+ resource = @collection.create(:name => 'John')
386
+ resource.should be_new_record
387
+
388
+ resource.collection.object_id.should_not == @collection.object_id
389
+ @collection.should_not include(resource)
390
+ end
391
+
392
+ it 'should use the query conditions to set default values' do
393
+ resource = @collection.create
394
+ resource.should_not be_new_record
395
+ resource.name.should be_nil
396
+
397
+ @collection.query.update(:name => 'John')
398
+
399
+ resource = @collection.create
400
+ resource.name.should == 'John'
401
+ end
402
+ end
403
+
404
+ describe '#delete' do
405
+ it 'should orphan the resource from the collection' do
406
+ collection = @nancy.collection
407
+
408
+ # resource is related
409
+ @nancy.collection.object_id.should == collection.object_id
410
+
411
+ collection.should have(1).entries
412
+ collection.delete(@nancy)
413
+ collection.should be_empty
414
+
415
+ # resource is orphaned
416
+ @nancy.collection.object_id.should_not == collection.object_id
417
+ end
418
+
419
+ it 'should return a Resource' do
420
+ collection = @nancy.collection
421
+
422
+ resource = collection.delete(@nancy)
423
+
424
+ resource.should be_kind_of(DataMapper::Resource)
425
+ resource.object_id.should == @nancy.object_id
426
+ end
427
+ end
428
+
429
+ describe '#delete_at' do
430
+ it 'should orphan the resource from the collection' do
431
+ collection = @nancy.collection
432
+
433
+ # resource is related
434
+ @nancy.collection.object_id.should == collection.object_id
435
+
436
+ collection.should have(1).entries
437
+ collection.delete_at(0).object_id.should == @nancy.object_id
438
+ collection.should be_empty
439
+
440
+ # resource is orphaned
441
+ @nancy.collection.object_id.should_not == collection.object_id
442
+ end
443
+
444
+ it 'should return a Resource' do
445
+ collection = @nancy.collection
446
+
447
+ resource = collection.delete_at(0)
448
+
449
+ resource.should be_kind_of(DataMapper::Resource)
450
+ resource.object_id.should == @nancy.object_id
451
+ end
452
+ end
453
+
454
+ describe '#destroy!' do
455
+ before do
456
+ @ids = [ @nancy.id, @bessie.id, @steve.id ]
457
+ end
458
+
459
+ it 'should destroy the resources in the collection' do
460
+ @collection.map { |r| r.id }.should == @ids
461
+ @collection.destroy!.should == true
462
+ @model.all(:id => @ids).should == []
463
+ @collection.reload.should == []
464
+ end
465
+
466
+ it 'should clear the collection' do
467
+ @collection.map { |r| r.id }.should == @ids
468
+ @collection.destroy!.should == true
469
+ @collection.should == []
470
+ end
471
+ end
472
+
473
+ describe '#each' do
474
+ it 'should return self' do
475
+ @collection.each { |resource| }.object_id.should == @collection.object_id
476
+ end
477
+ end
478
+
479
+ describe '#each_index' do
480
+ it 'should return self' do
481
+ @collection.each_index { |resource| }.object_id.should == @collection.object_id
482
+ end
483
+ end
484
+
485
+ describe '#eql?' do
486
+ it 'should return true if for the same collection' do
487
+ @collection.object_id.should == @collection.object_id
488
+ @collection.should be_eql(@collection)
489
+ end
490
+
491
+ it 'should return true for duplicate collections' do
492
+ dup = @collection.dup
493
+ dup.should be_kind_of(DataMapper::Collection)
494
+ dup.object_id.should_not == @collection.object_id
495
+ dup.entries.should == @collection.entries
496
+ dup.should be_eql(@collection)
497
+ end
498
+
499
+ it 'should return false for different collections' do
500
+ @collection.should_not be_eql(@other)
501
+ end
502
+ end
503
+
504
+ describe '#fetch' do
505
+ it 'should return a Resource' do
506
+ @collection.fetch(0).should be_kind_of(DataMapper::Resource)
507
+ end
508
+ end
509
+
510
+ describe '#first' do
511
+ describe 'with no arguments' do
512
+ it 'should return a Resource' do
513
+ first = @collection.first
514
+ first.should_not be_nil
515
+ first.should be_kind_of(DataMapper::Resource)
516
+ first.id.should == @nancy.id
517
+ end
518
+ end
519
+
520
+ describe 'with limit specified' do
521
+ it 'should return a Collection' do
522
+ collection = @collection.first(2)
523
+
524
+ collection.should be_kind_of(DataMapper::Collection)
525
+ collection.object_id.should_not == @collection.object_id
526
+
527
+ collection.query.order.size.should == 1
528
+ collection.query.order.first.property.should == @model.properties[:id]
529
+ collection.query.order.first.direction.should == :asc
530
+
531
+ collection.query.offset.should == 0
532
+ collection.query.limit.should == 2
533
+
534
+ collection.length.should == 2
535
+
536
+ collection.entries.map { |r| r.id }.should == [ @nancy.id, @bessie.id ]
537
+ end
538
+
539
+ it 'should return a Collection if limit is 1' do
540
+ collection = @collection.first(1)
541
+
542
+ collection.should be_kind_of(DataMapper::Collection)
543
+ collection.object_id.should_not == @collection.object_id
544
+ end
545
+ end
546
+ end
547
+
548
+ describe '#freeze' do
549
+ it 'should freeze the underlying array' do
550
+ @collection.should_not be_frozen
551
+ @collection.freeze
552
+ @collection.should be_frozen
553
+ end
554
+ end
555
+
556
+ describe '#get' do
557
+ it 'should find a resource in a collection by key' do
558
+ resource = @collection.get(*@nancy.key)
559
+ resource.should be_kind_of(DataMapper::Resource)
560
+ resource.id.should == @nancy.id
561
+ end
562
+
563
+ it "should find a resource in a collection by typecasting the key" do
564
+ resource = @collection.get(*@nancy.key)
565
+ resource.should be_kind_of(DataMapper::Resource)
566
+ resource.id.should == @nancy.id
567
+ end
568
+
569
+ it 'should not find a resource not in the collection' do
570
+ @query.update(:offset => 0, :limit => 3)
571
+ @david = Zebra.create(:name => 'David', :age => 15, :notes => 'Albino')
572
+ @collection.get(*@david.key).should be_nil
573
+ end
574
+ end
575
+
576
+ describe '#get!' do
577
+ it 'should find a resource in a collection by key' do
578
+ resource = @collection.get!(*@nancy.key)
579
+ resource.should be_kind_of(DataMapper::Resource)
580
+ resource.id.should == @nancy.id
581
+ end
582
+
583
+ it 'should raise an exception if the resource is not found' do
584
+ @query.update(:offset => 0, :limit => 3)
585
+ @david = Zebra.create(:name => 'David', :age => 15, :notes => 'Albino')
586
+ lambda {
587
+ @collection.get!(@david.key)
588
+ }.should raise_error(DataMapper::ObjectNotFoundError)
589
+ end
590
+ end
591
+
592
+ describe '#insert' do
593
+ it 'should return self' do
594
+ @collection.insert(1, @steve).object_id.should == @collection.object_id
595
+ end
596
+ end
597
+
598
+ describe '#last' do
599
+ describe 'with no arguments' do
600
+ it 'should return a Resource' do
601
+ last = @collection.last
602
+ last.should_not be_nil
603
+ last.should be_kind_of(DataMapper::Resource)
604
+ last.id.should == @steve.id
605
+ end
606
+ end
607
+
608
+ describe 'with limit specified' do
609
+ it 'should return a Collection' do
610
+ collection = @collection.last(2)
611
+
612
+ collection.should be_kind_of(DataMapper::Collection)
613
+ collection.object_id.should_not == @collection.object_id
614
+
615
+ collection.query.order.size.should == 1
616
+ collection.query.order.first.property.should == @model.properties[:id]
617
+ collection.query.order.first.direction.should == :desc
618
+
619
+ collection.query.offset.should == 0
620
+ collection.query.limit.should == 2
621
+
622
+ collection.length.should == 2
623
+
624
+ collection.entries.map { |r| r.id }.should == [ @bessie.id, @steve.id ]
625
+ end
626
+
627
+ it 'should return a Collection if limit is 1' do
628
+ collection = @collection.last(1)
629
+
630
+ collection.class.should == DataMapper::Collection # should be_kind_of(DataMapper::Collection)
631
+ collection.object_id.should_not == @collection.object_id
632
+ end
633
+ end
634
+ end
635
+
636
+ describe '#load' do
637
+ it 'should load resources from the identity map when possible' do
638
+ @steve.collection = nil
639
+ @repository.identity_map(@model).should_receive(:get).with([ @steve.id ]).and_return(@steve)
640
+
641
+ collection = @repository.read_many(@query.merge(:id => @steve.id))
642
+
643
+ collection.size.should == 1
644
+ collection.map { |r| r.object_id }.should == [ @steve.object_id ]
645
+
646
+ @steve.collection.object_id.should == collection.object_id
647
+ end
648
+
649
+ it 'should return a Resource' do
650
+ @collection.load([ @steve.id, @steve.name, @steve.age ]).should be_kind_of(DataMapper::Resource)
651
+ end
652
+ end
653
+
654
+ describe '#loaded?' do
655
+ if loaded
656
+ it 'should return true for an initialized collection' do
657
+ @collection.should be_loaded
658
+ end
659
+ else
660
+ it 'should return false for an uninitialized collection' do
661
+ @collection.should_not be_loaded
662
+ @collection.to_a # load collection
663
+ @collection.should be_loaded
664
+ end
665
+ end
666
+ end
667
+
668
+ describe '#pop' do
669
+ it 'should orphan the resource from the collection' do
670
+ collection = @steve.collection
671
+
672
+ # resource is related
673
+ @steve.collection.object_id.should == collection.object_id
674
+
675
+ collection.should have(1).entries
676
+ collection.pop.object_id.should == @steve.object_id
677
+ collection.should be_empty
678
+
679
+ # resource is orphaned
680
+ @steve.collection.object_id.should_not == collection.object_id
681
+ end
682
+
683
+ it 'should return a Resource' do
684
+ @collection.pop.key.should == @steve.key
685
+ end
686
+ end
687
+
688
+ describe '#properties' do
689
+ it 'should return a PropertySet' do
690
+ @collection.properties.should be_kind_of(DataMapper::PropertySet)
691
+ end
692
+
693
+ it 'should contain same properties as query.fields' do
694
+ properties = @collection.properties
695
+ properties.entries.should == @collection.query.fields
696
+ end
697
+ end
698
+
699
+ describe '#push' do
700
+ it 'should relate each new resource to the collection' do
701
+ # resource is orphaned
702
+ @nancy.collection.object_id.should_not == @collection.object_id
703
+
704
+ @collection.push(@nancy)
705
+
706
+ # resource is related
707
+ @nancy.collection.object_id.should == @collection.object_id
708
+ end
709
+
710
+ it 'should return self' do
711
+ @collection.push(@steve).object_id.should == @collection.object_id
712
+ end
713
+ end
714
+
715
+ describe '#relationships' do
716
+ it 'should return a Hash' do
717
+ @collection.relationships.should be_kind_of(Hash)
718
+ end
719
+
720
+ it 'should contain same properties as query.model.relationships' do
721
+ relationships = @collection.relationships
722
+ relationships.should == @collection.query.model.relationships
723
+ end
724
+ end
725
+
726
+ describe '#reject' do
727
+ it 'should return a Collection with resources that did not match the block' do
728
+ rejected = @collection.reject { |resource| false }
729
+ rejected.class.should == Array
730
+ rejected.should == [ @nancy, @bessie, @steve ]
731
+ end
732
+
733
+ it 'should return an empty Array if resources matched the block' do
734
+ rejected = @collection.reject { |resource| true }
735
+ rejected.class.should == Array
736
+ rejected.should == []
737
+ end
738
+ end
739
+
740
+ describe '#reject!' do
741
+ it 'should return self if resources matched the block' do
742
+ @collection.reject! { |resource| true }.object_id.should == @collection.object_id
743
+ end
744
+
745
+ it 'should return nil if no resources matched the block' do
746
+ @collection.reject! { |resource| false }.should be_nil
747
+ end
748
+ end
749
+
750
+ describe '#reload' do
751
+ it 'should return self' do
752
+ @collection.reload.object_id.should == @collection.object_id
753
+ end
754
+
755
+ it 'should replace the collection' do
756
+ original = @collection.dup
757
+ @collection.reload.should == @collection
758
+ @collection.should == original
759
+ end
760
+
761
+ it 'should reload lazily initialized fields' do
762
+ pending 'Move to unit specs'
763
+
764
+ @repository.should_receive(:all) do |model,query|
765
+ model.should == @model
766
+
767
+ query.should be_instance_of(DataMapper::Query)
768
+ query.reload.should == true
769
+ query.offset.should == 0
770
+ query.limit.should == 10
771
+ query.order.should == []
772
+ query.fields.should == @model.properties.defaults
773
+ query.links.should == []
774
+ query.includes.should == []
775
+ query.conditions.should == [ [ :eql, @model.properties[:id], [ 1, 2, 3 ] ] ]
776
+
777
+ @collection
778
+ end
779
+
780
+ @collection.reload
781
+ end
782
+ end
783
+
784
+ describe '#replace' do
785
+ it "should orphan each existing resource from the collection if loaded?" do
786
+ entries = @collection.entries
787
+
788
+ # resources are related
789
+ entries.each { |r| r.collection.object_id.should == @collection.object_id }
790
+
791
+ @collection.should have(3).entries
792
+ @collection.replace([]).object_id.should == @collection.object_id
793
+ @collection.should be_empty
794
+
795
+ # resources are orphaned
796
+ entries.each { |r| r.collection.object_id.should_not == @collection.object_id }
797
+ end
798
+
799
+ it 'should relate each new resource to the collection' do
800
+ # resource is orphaned
801
+ @nancy.collection.object_id.should_not == @collection.object_id
802
+
803
+ @collection.replace([ @nancy ])
804
+
805
+ # resource is related
806
+ @nancy.collection.object_id.should == @collection.object_id
807
+ end
808
+
809
+ it 'should replace the contents of the collection' do
810
+ other = [ @nancy ]
811
+ @collection.should_not == other
812
+ @collection.replace(other)
813
+ @collection.should == other
814
+ @collection.object_id.should_not == @other.object_id
815
+ end
816
+ end
817
+
818
+ describe '#reverse' do
819
+ [ true, false ].each do |loaded|
820
+ describe "on a collection where loaded? == #{loaded}" do
821
+ before do
822
+ @collection.to_a if loaded
823
+ end
824
+
825
+ it 'should return a Collection with reversed entries' do
826
+ reversed = @collection.reverse
827
+ reversed.should be_kind_of(DataMapper::Collection)
828
+ reversed.object_id.should_not == @collection.object_id
829
+ reversed.entries.should == @collection.entries.reverse
830
+
831
+ reversed.query.order.size.should == 1
832
+ reversed.query.order.first.property.should == @model.properties[:id]
833
+ reversed.query.order.first.direction.should == :desc
834
+ end
835
+ end
836
+ end
837
+ end
838
+
839
+ describe '#reverse!' do
840
+ it 'should return self' do
841
+ @collection.reverse!.object_id.should == @collection.object_id
842
+ end
843
+ end
844
+
845
+ describe '#reverse_each' do
846
+ it 'should return self' do
847
+ @collection.reverse_each { |resource| }.object_id.should == @collection.object_id
848
+ end
849
+ end
850
+
851
+ describe '#select' do
852
+ it 'should return an Array with resources that matched the block' do
853
+ selected = @collection.select { |resource| true }
854
+ selected.class.should == Array
855
+ selected.should == @collection
856
+ end
857
+
858
+ it 'should return an empty Array if no resources matched the block' do
859
+ selected = @collection.select { |resource| false }
860
+ selected.class.should == Array
861
+ selected.should == []
862
+ end
863
+ end
864
+
865
+ describe '#shift' do
866
+ it 'should orphan the resource from the collection' do
867
+ collection = @nancy.collection
868
+
869
+ # resource is related
870
+ @nancy.collection.object_id.should == collection.object_id
871
+
872
+ collection.should have(1).entries
873
+ collection.shift.object_id.should == @nancy.object_id
874
+ collection.should be_empty
875
+
876
+ # resource is orphaned
877
+ @nancy.collection.object_id.should_not == collection.object_id
878
+ end
879
+
880
+ it 'should return a Resource' do
881
+ @collection.shift.key.should == @nancy.key
882
+ end
883
+ end
884
+
885
+ [ :slice, :[] ].each do |method|
886
+ describe '#slice' do
887
+ describe 'with an index' do
888
+ it 'should return a Resource' do
889
+ resource = @collection.send(method, 0)
890
+ resource.should be_kind_of(DataMapper::Resource)
891
+ resource.id.should == @nancy.id
892
+ end
893
+ end
894
+
895
+ describe 'with a start and length' do
896
+ it 'should return a Collection' do
897
+ sliced = @collection.send(method, 0, 1)
898
+ sliced.should be_kind_of(DataMapper::Collection)
899
+ sliced.object_id.should_not == @collection.object_id
900
+ sliced.length.should == 1
901
+ sliced.map { |r| r.id }.should == [ @nancy.id ]
902
+ end
903
+ end
904
+
905
+ describe 'with a Range' do
906
+ it 'should return a Collection' do
907
+ sliced = @collection.send(method, 0..1)
908
+ sliced.should be_kind_of(DataMapper::Collection)
909
+ sliced.object_id.should_not == @collection.object_id
910
+ sliced.length.should == 2
911
+ sliced.map { |r| r.id }.should == [ @nancy.id, @bessie.id ]
912
+ end
913
+ end
914
+ end
915
+ end
916
+
917
+ describe '#slice!' do
918
+ describe 'with an index' do
919
+ it 'should return a Resource' do
920
+ resource = @collection.slice!(0)
921
+ resource.should be_kind_of(DataMapper::Resource)
922
+ end
923
+ end
924
+
925
+ describe 'with a start and length' do
926
+ it 'should return an Array' do
927
+ sliced = @collection.slice!(0, 1)
928
+ sliced.class.should == Array
929
+ sliced.map { |r| r.id }.should == [ @nancy.id ]
930
+ end
931
+ end
932
+
933
+ describe 'with a Range' do
934
+ it 'should return a Collection' do
935
+ sliced = @collection.slice(0..1)
936
+ sliced.should be_kind_of(DataMapper::Collection)
937
+ sliced.object_id.should_not == @collection.object_id
938
+ sliced.length.should == 2
939
+ sliced[0].id.should == @nancy.id
940
+ sliced[1].id.should == @bessie.id
941
+ end
942
+ end
943
+ end
944
+
945
+ describe '#sort' do
946
+ it 'should return an Array' do
947
+ sorted = @collection.sort { |a,b| a.age <=> b.age }
948
+ sorted.class.should == Array
949
+ end
950
+ end
951
+
952
+ describe '#sort!' do
953
+ it 'should return self' do
954
+ @collection.sort! { |a,b| 0 }.object_id.should == @collection.object_id
955
+ end
956
+ end
957
+
958
+ describe '#unshift' do
959
+ it 'should relate each new resource to the collection' do
960
+ # resource is orphaned
961
+ @nancy.collection.object_id.should_not == @collection.object_id
962
+
963
+ @collection.unshift(@nancy)
964
+
965
+ # resource is related
966
+ @nancy.collection.object_id.should == @collection.object_id
967
+ end
968
+
969
+ it 'should return self' do
970
+ @collection.unshift(@steve).object_id.should == @collection.object_id
971
+ end
972
+ end
973
+
974
+ describe '#update!' do
975
+ it 'should update the resources in the collection' do
976
+ pending do
977
+ # this will not pass with new update!
978
+ # update! should never loop through and set attributes
979
+ # even if it is loaded, and it will not reload the
980
+ # changed objects (even with reload=true, as objects
981
+ # are created is not in any identity map)
982
+ names = [ @nancy.name, @bessie.name, @steve.name ]
983
+ @collection.map { |r| r.name }.should == names
984
+ @collection.update!(:name => 'John')
985
+ @collection.map { |r| r.name }.should_not == names
986
+ @collection.map { |r| r.name }.should == %w[ John ] * 3
987
+ end
988
+ end
989
+
990
+ it 'should not update loaded resources unless forced' do
991
+ repository(ADAPTER) do
992
+ nancy = Zebra.first
993
+ nancy.name.should == "Nancy"
994
+
995
+ collection = Zebra.all(:name => ["Nancy","Bessie"])
996
+ collection.update!(:name => "Stevie")
997
+
998
+ nancy.name.should == "Nancy"
999
+ end
1000
+ end
1001
+
1002
+ it 'should update loaded resources if forced' do
1003
+ repository(ADAPTER) do
1004
+ nancy = Zebra.first
1005
+ nancy.name.should == "Nancy"
1006
+
1007
+ collection = Zebra.all(:name => ["Nancy","Bessie"])
1008
+ collection.update!({:name => "Stevie"},true)
1009
+
1010
+ nancy.name.should == "Stevie"
1011
+ end
1012
+ end
1013
+
1014
+ it 'should update collection-query when updating' do
1015
+ repository(ADAPTER) do
1016
+ collection = Zebra.all(:name => ["Nancy","Bessie"])
1017
+ collection.query.conditions.first[2].should == ["Nancy","Bessie"]
1018
+ collection.length.should == 2
1019
+ collection.update!(:name => "Stevie")
1020
+ collection.length.should == 2
1021
+ collection.query.conditions.first[2].should == "Stevie"
1022
+ end
1023
+ end
1024
+ end
1025
+
1026
+ describe '#keys' do
1027
+ it 'should return a hash of keys' do
1028
+ keys = @collection.send(:keys)
1029
+ keys.length.should == 1
1030
+ keys.each{|property,values| values.should == [1,2,3]}
1031
+ end
1032
+
1033
+ it 'should return an empty hash if collection is empty' do
1034
+ keys = Zebra.all(:id.gt => 10000).send(:keys)
1035
+ keys.should == {}
1036
+ end
1037
+ end
1038
+
1039
+ describe '#values_at' do
1040
+ it 'should return an Array' do
1041
+ values = @collection.values_at(0)
1042
+ values.class.should == Array
1043
+ end
1044
+
1045
+ it 'should return an Array of the resources at the index' do
1046
+ @collection.values_at(0).entries.map { |r| r.id }.should == [ @nancy.id ]
1047
+ end
1048
+ end
1049
+
1050
+ describe 'with lazy loading' do
1051
+ it "should take a materialization block" do
1052
+ collection = DataMapper::Collection.new(@query) do |c|
1053
+ c.should == []
1054
+ c.load([ 1, 'Bob', 10 ])
1055
+ c.load([ 2, 'Nancy', 11 ])
1056
+ end
1057
+
1058
+ collection.should_not be_loaded
1059
+ collection.length.should == 2
1060
+ collection.should be_loaded
1061
+ end
1062
+
1063
+ it "should load lazy columns when using offset" do
1064
+ repository(ADAPTER) do
1065
+ zebras = Zebra.all(:offset => 1, :limit => 2)
1066
+ zebras.first.notes.should_not be_nil
1067
+ end
1068
+ end
1069
+ end
1070
+ end
1071
+ end
1072
+ end
1073
+ end