sam-dm-core 0.9.6

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