dm-core 0.9.11 → 0.10.0

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 (194) hide show
  1. data/.autotest +17 -14
  2. data/.gitignore +3 -1
  3. data/FAQ +6 -5
  4. data/History.txt +5 -50
  5. data/Manifest.txt +66 -76
  6. data/QUICKLINKS +1 -1
  7. data/README.txt +21 -15
  8. data/Rakefile +6 -7
  9. data/SPECS +2 -29
  10. data/TODO +1 -1
  11. data/deps.rip +2 -0
  12. data/dm-core.gemspec +11 -15
  13. data/lib/dm-core.rb +105 -110
  14. data/lib/dm-core/adapters.rb +135 -16
  15. data/lib/dm-core/adapters/abstract_adapter.rb +251 -181
  16. data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
  17. data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
  18. data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
  19. data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
  20. data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
  21. data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
  22. data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
  23. data/lib/dm-core/associations/many_to_many.rb +372 -90
  24. data/lib/dm-core/associations/many_to_one.rb +220 -73
  25. data/lib/dm-core/associations/one_to_many.rb +319 -255
  26. data/lib/dm-core/associations/one_to_one.rb +66 -53
  27. data/lib/dm-core/associations/relationship.rb +561 -156
  28. data/lib/dm-core/collection.rb +1101 -379
  29. data/lib/dm-core/core_ext/kernel.rb +12 -0
  30. data/lib/dm-core/core_ext/symbol.rb +10 -0
  31. data/lib/dm-core/identity_map.rb +4 -34
  32. data/lib/dm-core/migrations.rb +1283 -0
  33. data/lib/dm-core/model.rb +570 -369
  34. data/lib/dm-core/model/descendant_set.rb +81 -0
  35. data/lib/dm-core/model/hook.rb +45 -0
  36. data/lib/dm-core/model/is.rb +32 -0
  37. data/lib/dm-core/model/property.rb +247 -0
  38. data/lib/dm-core/model/relationship.rb +335 -0
  39. data/lib/dm-core/model/scope.rb +90 -0
  40. data/lib/dm-core/property.rb +808 -273
  41. data/lib/dm-core/property_set.rb +141 -98
  42. data/lib/dm-core/query.rb +1037 -483
  43. data/lib/dm-core/query/conditions/comparison.rb +872 -0
  44. data/lib/dm-core/query/conditions/operation.rb +221 -0
  45. data/lib/dm-core/query/direction.rb +43 -0
  46. data/lib/dm-core/query/operator.rb +84 -0
  47. data/lib/dm-core/query/path.rb +138 -0
  48. data/lib/dm-core/query/sort.rb +45 -0
  49. data/lib/dm-core/repository.rb +210 -94
  50. data/lib/dm-core/resource.rb +641 -421
  51. data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
  52. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
  53. data/lib/dm-core/support/chainable.rb +22 -0
  54. data/lib/dm-core/support/deprecate.rb +12 -0
  55. data/lib/dm-core/support/logger.rb +13 -0
  56. data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
  57. data/lib/dm-core/transaction.rb +333 -92
  58. data/lib/dm-core/type.rb +98 -60
  59. data/lib/dm-core/types/boolean.rb +1 -1
  60. data/lib/dm-core/types/discriminator.rb +34 -20
  61. data/lib/dm-core/types/object.rb +7 -4
  62. data/lib/dm-core/types/paranoid_boolean.rb +11 -9
  63. data/lib/dm-core/types/paranoid_datetime.rb +11 -9
  64. data/lib/dm-core/types/serial.rb +3 -3
  65. data/lib/dm-core/types/text.rb +3 -4
  66. data/lib/dm-core/version.rb +1 -1
  67. data/script/performance.rb +102 -109
  68. data/script/profile.rb +169 -38
  69. data/spec/lib/adapter_helpers.rb +105 -0
  70. data/spec/lib/collection_helpers.rb +18 -0
  71. data/spec/lib/counter_adapter.rb +34 -0
  72. data/spec/lib/pending_helpers.rb +27 -0
  73. data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
  74. data/spec/public/associations/many_to_many_spec.rb +193 -0
  75. data/spec/public/associations/many_to_one_spec.rb +73 -0
  76. data/spec/public/associations/one_to_many_spec.rb +77 -0
  77. data/spec/public/associations/one_to_one_spec.rb +156 -0
  78. data/spec/public/collection_spec.rb +65 -0
  79. data/spec/public/migrations_spec.rb +359 -0
  80. data/spec/public/model/relationship_spec.rb +924 -0
  81. data/spec/public/model_spec.rb +159 -0
  82. data/spec/public/property_spec.rb +829 -0
  83. data/spec/public/resource_spec.rb +71 -0
  84. data/spec/public/sel_spec.rb +44 -0
  85. data/spec/public/setup_spec.rb +145 -0
  86. data/spec/public/shared/association_collection_shared_spec.rb +317 -0
  87. data/spec/public/shared/collection_shared_spec.rb +1670 -0
  88. data/spec/public/shared/finder_shared_spec.rb +1619 -0
  89. data/spec/public/shared/resource_shared_spec.rb +924 -0
  90. data/spec/public/shared/sel_shared_spec.rb +112 -0
  91. data/spec/public/transaction_spec.rb +129 -0
  92. data/spec/public/types/discriminator_spec.rb +130 -0
  93. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  94. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  95. data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
  96. data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
  97. data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
  98. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
  99. data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
  100. data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
  101. data/spec/semipublic/associations/relationship_spec.rb +194 -0
  102. data/spec/semipublic/associations_spec.rb +177 -0
  103. data/spec/semipublic/collection_spec.rb +142 -0
  104. data/spec/semipublic/property_spec.rb +61 -0
  105. data/spec/semipublic/query/conditions_spec.rb +528 -0
  106. data/spec/semipublic/query/path_spec.rb +443 -0
  107. data/spec/semipublic/query_spec.rb +2626 -0
  108. data/spec/semipublic/resource_spec.rb +47 -0
  109. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  110. data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
  111. data/spec/spec.opts +3 -1
  112. data/spec/spec_helper.rb +80 -57
  113. data/tasks/ci.rb +19 -31
  114. data/tasks/dm.rb +43 -48
  115. data/tasks/doc.rb +8 -11
  116. data/tasks/gemspec.rb +5 -5
  117. data/tasks/hoe.rb +15 -16
  118. data/tasks/install.rb +8 -10
  119. metadata +74 -111
  120. data/lib/dm-core/associations.rb +0 -207
  121. data/lib/dm-core/associations/relationship_chain.rb +0 -81
  122. data/lib/dm-core/auto_migrations.rb +0 -105
  123. data/lib/dm-core/dependency_queue.rb +0 -32
  124. data/lib/dm-core/hook.rb +0 -11
  125. data/lib/dm-core/is.rb +0 -16
  126. data/lib/dm-core/logger.rb +0 -232
  127. data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
  128. data/lib/dm-core/migrator.rb +0 -29
  129. data/lib/dm-core/scope.rb +0 -58
  130. data/lib/dm-core/support.rb +0 -7
  131. data/lib/dm-core/support/array.rb +0 -13
  132. data/lib/dm-core/support/assertions.rb +0 -8
  133. data/lib/dm-core/support/errors.rb +0 -23
  134. data/lib/dm-core/support/kernel.rb +0 -11
  135. data/lib/dm-core/support/symbol.rb +0 -41
  136. data/lib/dm-core/type_map.rb +0 -80
  137. data/lib/dm-core/types.rb +0 -19
  138. data/script/all +0 -4
  139. data/spec/integration/association_spec.rb +0 -1382
  140. data/spec/integration/association_through_spec.rb +0 -203
  141. data/spec/integration/associations/many_to_many_spec.rb +0 -449
  142. data/spec/integration/associations/many_to_one_spec.rb +0 -163
  143. data/spec/integration/associations/one_to_many_spec.rb +0 -188
  144. data/spec/integration/auto_migrations_spec.rb +0 -413
  145. data/spec/integration/collection_spec.rb +0 -1073
  146. data/spec/integration/data_objects_adapter_spec.rb +0 -32
  147. data/spec/integration/dependency_queue_spec.rb +0 -46
  148. data/spec/integration/model_spec.rb +0 -197
  149. data/spec/integration/mysql_adapter_spec.rb +0 -85
  150. data/spec/integration/postgres_adapter_spec.rb +0 -731
  151. data/spec/integration/property_spec.rb +0 -253
  152. data/spec/integration/query_spec.rb +0 -514
  153. data/spec/integration/repository_spec.rb +0 -61
  154. data/spec/integration/resource_spec.rb +0 -513
  155. data/spec/integration/sqlite3_adapter_spec.rb +0 -352
  156. data/spec/integration/sti_spec.rb +0 -273
  157. data/spec/integration/strategic_eager_loading_spec.rb +0 -156
  158. data/spec/integration/transaction_spec.rb +0 -75
  159. data/spec/integration/type_spec.rb +0 -275
  160. data/spec/lib/logging_helper.rb +0 -18
  161. data/spec/lib/mock_adapter.rb +0 -27
  162. data/spec/lib/model_loader.rb +0 -100
  163. data/spec/lib/publicize_methods.rb +0 -28
  164. data/spec/models/content.rb +0 -16
  165. data/spec/models/vehicles.rb +0 -34
  166. data/spec/models/zoo.rb +0 -48
  167. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
  168. data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
  169. data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
  170. data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
  171. data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
  172. data/spec/unit/associations/many_to_many_spec.rb +0 -32
  173. data/spec/unit/associations/many_to_one_spec.rb +0 -159
  174. data/spec/unit/associations/one_to_many_spec.rb +0 -393
  175. data/spec/unit/associations/one_to_one_spec.rb +0 -7
  176. data/spec/unit/associations/relationship_spec.rb +0 -71
  177. data/spec/unit/associations_spec.rb +0 -242
  178. data/spec/unit/auto_migrations_spec.rb +0 -111
  179. data/spec/unit/collection_spec.rb +0 -182
  180. data/spec/unit/data_mapper_spec.rb +0 -35
  181. data/spec/unit/identity_map_spec.rb +0 -126
  182. data/spec/unit/is_spec.rb +0 -80
  183. data/spec/unit/migrator_spec.rb +0 -33
  184. data/spec/unit/model_spec.rb +0 -321
  185. data/spec/unit/naming_conventions_spec.rb +0 -36
  186. data/spec/unit/property_set_spec.rb +0 -90
  187. data/spec/unit/property_spec.rb +0 -753
  188. data/spec/unit/query_spec.rb +0 -571
  189. data/spec/unit/repository_spec.rb +0 -93
  190. data/spec/unit/resource_spec.rb +0 -649
  191. data/spec/unit/scope_spec.rb +0 -142
  192. data/spec/unit/transaction_spec.rb +0 -493
  193. data/spec/unit/type_map_spec.rb +0 -114
  194. data/spec/unit/type_spec.rb +0 -119
