datamapper-dm-core 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
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 +41 -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 +30 -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 +136 -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 +229 -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 +267 -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 +75 -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 +493 -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