@@ -0,0 +1,1670 @@
1
+ share_examples_for 'A public Collection' do
2
+ before :all do
3
+ %w[ @article_model @article @other @original @articles @other_articles ].each do |ivar|
4
+ raise "+#{ivar}+ should be defined in before block" unless instance_variable_defined?(ivar)
5
+ raise "+#{ivar}+ should not be nil in before block" unless instance_variable_get(ivar)
6
+ end
7
+
8
+ @articles.loaded?.should == loaded
9
+ end
10
+
11
+ before :all do
12
+ @no_join = defined?(DataMapper::Adapters::InMemoryAdapter) && @adapter.kind_of?(DataMapper::Adapters::InMemoryAdapter) ||
13
+ defined?(DataMapper::Adapters::YamlAdapter) && @adapter.kind_of?(DataMapper::Adapters::YamlAdapter)
14
+
15
+ @one_to_many = @articles.kind_of?(DataMapper::Associations::OneToMany::Collection)
16
+ @many_to_many = @articles.kind_of?(DataMapper::Associations::ManyToMany::Collection)
17
+
18
+ @skip = @no_join && @many_to_many
19
+ end
20
+
21
+ before do
22
+ pending if @skip
23
+ end
24
+
25
+ it { @articles.should respond_to(:<<) }
26
+
27
+ describe '#<<' do
28
+ before :all do
29
+ @resource = @article_model.new(:title => 'Title')
30
+
31
+ @return = @articles << @resource
32
+ end
33
+
34
+ it 'should return a Collection' do
35
+ @return.should be_kind_of(DataMapper::Collection)
36
+ end
37
+
38
+ it 'should return self' do
39
+ @return.should equal(@articles)
40
+ end
41
+
42
+ it 'should append one Resource to the Collection' do
43
+ @articles.last.should equal(@resource)
44
+ end
45
+
46
+ it 'should not relate the Resource to the Collection' do
47
+ @resource.collection.should_not equal(@articles)
48
+ end
49
+ end
50
+
51
+ it { @articles.should respond_to(:blank?) }
52
+
53
+ describe '#blank?' do
54
+ describe 'when the collection is empty' do
55
+ it 'should be true' do
56
+ @articles.clear.blank?.should be_true
57
+ end
58
+ end
59
+
60
+ describe 'when the collection is not empty' do
61
+ it 'should be false' do
62
+ @articles.blank?.should be_false
63
+ end
64
+ end
65
+ end
66
+
67
+ it { @articles.should respond_to(:clean?) }
68
+
69
+ describe '#clean?' do
70
+ describe 'with all clean resources in the collection' do
71
+ it 'should return true' do
72
+ @articles.clean?.should be_true
73
+ end
74
+ end
75
+
76
+ describe 'with a dirty resource in the collection' do
77
+ before :all do
78
+ @articles.each { |r| r.content = 'Changed' }
79
+ end
80
+
81
+ it 'should return true' do
82
+ @articles.clean?.should be_false
83
+ end
84
+ end
85
+ end
86
+
87
+ it { @articles.should respond_to(:clear) }
88
+
89
+ describe '#clear' do
90
+ before :all do
91
+ @resources = @articles.entries
92
+
93
+ @return = @articles.clear
94
+ end
95
+
96
+ it 'should return a Collection' do
97
+ @return.should be_kind_of(DataMapper::Collection)
98
+ end
99
+
100
+ it 'should return self' do
101
+ @return.should equal(@articles)
102
+ end
103
+
104
+ it 'should make the Collection empty' do
105
+ @articles.should be_empty
106
+ end
107
+
108
+ it 'should orphan the Resources' do
109
+ @resources.each { |resource| resource.collection.should_not equal(@articles) }
110
+ end
111
+ end
112
+
113
+ [ :collect!, :map! ].each do |method|
114
+ it { @articles.should respond_to(method) }
115
+
116
+ describe "##{method}" do
117
+ before :all do
118
+ @resources = @articles.dup.entries
119
+
120
+ @return = @articles.send(method) { |resource| @article_model.new(:title => 'Ignored Title', :content => 'New Content') }
121
+ end
122
+
123
+ it 'should return a Collection' do
124
+ @return.should be_kind_of(DataMapper::Collection)
125
+ end
126
+
127
+ it 'should return self' do
128
+ @return.should equal(@articles)
129
+ end
130
+
131
+ it 'should update the Collection inline' do
132
+ @articles.each { |resource| resource.attributes.only(:title, :content).should == { :title => 'Sample Article', :content => 'New Content' } }
133
+ end
134
+
135
+ it 'should orphan each replaced Resource in the Collection' do
136
+ @resources.each { |resource| resource.collection.should_not equal(@articles) }
137
+ end
138
+ end
139
+ end
140
+
141
+ it { @articles.should respond_to(:concat) }
142
+
143
+ describe '#concat' do
144
+ before :all do
145
+ @return = @articles.concat(@other_articles)
146
+ end
147
+
148
+ it 'should return a Collection' do
149
+ @return.should be_kind_of(DataMapper::Collection)
150
+ end
151
+
152
+ it 'should return self' do
153
+ @return.should equal(@articles)
154
+ end
155
+
156
+ it 'should concatenate the two collections' do
157
+ @return.should == [ @article, @other ]
158
+ end
159
+
160
+ it 'should relate each Resource to the Collection' do
161
+ @other_articles.each { |resource| resource.collection.should equal(@articles) }
162
+ end
163
+ end
164
+
165
+ [ :create, :create! ].each do |method|
166
+ it { @articles.should respond_to(method) }
167
+
168
+ describe "##{method}" do
169
+ describe 'when scoped to a property' do
170
+ before :all do
171
+ @return = @resource = @articles.send(method)
172
+ end
173
+
174
+ it 'should return a Resource' do
175
+ @return.should be_kind_of(DataMapper::Resource)
176
+ end
177
+
178
+ it 'should be a saved Resource' do
179
+ @resource.should be_saved
180
+ end
181
+
182
+ it 'should append the Resource to the Collection' do
183
+ @articles.last.should equal(@resource)
184
+ end
185
+
186
+ it 'should use the query conditions to set default values' do
187
+ @resource.title.should == 'Sample Article'
188
+ end
189
+
190
+ it 'should not append a Resource if create fails' do
191
+ pending 'TODO: not sure how to best spec this'
192
+ end
193
+ end
194
+
195
+ describe 'when scoped to the key' do
196
+ before :all do
197
+ @articles = @articles.all(:id => 1)
198
+
199
+ @return = @resource = @articles.send(method)
200
+ end
201
+
202
+ it 'should return a Resource' do
203
+ @return.should be_kind_of(DataMapper::Resource)
204
+ end
205
+
206
+ it 'should be a saved Resource' do
207
+ @resource.should be_saved
208
+ end
209
+
210
+ it 'should append the Resource to the Collection' do
211
+ @articles.last.should equal(@resource)
212
+ end
213
+
214
+ it 'should not use the query conditions to set default values' do
215
+ @resource.id.should_not == 1
216
+ end
217
+
218
+ it 'should not append a Resource if create fails' do
219
+ pending 'TODO: not sure how to best spec this'
220
+ end
221
+ end
222
+
223
+ describe 'when scoped to a property with multiple values' do
224
+ before :all do
225
+ @articles = @articles.all(:content => %w[ Sample Other ])
226
+
227
+ @return = @resource = @articles.send(method)
228
+ end
229
+
230
+ it 'should return a Resource' do
231
+ @return.should be_kind_of(DataMapper::Resource)
232
+ end
233
+
234
+ it 'should be a saved Resource' do
235
+ @resource.should be_saved
236
+ end
237
+
238
+ it 'should append the Resource to the Collection' do
239
+ @articles.last.should equal(@resource)
240
+ end
241
+
242
+ it 'should not use the query conditions to set default values' do
243
+ @resource.content.should be_nil
244
+ end
245
+
246
+ it 'should not append a Resource if create fails' do
247
+ pending 'TODO: not sure how to best spec this'
248
+ end
249
+ end
250
+
251
+ describe 'when scoped with a condition other than eql' do
252
+ before :all do
253
+ @articles = @articles.all(:content.not => 'Sample')
254
+
255
+ @return = @resource = @articles.send(method)
256
+ end
257
+
258
+ it 'should return a Resource' do
259
+ @return.should be_kind_of(DataMapper::Resource)
260
+ end
261
+
262
+ it 'should be a saved Resource' do
263
+ @resource.should be_saved
264
+ end
265
+
266
+ it 'should append the Resource to the Collection' do
267
+ @articles.last.should equal(@resource)
268
+ end
269
+
270
+ it 'should not use the query conditions to set default values' do
271
+ @resource.content.should be_nil
272
+ end
273
+
274
+ it 'should not append a Resource if create fails' do
275
+ pending 'TODO: not sure how to best spec this'
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+ it { @articles.should respond_to(:delete) }
282
+
283
+ describe '#delete' do
284
+ describe 'with a Resource within the Collection' do
285
+ before :all do
286
+ @return = @resource = @articles.delete(@article)
287
+ end
288
+
289
+ it 'should return a DataMapper::Resource' do
290
+ @return.should be_kind_of(DataMapper::Resource)
291
+ end
292
+
293
+ it 'should be the expected Resource' do
294
+ # compare keys because FK attributes may have been altered
295
+ # when removing from the Collection
296
+ @resource.key.should == @article.key
297
+ end
298
+
299
+ it 'should remove the Resource from the Collection' do
300
+ @articles.should_not be_include(@resource)
301
+ end
302
+
303
+ it 'should orphan the Resource' do
304
+ @resource.collection.should_not equal(@articles)
305
+ end
306
+ end
307
+
308
+ describe 'with a Resource not within the Collection' do
309
+ before :all do
310
+ @return = @articles.delete(@other)
311
+ end
312
+
313
+ it 'should return nil' do
314
+ @return.should be_nil
315
+ end
316
+ end
317
+ end
318
+
319
+ it { @articles.should respond_to(:delete_at) }
320
+
321
+ describe '#delete_at' do
322
+ describe 'with an offset within the Collection' do
323
+ before :all do
324
+ @return = @resource = @articles.delete_at(0)
325
+ end
326
+
327
+ it 'should return a DataMapper::Resource' do
328
+ @return.should be_kind_of(DataMapper::Resource)
329
+ end
330
+
331
+ it 'should be the expected Resource' do
332
+ @resource.key.should == @article.key
333
+ end
334
+
335
+ it 'should remove the Resource from the Collection' do
336
+ @articles.should_not be_include(@resource)
337
+ end
338
+
339
+ it 'should orphan the Resource' do
340
+ @resource.collection.should_not equal(@articles)
341
+ end
342
+ end
343
+
344
+ describe 'with an offset not within the Collection' do
345
+ before :all do
346
+ @return = @articles.delete_at(1)
347
+ end
348
+
349
+ it 'should return nil' do
350
+ @return.should be_nil
351
+ end
352
+ end
353
+ end
354
+
355
+ it { @articles.should respond_to(:delete_if) }
356
+
357
+ describe '#delete_if' do
358
+ describe 'with a block that matches a Resource in the Collection' do
359
+ before :all do
360
+ @resources = @articles.dup.entries
361
+
362
+ @return = @articles.delete_if { true }
363
+ end
364
+
365
+ it 'should return a Collection' do
366
+ @return.should be_kind_of(DataMapper::Collection)
367
+ end
368
+
369
+ it 'should return self' do
370
+ @return.should equal(@articles)
371
+ end
372
+
373
+ it 'should remove the Resources from the Collection' do
374
+ @resources.each { |resource| @articles.should_not be_include(resource) }
375
+ end
376
+
377
+ it 'should orphan the Resources' do
378
+ @resources.each { |resource| resource.collection.should_not equal(@articles) }
379
+ end
380
+ end
381
+
382
+ describe 'with a block that does not match a Resource in the Collection' do
383
+ before :all do
384
+ @resources = @articles.dup.entries
385
+
386
+ @return = @articles.delete_if { false }
387
+ end
388
+
389
+ it 'should return a Collection' do
390
+ @return.should be_kind_of(DataMapper::Collection)
391
+ end
392
+
393
+ it 'should return self' do
394
+ @return.should equal(@articles)
395
+ end
396
+
397
+ it 'should not modify the Collection' do
398
+ @articles.should == @resources
399
+ end
400
+ end
401
+ end
402
+
403
+ [ :destroy, :destroy! ].each do |method|
404
+ it { @articles.should respond_to(method) }
405
+
406
+ describe "##{method}" do
407
+ describe 'on a normal collection' do
408
+ before :all do
409
+ @return = @articles.send(method)
410
+ end
411
+
412
+ it 'should return true' do
413
+ @return.should be_true
414
+ end
415
+
416
+ it 'should remove the Resources from the datasource' do
417
+ @article_model.all(:title => 'Sample Article').should be_empty
418
+ end
419
+
420
+ it 'should clear the collection' do
421
+ @articles.should be_empty
422
+ end
423
+ end
424
+
425
+ describe 'on a limited collection' do
426
+ before :all do
427
+ @other = @articles.create.freeze
428
+ @limited = @articles.all(:limit => 1)
429
+
430
+ @return = @limited.send(method)
431
+ end
432
+
433
+ it 'should return true' do
434
+ @return.should be_true
435
+ end
436
+
437
+ it 'should remove the Resources from the datasource' do
438
+ @article_model.all(:title => 'Sample Article').should == [ @other ]
439
+ end
440
+
441
+ it 'should clear the collection' do
442
+ @limited.should be_empty
443
+ end
444
+
445
+ it 'should not destroy the other Resource' do
446
+ @article_model.get(*@other.key).should_not be_nil
447
+ end
448
+ end
449
+ end
450
+ end
451
+
452
+ it { @articles.should respond_to(:dirty?) }
453
+
454
+ describe '#dirty?' do
455
+ describe 'with all clean resources in the collection' do
456
+ it 'should return false' do
457
+ @articles.dirty?.should be_false
458
+ end
459
+ end
460
+
461
+ describe 'with a dirty resource in the collection' do
462
+ before :all do
463
+ @articles.each { |r| r.content = 'Changed' }
464
+ end
465
+
466
+ it 'should return true' do
467
+ @articles.dirty?.should be_true
468
+ end
469
+ end
470
+ end
471
+
472
+ it { @articles.should respond_to(:insert) }
473
+
474
+ describe '#insert' do
475
+ before :all do
476
+ @resources = @other_articles
477
+
478
+ @return = @articles.insert(0, *@resources)
479
+ end
480
+
481
+ it 'should return a Collection' do
482
+ @return.should be_kind_of(DataMapper::Collection)
483
+ end
484
+
485
+ it 'should return self' do
486
+ @return.should equal(@articles)
487
+ end
488
+
489
+ it 'should insert one or more Resources at a given offset' do
490
+ @articles.should == @resources + [ @article ]
491
+ end
492
+
493
+ it 'should relate the Resources to the Collection' do
494
+ @resources.each { |resource| resource.collection.should equal(@articles) }
495
+ end
496
+ end
497
+
498
+ it { @articles.should respond_to(:inspect) }
499
+
500
+ describe '#inspect' do
501
+ before :all do
502
+ @copy = @articles.dup
503
+ @copy << @article_model.new(:title => 'Ignored Title', :content => 'Other Article')
504
+
505
+ @return = @copy.inspect
506
+ end
507
+
508
+ it { @return.should match(/\A\[.*\]\z/) }
509
+
510
+ it { @return.should match(/\bid=#{@article.id}\b/) }
511
+ it { @return.should match(/\bid=nil\b/) }
512
+
513
+ it { @return.should match(/\btitle=\"Sample Article\"\s/) }
514
+ it { @return.should_not match(/\btitle=\"Ignored Title\"\s/) }
515
+ it { @return.should match(/\bcontent=\"Other Article\"\s/) }
516
+ end
517
+
518
+ it { @articles.should respond_to(:new) }
519
+
520
+ describe '#new' do
521
+ describe 'when scoped to a property' do
522
+ before :all do
523
+ @return = @resource = @articles.new
524
+ end
525
+
526
+ it 'should return a Resource' do
527
+ @return.should be_kind_of(DataMapper::Resource)
528
+ end
529
+
530
+ it 'should be a new Resource' do
531
+ @resource.should be_new
532
+ end
533
+
534
+ it 'should append the Resource to the Collection' do
535
+ @articles.last.should equal(@resource)
536
+ end
537
+
538
+ it 'should use the query conditions to set default values' do
539
+ @resource.title.should == 'Sample Article'
540
+ end
541
+ end
542
+
543
+ describe 'when scoped to the key' do
544
+ before :all do
545
+ @articles = @articles.all(:id => 1)
546
+
547
+ @return = @resource = @articles.new
548
+ end
549
+
550
+ it 'should return a Resource' do
551
+ @return.should be_kind_of(DataMapper::Resource)
552
+ end
553
+
554
+ it 'should be a new Resource' do
555
+ @resource.should be_new
556
+ end
557
+
558
+ it 'should append the Resource to the Collection' do
559
+ @articles.last.should equal(@resource)
560
+ end
561
+
562
+ it 'should not use the query conditions to set default values' do
563
+ @resource.id.should be_nil
564
+ end
565
+ end
566
+
567
+ describe 'when scoped to a property with multiple values' do
568
+ before :all do
569
+ @articles = @articles.all(:content => %w[ Sample Other ])
570
+
571
+ @return = @resource = @articles.new
572
+ end
573
+
574
+ it 'should return a Resource' do
575
+ @return.should be_kind_of(DataMapper::Resource)
576
+ end
577
+
578
+ it 'should be a new Resource' do
579
+ @resource.should be_new
580
+ end
581
+
582
+ it 'should append the Resource to the Collection' do
583
+ @articles.last.should equal(@resource)
584
+ end
585
+
586
+ it 'should not use the query conditions to set default values' do
587
+ @resource.content.should be_nil
588
+ end
589
+ end
590
+
591
+ describe 'when scoped with a condition other than eql' do
592
+ before :all do
593
+ @articles = @articles.all(:content.not => 'Sample')
594
+
595
+ @return = @resource = @articles.new
596
+ end
597
+
598
+ it 'should return a Resource' do
599
+ @return.should be_kind_of(DataMapper::Resource)
600
+ end
601
+
602
+ it 'should be a new Resource' do
603
+ @resource.should be_new
604
+ end
605
+
606
+ it 'should append the Resource to the Collection' do
607
+ @articles.last.should equal(@resource)
608
+ end
609
+
610
+ it 'should not use the query conditions to set default values' do
611
+ @resource.content.should be_nil
612
+ end
613
+ end
614
+ end
615
+
616
+ it { @articles.should respond_to(:pop) }
617
+
618
+ describe '#pop' do
619
+ before :all do
620
+ @new = @articles.create(:title => 'Sample Article') # TODO: freeze @new
621
+
622
+ @return = @resource = @articles.pop
623
+ end
624
+
625
+ it 'should return a Resource' do
626
+ @return.should be_kind_of(DataMapper::Resource)
627
+ end
628
+
629
+ it 'should be the last Resource in the Collection' do
630
+ @resource.should == @new
631
+ end
632
+
633
+ it 'should remove the Resource from the Collection' do
634
+ @articles.should_not be_include(@resource)
635
+ end
636
+
637
+ it 'should orphan the Resource' do
638
+ @resource.collection.should_not equal(@articles)
639
+ end
640
+ end
641
+
642
+ it { @articles.should respond_to(:push) }
643
+
644
+ describe '#push' do
645
+ before :all do
646
+ @resources = [ @article_model.new(:title => 'Title 1'), @article_model.new(:title => 'Title 2') ]
647
+
648
+ @return = @articles.push(*@resources)
649
+ end
650
+
651
+ it 'should return a Collection' do
652
+ @return.should be_kind_of(DataMapper::Collection)
653
+ end
654
+
655
+ it 'should return self' do
656
+ @return.should equal(@articles)
657
+ end
658
+
659
+ it 'should append the Resources to the Collection' do
660
+ @articles.should == [ @article ] + @resources
661
+ end
662
+
663
+ it 'should relate the Resources to the Collection' do
664
+ @resources.each { |resource| resource.collection.should equal(@articles) }
665
+ end
666
+ end
667
+
668
+ it { @articles.should respond_to(:reject!) }
669
+
670
+ describe '#reject!' do
671
+ describe 'with a block that matches a Resource in the Collection' do
672
+ before :all do
673
+ @resources = @articles.dup.entries
674
+
675
+ @return = @articles.reject! { true }
676
+ end
677
+
678
+ it 'should return a Collection' do
679
+ @return.should be_kind_of(DataMapper::Collection)
680
+ end
681
+
682
+ it 'should return self' do
683
+ @return.should equal(@articles)
684
+ end
685
+
686
+ it 'should remove the Resources from the Collection' do
687
+ @resources.each { |resource| @articles.should_not be_include(resource) }
688
+ end
689
+
690
+ it 'should orphan the Resources' do
691
+ @resources.each { |resource| resource.collection.should_not equal(@articles) }
692
+ end
693
+ end
694
+
695
+ describe 'with a block that does not match a Resource in the Collection' do
696
+ before :all do
697
+ @resources = @articles.dup.entries
698
+
699
+ @return = @articles.reject! { false }
700
+ end
701
+
702
+ it 'should return nil' do
703
+ @return.should be_nil
704
+ end
705
+
706
+ it 'should not modify the Collection' do
707
+ @articles.should == @resources
708
+ end
709
+ end
710
+ end
711
+
712
+ it { @articles.should respond_to(:reload) }
713
+
714
+ describe '#reload' do
715
+ describe 'with no arguments' do
716
+ before :all do
717
+ @resources = @articles.dup.entries
718
+
719
+ @return = @collection = @articles.reload
720
+ end
721
+
722
+ # FIXME: this is spec order dependent, move this into a helper method
723
+ # and execute in the before :all block
724
+ unless loaded
725
+ it 'should not be a kicker' do
726
+ pending do
727
+ @articles.should_not be_loaded
728
+ end
729
+ end
730
+ end
731
+
732
+ it 'should return a Collection' do
733
+ @return.should be_kind_of(DataMapper::Collection)
734
+ end
735
+
736
+ it 'should return self' do
737
+ @return.should equal(@articles)
738
+ end
739
+
740
+ { :title => true, :content => false }.each do |attribute, expected|
741
+ it "should have query field #{attribute.inspect} #{'not' unless expected} loaded".squeeze(' ') do
742
+ @collection.each { |resource| resource.attribute_loaded?(attribute).should == expected }
743
+ end
744
+ end
745
+ end
746
+
747
+ describe 'with a Hash query' do
748
+ before :all do
749
+ @resources = @articles.dup.entries
750
+
751
+ @return = @collection = @articles.reload(:fields => [ :content ]) # :title is a default field
752
+ end
753
+
754
+ # FIXME: this is spec order dependent, move this into a helper method
755
+ # and execute in the before :all block
756
+ unless loaded
757
+ it 'should not be a kicker' do
758
+ pending do
759
+ @articles.should_not be_loaded
760
+ end
761
+ end
762
+ end
763
+
764
+ it 'should return a Collection' do
765
+ @return.should be_kind_of(DataMapper::Collection)
766
+ end
767
+
768
+ it 'should return self' do
769
+ @return.should equal(@articles)
770
+ end
771
+
772
+ { :id => true, :content => true, :title => true }.each do |attribute, expected|
773
+ it "should have query field #{attribute.inspect} #{'not' unless expected} loaded".squeeze(' ') do
774
+ @collection.each { |resource| resource.attribute_loaded?(attribute).should == expected }
775
+ end
776
+ end
777
+ end
778
+
779
+ describe 'with a Query' do
780
+ before :all do
781
+ @query = DataMapper::Query.new(@repository, @article_model, :fields => [ :content ]) # :title is an original field
782
+
783
+ @return = @collection = @articles.reload(@query)
784
+ end
785
+
786
+ # FIXME: this is spec order dependent, move this into a helper method
787
+ # and execute in the before :all block
788
+ unless loaded
789
+ it 'should not be a kicker' do
790
+ pending do
791
+ @articles.should_not be_loaded
792
+ end
793
+ end
794
+ end
795
+
796
+ it 'should return a Collection' do
797
+ @return.should be_kind_of(DataMapper::Collection)
798
+ end
799
+
800
+ it 'should return self' do
801
+ @return.should equal(@articles)
802
+ end
803
+
804
+ { :id => true, :content => true, :title => loaded }.each do |attribute, expected|
805
+ it "should have query field #{attribute.inspect} #{'not' unless expected} loaded".squeeze(' ') do
806
+ pending_if "TODO: #{@articles.class}#reload should not be a kicker", @one_to_many && loaded == false && attribute == :title do
807
+ @collection.each { |resource| resource.attribute_loaded?(attribute).should == expected }
808
+ end
809
+ end
810
+ end
811
+ end
812
+ end
813
+
814
+ it { @articles.should respond_to(:replace) }
815
+
816
+ describe '#replace' do
817
+ describe 'when provided an Array of Resources' do
818
+ before :all do
819
+ @resources = @articles.dup.entries
820
+
821
+ @return = @articles.replace(@other_articles)
822
+ end
823
+
824
+ it 'should return a Collection' do
825
+ @return.should be_kind_of(DataMapper::Collection)
826
+ end
827
+
828
+ it 'should return self' do
829
+ @return.should equal(@articles)
830
+ end
831
+
832
+ it 'should update the Collection with new Resources' do
833
+ @articles.should == @other_articles
834
+ end
835
+
836
+ it 'should relate each Resource added to the Collection' do
837
+ @articles.each { |resource| resource.collection.should equal(@articles) }
838
+ end
839
+
840
+ it 'should orphan each Resource removed from the Collection' do
841
+ @resources.each { |resource| resource.collection.should_not equal(@articles) }
842
+ end
843
+ end
844
+
845
+ describe 'when provided an Array of Hashes' do
846
+ before :all do
847
+ @array = [ { :content => 'From Hash' } ].freeze
848
+
849
+ @return = @articles.replace(@array)
850
+ end
851
+
852
+ it 'should return a Collection' do
853
+ @return.should be_kind_of(DataMapper::Collection)
854
+ end
855
+
856
+ it 'should return self' do
857
+ @return.should equal(@articles)
858
+ end
859
+
860
+ it 'should initialize a Resource' do
861
+ @return.first.should be_kind_of(DataMapper::Resource)
862
+ end
863
+
864
+ it 'should be a new Resource' do
865
+ @return.first.should be_new
866
+ end
867
+
868
+ it 'should be a Resource with attributes matching the Hash' do
869
+ @return.first.attributes.only(*@array.first.keys).should == @array.first
870
+ end
871
+ end
872
+ end
873
+
874
+ it { @articles.should respond_to(:reverse!) }
875
+
876
+ describe '#reverse!' do
877
+ before :all do
878
+ @query = @articles.query
879
+
880
+ @new = @articles.create(:title => 'Sample Article')
881
+
882
+ @return = @articles.reverse!
883
+ end
884
+
885
+ it 'should return a Collection' do
886
+ @return.should be_kind_of(DataMapper::Collection)
887
+ end
888
+
889
+ it 'should return self' do
890
+ @return.should equal(@articles)
891
+ end
892
+
893
+ it 'should return a Collection with reversed entries' do
894
+ @return.should == [ @new, @article ]
895
+ end
896
+
897
+ it 'should return a Query that equal to the original' do
898
+ @return.query.should equal(@query)
899
+ end
900
+ end
901
+
902
+ [ :save, :save! ].each do |method|
903
+ it { @articles.should respond_to(method) }
904
+
905
+ describe "##{method}" do
906
+ describe 'when Resources are not saved' do
907
+ before :all do
908
+ @articles.new(:title => 'New Article', :content => 'New Article')
909
+
910
+ @return = @articles.send(method)
911
+ end
912
+
913
+ it 'should return true' do
914
+ @return.should be_true
915
+ end
916
+
917
+ it 'should save each Resource' do
918
+ @articles.each { |resource| resource.should be_saved }
919
+ end
920
+ end
921
+
922
+ describe 'when Resources have been orphaned' do
923
+ before :all do
924
+ @resources = @articles.entries
925
+ @articles.replace([])
926
+
927
+ @return = @articles.send(method)
928
+ end
929
+
930
+ it 'should return true' do
931
+ @return.should be_true
932
+ end
933
+
934
+ it 'should orphan the Resources' do
935
+ @resources.each { |resource| resource.collection.should_not equal(@articles) }
936
+ end
937
+ end
938
+ end
939
+ end
940
+
941
+ it { @articles.should respond_to(:shift) }
942
+
943
+ describe '#shift' do
944
+ before :all do
945
+ @new = @articles.create(:title => 'Sample Article')
946
+
947
+ @return = @resource = @articles.shift
948
+ end
949
+
950
+ it 'should return a Resource' do
951
+ @return.should be_kind_of(DataMapper::Resource)
952
+ end
953
+
954
+ it 'should be the first Resource in the Collection' do
955
+ @resource.key.should == @article.key
956
+ end
957
+
958
+ it 'should remove the Resource from the Collection' do
959
+ @articles.should_not be_include(@resource)
960
+ end
961
+
962
+ it 'should orphan the Resource' do
963
+ @resource.collection.should_not equal(@articles)
964
+ end
965
+ end
966
+
967
+ it { @articles.should respond_to(:slice!) }
968
+
969
+ describe '#slice!' do
970
+ before :all do
971
+ 1.upto(10) { |number| @articles.create(:content => "Article #{number}") }
972
+
973
+ @copy = @articles.dup
974
+ end
975
+
976
+ describe 'with a positive offset' do
977
+ before :all do
978
+ unless @skip
979
+ @return = @resource = @articles.slice!(0)
980
+ end
981
+ end
982
+
983
+ it 'should return a Resource' do
984
+ @return.should be_kind_of(DataMapper::Resource)
985
+ end
986
+
987
+ it 'should return expected Resource' do
988
+ @return.key.should == @article.key
989
+ end
990
+
991
+ it 'should return the same as Array#slice!' do
992
+ @return.should == @copy.entries.slice!(0)
993
+ end
994
+
995
+ it 'should remove the Resource from the Collection' do
996
+ @articles.should_not be_include(@resource)
997
+ end
998
+
999
+ it 'should orphan the Resource' do
1000
+ @resource.collection.should_not equal(@articles)
1001
+ end
1002
+ end
1003
+
1004
+ describe 'with a positive offset and length' do
1005
+ before :all do
1006
+ unless @skip
1007
+ @return = @resources = @articles.slice!(5, 5)
1008
+ end
1009
+ end
1010
+
1011
+ it 'should return a Collection' do
1012
+ @return.should be_kind_of(DataMapper::Collection)
1013
+ end
1014
+
1015
+ it 'should return the expected Resource' do
1016
+ @return.should == @copy.entries.slice!(5, 5)
1017
+ end
1018
+
1019
+ it 'should remove the Resources from the Collection' do
1020
+ @resources.each { |resource| @articles.should_not be_include(resource) }
1021
+ end
1022
+
1023
+ it 'should orphan the Resources' do
1024
+ @resources.each { |resource| resource.collection.should_not equal(@articles) }
1025
+ end
1026
+
1027
+ it 'should scope the Collection' do
1028
+ @resources.reload.should == @copy.entries.slice!(5, 5)
1029
+ end
1030
+ end
1031
+
1032
+ describe 'with a positive range' do
1033
+ before :all do
1034
+ unless @skip
1035
+ @return = @resources = @articles.slice!(5..10)
1036
+ end
1037
+ end
1038
+
1039
+ it 'should return a Collection' do
1040
+ @return.should be_kind_of(DataMapper::Collection)
1041
+ end
1042
+
1043
+ it 'should return the expected Resources' do
1044
+ @return.should == @copy.entries.slice!(5..10)
1045
+ end
1046
+
1047
+ it 'should remove the Resources from the Collection' do
1048
+ @resources.each { |resource| @articles.should_not be_include(resource) }
1049
+ end
1050
+
1051
+ it 'should orphan the Resources' do
1052
+ @resources.each { |resource| resource.collection.should_not equal(@articles) }
1053
+ end
1054
+
1055
+ it 'should scope the Collection' do
1056
+ @resources.reload.should == @copy.entries.slice!(5..10)
1057
+ end
1058
+ end
1059
+
1060
+ describe 'with a negative offset' do
1061
+ before :all do
1062
+ unless @skip
1063
+ @return = @resource = @articles.slice!(-1)
1064
+ end
1065
+ end
1066
+
1067
+ it 'should return a Resource' do
1068
+ @return.should be_kind_of(DataMapper::Resource)
1069
+ end
1070
+
1071
+ it 'should return expected Resource' do
1072
+ @return.should == @copy.entries.slice!(-1)
1073
+ end
1074
+
1075
+ it 'should remove the Resource from the Collection' do
1076
+ @articles.should_not be_include(@resource)
1077
+ end
1078
+
1079
+ it 'should orphan the Resource' do
1080
+ @resource.collection.should_not equal(@articles)
1081
+ end
1082
+ end
1083
+
1084
+ describe 'with a negative offset and length' do
1085
+ before :all do
1086
+ unless @skip
1087
+ @return = @resources = @articles.slice!(-5, 5)
1088
+ end
1089
+ end
1090
+
1091
+ it 'should return a Collection' do
1092
+ @return.should be_kind_of(DataMapper::Collection)
1093
+ end
1094
+
1095
+ it 'should return the expected Resources' do
1096
+ @return.should == @copy.entries.slice!(-5, 5)
1097
+ end
1098
+
1099
+ it 'should remove the Resources from the Collection' do
1100
+ @resources.each { |resource| @articles.should_not be_include(resource) }
1101
+ end
1102
+
1103
+ it 'should orphan the Resources' do
1104
+ @resources.each { |resource| resource.collection.should_not equal(@articles) }
1105
+ end
1106
+
1107
+ it 'should scope the Collection' do
1108
+ @resources.reload.should == @copy.entries.slice!(-5, 5)
1109
+ end
1110
+ end
1111
+
1112
+ describe 'with a negative range' do
1113
+ before :all do
1114
+ unless @skip
1115
+ @return = @resources = @articles.slice!(-3..-2)
1116
+ end
1117
+ end
1118
+
1119
+ it 'should return a Collection' do
1120
+ @return.should be_kind_of(DataMapper::Collection)
1121
+ end
1122
+
1123
+ it 'should return the expected Resources' do
1124
+ @return.should == @copy.entries.slice!(-3..-2)
1125
+ end
1126
+
1127
+ it 'should remove the Resources from the Collection' do
1128
+ @resources.each { |resource| @articles.should_not be_include(resource) }
1129
+ end
1130
+
1131
+ it 'should orphan the Resources' do
1132
+ @resources.each { |resource| resource.collection.should_not equal(@articles) }
1133
+ end
1134
+
1135
+ it 'should scope the Collection' do
1136
+ @resources.reload.should == @copy.entries.slice!(-3..-2)
1137
+ end
1138
+ end
1139
+
1140
+ describe 'with an offset not within the Collection' do
1141
+ before :all do
1142
+ unless @skip
1143
+ @return = @articles.slice!(12)
1144
+ end
1145
+ end
1146
+
1147
+ it 'should return nil' do
1148
+ @return.should be_nil
1149
+ end
1150
+ end
1151
+
1152
+ describe 'with an offset and length not within the Collection' do
1153
+ before :all do
1154
+ unless @skip
1155
+ @return = @articles.slice!(12, 1)
1156
+ end
1157
+ end
1158
+
1159
+ it 'should return nil' do
1160
+ @return.should be_nil
1161
+ end
1162
+ end
1163
+
1164
+ describe 'with a range not within the Collection' do
1165
+ before :all do
1166
+ unless @skip
1167
+ @return = @articles.slice!(12..13)
1168
+ end
1169
+ end
1170
+
1171
+ it 'should return nil' do
1172
+ @return.should be_nil
1173
+ end
1174
+ end
1175
+ end
1176
+
1177
+ it { @articles.should respond_to(:sort!) }
1178
+
1179
+ describe '#sort!' do
1180
+ describe 'without a block' do
1181
+ before :all do
1182
+ @return = @articles.unshift(@other).sort!
1183
+ end
1184
+
1185
+ it 'should return a Collection' do
1186
+ @return.should be_kind_of(DataMapper::Collection)
1187
+ end
1188
+
1189
+ it 'should return self' do
1190
+ @return.should equal(@articles)
1191
+ end
1192
+
1193
+ it 'should modify and sort the Collection using default sort order' do
1194
+ @articles.should == [ @article, @other ]
1195
+ end
1196
+ end
1197
+
1198
+ describe 'with a block' do
1199
+ before :all do
1200
+ @return = @articles.unshift(@other).sort! { |a_resource, b_resource| b_resource.id <=> a_resource.id }
1201
+ end
1202
+
1203
+ it 'should return a Collection' do
1204
+ @return.should be_kind_of(DataMapper::Collection)
1205
+ end
1206
+
1207
+ it 'should return self' do
1208
+ @return.should equal(@articles)
1209
+ end
1210
+
1211
+ it 'should modify and sort the Collection using supplied block' do
1212
+ @articles.should == [ @other, @article ]
1213
+ end
1214
+ end
1215
+ end
1216
+
1217
+ [ :splice, :[]= ].each do |method|
1218
+ it { @articles.should respond_to(method) }
1219
+
1220
+ describe "##{method}" do
1221
+ before :all do
1222
+ unless @skip
1223
+ orphans = (1..10).map do |number|
1224
+ articles = @articles.dup
1225
+ articles.create(:content => "Article #{number}")
1226
+ articles.pop # remove the article from the tail
1227
+ end
1228
+
1229
+ @articles.unshift(*orphans.first(5))
1230
+ @articles.concat(orphans.last(5))
1231
+
1232
+ unless loaded
1233
+ @articles.should_not be_loaded
1234
+ end
1235
+
1236
+ @copy = @articles.dup
1237
+ @new = @article_model.new(:content => 'New Article')
1238
+ end
1239
+ end
1240
+
1241
+ describe 'with a positive offset and a Resource' do
1242
+ before :all do
1243
+ rescue_if 'TODO', @skip do
1244
+ @original = @copy[1]
1245
+ @original.collection.should equal(@articles)
1246
+
1247
+ @return = @resource = @articles.send(method, 1, @new)
1248
+ end
1249
+ end
1250
+
1251
+ should_not_be_a_kicker
1252
+
1253
+ it 'should return a Resource' do
1254
+ @return.should be_kind_of(DataMapper::Resource)
1255
+ end
1256
+
1257
+ it 'should return expected Resource' do
1258
+ @return.should equal(@new)
1259
+ end
1260
+
1261
+ it 'should return the same as Array#[]=' do
1262
+ @return.should == @copy.entries[1] = @new
1263
+ end
1264
+
1265
+ it 'should include the Resource in the Collection' do
1266
+ @articles.should be_include(@resource)
1267
+ end
1268
+
1269
+ it 'should relate the Resource to the Collection' do
1270
+ @resource.collection.should equal(@articles)
1271
+ end
1272
+
1273
+ it 'should orphan the original Resource' do
1274
+ @original.collection.should_not equal(@articles)
1275
+ end
1276
+ end
1277
+
1278
+ describe 'with a positive offset and length and a Resource' do
1279
+ before :all do
1280
+ rescue_if 'TODO', @skip do
1281
+ @original = @copy[2]
1282
+ @original.collection.should equal(@articles)
1283
+
1284
+ @return = @resource = @articles.send(method, 2, 1, @new)
1285
+ end
1286
+ end
1287
+
1288
+ should_not_be_a_kicker
1289
+
1290
+ it 'should return a Resource' do
1291
+ @return.should be_kind_of(DataMapper::Resource)
1292
+ end
1293
+
1294
+ it 'should return the expected Resource' do
1295
+ @return.should equal(@new)
1296
+ end
1297
+
1298
+ it 'should return the same as Array#[]=' do
1299
+ @return.should == @copy.entries[2, 1] = @new
1300
+ end
1301
+
1302
+ it 'should include the Resource in the Collection' do
1303
+ @articles.should be_include(@resource)
1304
+ end
1305
+
1306
+ it 'should orphan the original Resource' do
1307
+ @original.collection.should_not equal(@articles)
1308
+ end
1309
+ end
1310
+
1311
+ describe 'with a positive range and a Resource' do
1312
+ before :all do
1313
+ rescue_if 'TODO', @skip do
1314
+ @originals = @copy.values_at(2..3)
1315
+ @originals.each { |resource| resource.collection.should equal(@articles) }
1316
+
1317
+ @return = @resource = @articles.send(method, 2..3, @new)
1318
+ end
1319
+ end
1320
+
1321
+ should_not_be_a_kicker
1322
+
1323
+ it 'should return a Resource' do
1324
+ @return.should be_kind_of(DataMapper::Resource)
1325
+ end
1326
+
1327
+ it 'should return the expected Resources' do
1328
+ @return.should equal(@new)
1329
+ end
1330
+
1331
+ it 'should return the same as Array#[]=' do
1332
+ @return.should == @copy.entries[2..3] = @new
1333
+ end
1334
+
1335
+ it 'should include the Resource in the Collection' do
1336
+ @articles.should be_include(@resource)
1337
+ end
1338
+
1339
+ it 'should orphan the original Resources' do
1340
+ @originals.each { |resource| resource.collection.should_not equal(@articles) }
1341
+ end
1342
+ end
1343
+
1344
+ describe 'with a negative offset and a Resource' do
1345
+ before :all do
1346
+ rescue_if 'TODO', @skip do
1347
+ @original = @copy[-1]
1348
+ @original.collection.should equal(@articles)
1349
+
1350
+ @return = @resource = @articles.send(method, -1, @new)
1351
+ end
1352
+ end
1353
+
1354
+ should_not_be_a_kicker
1355
+
1356
+ it 'should return a Resource' do
1357
+ @return.should be_kind_of(DataMapper::Resource)
1358
+ end
1359
+
1360
+ it 'should return expected Resource' do
1361
+ @return.should equal(@new)
1362
+ end
1363
+
1364
+ it 'should return the same as Array#[]=' do
1365
+ @return.should == @copy.entries[-1] = @new
1366
+ end
1367
+
1368
+ it 'should include the Resource in the Collection' do
1369
+ @articles.should be_include(@resource)
1370
+ end
1371
+
1372
+ it 'should relate the Resource to the Collection' do
1373
+ @resource.collection.should equal(@articles)
1374
+ end
1375
+
1376
+ it 'should orphan the original Resource' do
1377
+ @original.collection.should_not equal(@articles)
1378
+ end
1379
+ end
1380
+
1381
+ describe 'with a negative offset and length and a Resource' do
1382
+ before :all do
1383
+ rescue_if 'TODO', @skip do
1384
+ @original = @copy[-2]
1385
+ @original.collection.should equal(@articles)
1386
+
1387
+ @return = @resource = @articles.send(method, -2, 1, @new)
1388
+ end
1389
+ end
1390
+
1391
+ should_not_be_a_kicker
1392
+
1393
+ it 'should return a Resource' do
1394
+ @return.should be_kind_of(DataMapper::Resource)
1395
+ end
1396
+
1397
+ it 'should return the expected Resource' do
1398
+ @return.should equal(@new)
1399
+ end
1400
+
1401
+ it 'should return the same as Array#[]=' do
1402
+ @return.should == @copy.entries[-2, 1] = @new
1403
+ end
1404
+
1405
+ it 'should include the Resource in the Collection' do
1406
+ @articles.should be_include(@resource)
1407
+ end
1408
+
1409
+ it 'should orphan the original Resource' do
1410
+ @original.collection.should_not equal(@articles)
1411
+ end
1412
+ end
1413
+
1414
+ describe 'with a negative range and a Resource' do
1415
+ before :all do
1416
+ rescue_if 'TODO', @skip do
1417
+ @originals = @articles.values_at(-3..-2)
1418
+ @originals.each { |resource| resource.collection.should equal(@articles) }
1419
+
1420
+ @return = @resource = @articles.send(method, -3..-2, @new)
1421
+ end
1422
+ end
1423
+
1424
+ should_not_be_a_kicker
1425
+
1426
+ it 'should return a Resource' do
1427
+ @return.should be_kind_of(DataMapper::Resource)
1428
+ end
1429
+
1430
+ it 'should return the expected Resources' do
1431
+ @return.should equal(@new)
1432
+ end
1433
+
1434
+ it 'should return the same as Array#[]=' do
1435
+ @return.should == @copy.entries[-3..-2] = @new
1436
+ end
1437
+
1438
+ it 'should include the Resource in the Collection' do
1439
+ @articles.should be_include(@resource)
1440
+ end
1441
+
1442
+ it 'should orphan the original Resources' do
1443
+ @originals.each { |resource| resource.collection.should_not equal(@articles) }
1444
+ end
1445
+ end
1446
+ end
1447
+ end
1448
+
1449
+ describe '#[]=' do
1450
+ describe 'when swapping resources' do
1451
+ before :all do
1452
+ rescue_if 'TODO', @skip do
1453
+ @articles.create(:content => 'Another Article')
1454
+
1455
+ @entries = @articles.entries
1456
+
1457
+ @articles[0], @articles[1] = @articles[1], @articles[0]
1458
+ end
1459
+ end
1460
+
1461
+ it 'should include the Resource in the Collection' do
1462
+ @articles.should == @entries.reverse
1463
+ end
1464
+
1465
+ it 'should relate the Resource to the Collection' do
1466
+ @articles.each { |resource| resource.collection.should equal(@articles) }
1467
+ end
1468
+ end
1469
+ end
1470
+
1471
+ it { @articles.should respond_to(:unshift) }
1472
+
1473
+ describe '#unshift' do
1474
+ before :all do
1475
+ @resources = [ @article_model.new(:title => 'Title 1'), @article_model.new(:title => 'Title 2') ]
1476
+
1477
+ @return = @articles.unshift(*@resources)
1478
+ end
1479
+
1480
+ it 'should return a Collection' do
1481
+ @return.should be_kind_of(DataMapper::Collection)
1482
+ end
1483
+
1484
+ it 'should return self' do
1485
+ @return.should equal(@articles)
1486
+ end
1487
+
1488
+ it 'should prepend the Resources to the Collection' do
1489
+ @articles.should == @resources + [ @article ]
1490
+ end
1491
+
1492
+ it 'should relate the Resources to the Collection' do
1493
+ @resources.each { |resource| resource.collection.should equal(@articles) }
1494
+ end
1495
+ end
1496
+
1497
+ [ :update, :update! ].each do |method|
1498
+ it { @articles.should respond_to(method) }
1499
+
1500
+ describe "##{method}" do
1501
+ describe 'with no arguments' do
1502
+ before :all do
1503
+ @return = @articles.send(method)
1504
+ end
1505
+
1506
+ if method == :update!
1507
+ should_not_be_a_kicker
1508
+ end
1509
+
1510
+ it 'should return true' do
1511
+ @return.should be_true
1512
+ end
1513
+ end
1514
+
1515
+ describe 'with attributes' do
1516
+ before :all do
1517
+ @attributes = { :title => 'Updated Title' }
1518
+
1519
+ @return = @articles.send(method, @attributes)
1520
+ end
1521
+
1522
+ if method == :update!
1523
+ # FIXME: this is spec order dependent, move this into a helper method
1524
+ # and execute in the before :all block
1525
+ unless loaded
1526
+ it 'should not be a kicker' do
1527
+ pending_if 'TODO', @many_to_many do
1528
+ @articles.should_not be_loaded
1529
+ end
1530
+ end
1531
+ end
1532
+ end
1533
+
1534
+ it 'should return true' do
1535
+ @return.should be_true
1536
+ end
1537
+
1538
+ it 'should update attributes of all Resources' do
1539
+ @articles.each { |resource| @attributes.each { |key, value| resource.send(key).should == value } }
1540
+ end
1541
+
1542
+ it 'should persist the changes' do
1543
+ resource = @article_model.get(*@article.key)
1544
+ @attributes.each { |key, value| resource.send(key).should == value }
1545
+ end
1546
+ end
1547
+
1548
+ describe 'with attributes where one is a parent association' do
1549
+ before :all do
1550
+ @attributes = { :original => @other }
1551
+
1552
+ @return = @articles.send(method, @attributes)
1553
+ end
1554
+
1555
+ if method == :update!
1556
+ # FIXME: this is spec order dependent, move this into a helper method
1557
+ # and execute in the before :all block
1558
+ unless loaded
1559
+ it 'should not be a kicker' do
1560
+ pending_if 'TODO', @many_to_many do
1561
+ @articles.should_not be_loaded
1562
+ end
1563
+ end
1564
+ end
1565
+ end
1566
+
1567
+ it 'should return true' do
1568
+ @return.should be_true
1569
+ end
1570
+
1571
+ it 'should update attributes of all Resources' do
1572
+ @articles.each { |resource| @attributes.each { |key, value| resource.send(key).should == value } }
1573
+ end
1574
+
1575
+ it 'should persist the changes' do
1576
+ resource = @article_model.get(*@article.key)
1577
+ @attributes.each { |key, value| resource.send(key).should == value }
1578
+ end
1579
+ end
1580
+
1581
+ describe 'with attributes where a not-nullable property is nil' do
1582
+ before :all do
1583
+ @return = @articles.send(method, :title => nil)
1584
+ end
1585
+
1586
+ if method == :update!
1587
+ should_not_be_a_kicker
1588
+ end
1589
+
1590
+ it 'should return false' do
1591
+ @return.should be_false
1592
+ end
1593
+ end
1594
+
1595
+ describe 'on a limited collection' do
1596
+ before :all do
1597
+ @other = @articles.create
1598
+ @limited = @articles.all(:limit => 1)
1599
+ @attributes = { :content => 'Updated Content' }
1600
+
1601
+ @return = @limited.send(method, @attributes)
1602
+ end
1603
+
1604
+ if method == :update!
1605
+ # FIXME: this is spec order dependent, move this into a helper method
1606
+ # and execute in the before :all block
1607
+ unless loaded
1608
+ it 'should not be a kicker' do
1609
+ pending "Update Collection##{method} to use a subquery" do
1610
+ @limited.should_not be_loaded
1611
+ end
1612
+ end
1613
+ end
1614
+ end
1615
+
1616
+ it 'should return true' do
1617
+ @return.should be_true
1618
+ end
1619
+
1620
+ it 'should bypass validation' do
1621
+ pending 'TODO: not sure how to best spec this'
1622
+ end
1623
+
1624
+ it 'should update attributes of all Resources' do
1625
+ @limited.each { |resource| @attributes.each { |key, value| resource.send(key).should == value } }
1626
+ end
1627
+
1628
+ it 'should persist the changes' do
1629
+ resource = @article_model.get(*@article.key)
1630
+ @attributes.each { |key, value| resource.send(key).should == value }
1631
+ end
1632
+
1633
+ it 'should not update the other Resource' do
1634
+ @other.reload
1635
+ @attributes.each { |key, value| @other.send(key).should_not == value }
1636
+ end
1637
+ end
1638
+
1639
+ describe 'on a dirty collection' do
1640
+ before :all do
1641
+ @articles.each { |r| r.content = 'Changed' }
1642
+ end
1643
+
1644
+ it 'should raise an exception' do
1645
+ lambda {
1646
+ @articles.send(method, :content => 'New Content')
1647
+ }.should raise_error(DataMapper::UpdateConflictError, "##{method} cannot be called on a dirty collection")
1648
+ end
1649
+ end
1650
+ end
1651
+ end
1652
+
1653
+ it 'should respond to a public model method with #method_missing' do
1654
+ @articles.should respond_to(:base_model)
1655
+ end
1656
+
1657
+ describe '#method_missing' do
1658
+ describe 'with a public model method' do
1659
+ before :all do
1660
+ @return = @articles.model.base_model
1661
+ end
1662
+
1663
+ should_not_be_a_kicker
1664
+
1665
+ it 'should return expected object' do
1666
+ @return.should == @article_model
1667
+ end
1668
+ end
1669
+ end
1670
+ end