datamapper-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 (192) hide show
  1. data/.autotest +17 -14
  2. data/.gitignore +3 -1
  3. data/FAQ +6 -5
  4. data/History.txt +5 -39
  5. data/Manifest.txt +67 -76
  6. data/QUICKLINKS +1 -1
  7. data/README.txt +21 -15
  8. data/Rakefile +16 -15
  9. data/SPECS +2 -29
  10. data/TODO +1 -1
  11. data/dm-core.gemspec +11 -15
  12. data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
  13. data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
  14. data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
  15. data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
  16. data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
  19. data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
  20. data/lib/dm-core/adapters.rb +135 -16
  21. data/lib/dm-core/associations/many_to_many.rb +372 -90
  22. data/lib/dm-core/associations/many_to_one.rb +220 -73
  23. data/lib/dm-core/associations/one_to_many.rb +319 -255
  24. data/lib/dm-core/associations/one_to_one.rb +66 -53
  25. data/lib/dm-core/associations/relationship.rb +560 -158
  26. data/lib/dm-core/collection.rb +1104 -381
  27. data/lib/dm-core/core_ext/kernel.rb +12 -0
  28. data/lib/dm-core/core_ext/symbol.rb +10 -0
  29. data/lib/dm-core/identity_map.rb +4 -34
  30. data/lib/dm-core/migrations.rb +1283 -0
  31. data/lib/dm-core/model/descendant_set.rb +81 -0
  32. data/lib/dm-core/model/hook.rb +45 -0
  33. data/lib/dm-core/model/is.rb +32 -0
  34. data/lib/dm-core/model/property.rb +248 -0
  35. data/lib/dm-core/model/relationship.rb +335 -0
  36. data/lib/dm-core/model/scope.rb +90 -0
  37. data/lib/dm-core/model.rb +570 -369
  38. data/lib/dm-core/property.rb +753 -280
  39. data/lib/dm-core/property_set.rb +141 -98
  40. data/lib/dm-core/query/conditions/comparison.rb +814 -0
  41. data/lib/dm-core/query/conditions/operation.rb +247 -0
  42. data/lib/dm-core/query/direction.rb +43 -0
  43. data/lib/dm-core/query/operator.rb +42 -0
  44. data/lib/dm-core/query/path.rb +102 -0
  45. data/lib/dm-core/query/sort.rb +45 -0
  46. data/lib/dm-core/query.rb +974 -492
  47. data/lib/dm-core/repository.rb +147 -107
  48. data/lib/dm-core/resource.rb +644 -429
  49. data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
  50. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
  51. data/lib/dm-core/support/chainable.rb +20 -0
  52. data/lib/dm-core/support/deprecate.rb +12 -0
  53. data/lib/dm-core/support/equalizer.rb +23 -0
  54. data/lib/dm-core/support/logger.rb +13 -0
  55. data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
  56. data/lib/dm-core/transaction.rb +333 -92
  57. data/lib/dm-core/type.rb +98 -60
  58. data/lib/dm-core/types/boolean.rb +1 -1
  59. data/lib/dm-core/types/discriminator.rb +34 -20
  60. data/lib/dm-core/types/object.rb +7 -4
  61. data/lib/dm-core/types/paranoid_boolean.rb +11 -9
  62. data/lib/dm-core/types/paranoid_datetime.rb +11 -9
  63. data/lib/dm-core/types/serial.rb +3 -3
  64. data/lib/dm-core/types/text.rb +3 -4
  65. data/lib/dm-core/version.rb +1 -1
  66. data/lib/dm-core.rb +106 -110
  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/model/relationship_spec.rb +924 -0
  80. data/spec/public/model_spec.rb +159 -0
  81. data/spec/public/property_spec.rb +829 -0
  82. data/spec/public/resource_spec.rb +71 -0
  83. data/spec/public/sel_spec.rb +44 -0
  84. data/spec/public/setup_spec.rb +145 -0
  85. data/spec/public/shared/association_collection_shared_spec.rb +317 -0
  86. data/spec/public/shared/collection_shared_spec.rb +1723 -0
  87. data/spec/public/shared/finder_shared_spec.rb +1619 -0
  88. data/spec/public/shared/resource_shared_spec.rb +924 -0
  89. data/spec/public/shared/sel_shared_spec.rb +112 -0
  90. data/spec/public/transaction_spec.rb +129 -0
  91. data/spec/public/types/discriminator_spec.rb +130 -0
  92. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  93. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  94. data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
  95. data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
  96. data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
  97. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
  98. data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
  99. data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
  100. data/spec/semipublic/associations/relationship_spec.rb +194 -0
  101. data/spec/semipublic/associations_spec.rb +177 -0
  102. data/spec/semipublic/collection_spec.rb +142 -0
  103. data/spec/semipublic/property_spec.rb +61 -0
  104. data/spec/semipublic/query/conditions_spec.rb +528 -0
  105. data/spec/semipublic/query/path_spec.rb +443 -0
  106. data/spec/semipublic/query_spec.rb +2626 -0
  107. data/spec/semipublic/resource_spec.rb +47 -0
  108. data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
  109. data/spec/spec.opts +3 -1
  110. data/spec/spec_helper.rb +80 -57
  111. data/tasks/ci.rb +19 -31
  112. data/tasks/dm.rb +43 -48
  113. data/tasks/doc.rb +8 -11
  114. data/tasks/gemspec.rb +5 -5
  115. data/tasks/hoe.rb +15 -16
  116. data/tasks/install.rb +8 -10
  117. metadata +72 -93
  118. data/lib/dm-core/associations/relationship_chain.rb +0 -81
  119. data/lib/dm-core/associations.rb +0 -207
  120. data/lib/dm-core/auto_migrations.rb +0 -105
  121. data/lib/dm-core/dependency_queue.rb +0 -32
  122. data/lib/dm-core/hook.rb +0 -11
  123. data/lib/dm-core/is.rb +0 -16
  124. data/lib/dm-core/logger.rb +0 -232
  125. data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
  126. data/lib/dm-core/migrator.rb +0 -29
  127. data/lib/dm-core/scope.rb +0 -58
  128. data/lib/dm-core/support/array.rb +0 -13
  129. data/lib/dm-core/support/assertions.rb +0 -8
  130. data/lib/dm-core/support/errors.rb +0 -23
  131. data/lib/dm-core/support/kernel.rb +0 -11
  132. data/lib/dm-core/support/symbol.rb +0 -41
  133. data/lib/dm-core/support.rb +0 -7
  134. data/lib/dm-core/type_map.rb +0 -80
  135. data/lib/dm-core/types.rb +0 -19
  136. data/script/all +0 -4
  137. data/spec/integration/association_spec.rb +0 -1382
  138. data/spec/integration/association_through_spec.rb +0 -203
  139. data/spec/integration/associations/many_to_many_spec.rb +0 -449
  140. data/spec/integration/associations/many_to_one_spec.rb +0 -163
  141. data/spec/integration/associations/one_to_many_spec.rb +0 -188
  142. data/spec/integration/auto_migrations_spec.rb +0 -413
  143. data/spec/integration/collection_spec.rb +0 -1073
  144. data/spec/integration/data_objects_adapter_spec.rb +0 -32
  145. data/spec/integration/dependency_queue_spec.rb +0 -46
  146. data/spec/integration/model_spec.rb +0 -197
  147. data/spec/integration/mysql_adapter_spec.rb +0 -85
  148. data/spec/integration/postgres_adapter_spec.rb +0 -731
  149. data/spec/integration/property_spec.rb +0 -253
  150. data/spec/integration/query_spec.rb +0 -514
  151. data/spec/integration/repository_spec.rb +0 -61
  152. data/spec/integration/resource_spec.rb +0 -513
  153. data/spec/integration/sqlite3_adapter_spec.rb +0 -352
  154. data/spec/integration/sti_spec.rb +0 -273
  155. data/spec/integration/strategic_eager_loading_spec.rb +0 -156
  156. data/spec/integration/transaction_spec.rb +0 -75
  157. data/spec/integration/type_spec.rb +0 -275
  158. data/spec/lib/logging_helper.rb +0 -18
  159. data/spec/lib/mock_adapter.rb +0 -27
  160. data/spec/lib/model_loader.rb +0 -100
  161. data/spec/lib/publicize_methods.rb +0 -28
  162. data/spec/models/content.rb +0 -16
  163. data/spec/models/vehicles.rb +0 -34
  164. data/spec/models/zoo.rb +0 -48
  165. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
  166. data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
  167. data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
  168. data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
  169. data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
  170. data/spec/unit/associations/many_to_many_spec.rb +0 -32
  171. data/spec/unit/associations/many_to_one_spec.rb +0 -159
  172. data/spec/unit/associations/one_to_many_spec.rb +0 -393
  173. data/spec/unit/associations/one_to_one_spec.rb +0 -7
  174. data/spec/unit/associations/relationship_spec.rb +0 -71
  175. data/spec/unit/associations_spec.rb +0 -242
  176. data/spec/unit/auto_migrations_spec.rb +0 -111
  177. data/spec/unit/collection_spec.rb +0 -182
  178. data/spec/unit/data_mapper_spec.rb +0 -35
  179. data/spec/unit/identity_map_spec.rb +0 -126
  180. data/spec/unit/is_spec.rb +0 -80
  181. data/spec/unit/migrator_spec.rb +0 -33
  182. data/spec/unit/model_spec.rb +0 -321
  183. data/spec/unit/naming_conventions_spec.rb +0 -36
  184. data/spec/unit/property_set_spec.rb +0 -90
  185. data/spec/unit/property_spec.rb +0 -753
  186. data/spec/unit/query_spec.rb +0 -571
  187. data/spec/unit/repository_spec.rb +0 -93
  188. data/spec/unit/resource_spec.rb +0 -649
  189. data/spec/unit/scope_spec.rb +0 -142
  190. data/spec/unit/transaction_spec.rb +0 -493
  191. data/spec/unit/type_map_spec.rb +0 -114
  192. data/spec/unit/type_spec.rb +0 -119
@@ -0,0 +1,924 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ share_examples_for 'it creates a one accessor' do
4
+ describe 'accessor' do
5
+ describe 'when there is no associated resource' do
6
+ describe 'without a query' do
7
+ before :all do
8
+ @return = @car.send(@name)
9
+ end
10
+
11
+ it 'should return nil' do
12
+ @return.should be_nil
13
+ end
14
+ end
15
+
16
+ describe 'with a query' do
17
+ before :all do
18
+ @return = @car.send(@name, :id => 99)
19
+ end
20
+
21
+ it 'should return nil' do
22
+ @return.should be_nil
23
+ end
24
+ end
25
+ end
26
+
27
+ describe 'when there is an associated resource' do
28
+ before :all do
29
+ @expected = @model.new
30
+ @car.send("#{@name}=", @expected)
31
+ end
32
+
33
+ describe 'without a query' do
34
+ before :all do
35
+ @return = @car.send(@name)
36
+ end
37
+
38
+ it 'should return a Resource' do
39
+ @return.should be_kind_of(DataMapper::Resource)
40
+ end
41
+
42
+ it 'should return the expected Resource' do
43
+ @return.should equal(@expected)
44
+ end
45
+ end
46
+
47
+ describe 'with a query' do
48
+ before :all do
49
+ @car.save # save @car and @expected to set @expected.id
50
+
51
+ @expected.id.should_not be_nil
52
+
53
+ @return = @car.send(@name, :id => @expected.id)
54
+ end
55
+
56
+ it 'should return a Resource' do
57
+ @return.should be_kind_of(DataMapper::Resource)
58
+ end
59
+
60
+ it 'should return the expected Resource' do
61
+ @return.should == @expected
62
+ end
63
+ end
64
+ end
65
+
66
+ describe 'when the target model is scoped' do
67
+ before :all do
68
+ @resource = @model.new
69
+ @car.send("#{@name}=", @resource)
70
+ @car.save
71
+
72
+ # set the model scope to not match the expected resource
73
+ @model.default_scope.update(:id.not => @resource.id)
74
+
75
+ @return = @car.model.get(*@car.key).send(@name)
76
+ end
77
+
78
+ it 'should return nil' do
79
+ @return.should be_nil
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ share_examples_for 'it creates a one mutator' do
86
+ describe 'mutator' do
87
+ describe 'when setting a Resource' do
88
+ before :all do
89
+ @expected = @model.new
90
+
91
+ @return = @car.send("#{@name}=", @expected)
92
+ end
93
+
94
+ it 'should return the expected Resource' do
95
+ @return.should equal(@expected)
96
+ end
97
+
98
+ it 'should set the Resource' do
99
+ @car.send(@name).should equal(@expected)
100
+ end
101
+
102
+ it 'should relate associated Resource' do
103
+ relationship = Car.relationships[@name]
104
+ many_to_one = relationship.kind_of?(DataMapper::Associations::ManyToOne::Relationship)
105
+ one_to_one_through = relationship.kind_of?(DataMapper::Associations::OneToOne::Relationship) && relationship.respond_to?(:through)
106
+
107
+ pending_if 'TODO', many_to_one || one_to_one_through do
108
+ @expected.car.should == @car
109
+ end
110
+ end
111
+
112
+ it 'should persist the Resource' do
113
+ @car.save.should be_true
114
+ @car.model.get(*@car.key).send(@name).should == @expected
115
+ end
116
+
117
+ it 'should persist the associated Resource' do
118
+ @car.save.should be_true
119
+ @expected.should be_saved
120
+ @expected.model.get(*@expected.key).car.should == @car
121
+ end
122
+ end
123
+
124
+ describe 'when setting a Hash' do
125
+ before :all do
126
+ @car.send("#{@name}=", @model.new)
127
+
128
+ attributes = { :id => 10 }
129
+ @expected = @model.new(attributes)
130
+
131
+ @return = @car.send("#{@name}=", attributes)
132
+ end
133
+
134
+ it 'should return the expected Resource' do
135
+ @return.should == @expected
136
+ end
137
+
138
+ it 'should set the Resource' do
139
+ @car.send(@name).should equal(@return)
140
+ end
141
+
142
+ it 'should relate associated Resource' do
143
+ relationship = Car.relationships[@name]
144
+ many_to_one = relationship.kind_of?(DataMapper::Associations::ManyToOne::Relationship)
145
+ one_to_one_through = relationship.kind_of?(DataMapper::Associations::OneToOne::Relationship) && relationship.respond_to?(:through)
146
+
147
+ pending_if 'TODO', many_to_one || one_to_one_through do
148
+ @return.car.should == @car
149
+ end
150
+ end
151
+
152
+ it 'should persist the Resource' do
153
+ @car.save.should be_true
154
+ @car.model.get(*@car.key).send(@name).should == @return
155
+ end
156
+
157
+ it 'should persist the associated Resource' do
158
+ @car.save.should be_true
159
+ @return.should be_saved
160
+ @return.model.get(*@return.key).car.should == @car
161
+ end
162
+ end
163
+
164
+ describe 'when setting nil' do
165
+ before :all do
166
+ @car.send("#{@name}=", @model.new)
167
+
168
+ @return = @car.send("#{@name}=", nil)
169
+ end
170
+
171
+ it 'should return nil' do
172
+ @return.should be_nil
173
+ end
174
+
175
+ it 'should set nil' do
176
+ @car.send(@name).should be_nil
177
+ end
178
+
179
+ it 'should persist as nil' do
180
+ @car.save.should be_true
181
+ @car.model.get(*@car.key).send(@name).should be_nil
182
+ end
183
+ end
184
+
185
+ describe 'when changing the Resource' do
186
+ before :all do
187
+ @car.send("#{@name}=", @model.new)
188
+ @expected = @model.new
189
+
190
+ @return = @car.send("#{@name}=", @expected)
191
+ end
192
+
193
+ it 'should return the expected Resource' do
194
+ @return.should equal(@expected)
195
+ end
196
+
197
+ it 'should set the Resource' do
198
+ @car.send(@name).should equal(@expected)
199
+ end
200
+
201
+ it 'should relate associated Resource' do
202
+ relationship = Car.relationships[@name]
203
+ many_to_one = relationship.kind_of?(DataMapper::Associations::ManyToOne::Relationship)
204
+ one_to_one_through = relationship.kind_of?(DataMapper::Associations::OneToOne::Relationship) && relationship.respond_to?(:through)
205
+
206
+ pending_if 'should create back-reference', many_to_one || one_to_one_through do
207
+ @expected.car.should == @car
208
+ end
209
+ end
210
+
211
+ it 'should persist the Resource' do
212
+ @car.save.should be_true
213
+ @car.model.get(*@car.key).send(@name).should == @expected
214
+ end
215
+
216
+ it 'should persist the associated Resource' do
217
+ @car.save.should be_true
218
+ @expected.should be_saved
219
+ @expected.model.get(*@expected.key).car.should == @car
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ share_examples_for 'it creates a many accessor' do
226
+ describe 'accessor' do
227
+ describe 'when there is no child resource and the source is saved' do
228
+ before :all do
229
+ @car.save.should be_true
230
+ @return = @car.send(@name)
231
+ end
232
+
233
+ it 'should return a Collection' do
234
+ @return.should be_kind_of(DataMapper::Collection)
235
+ end
236
+
237
+ it 'should return an empty Collection' do
238
+ @return.should be_empty
239
+ end
240
+ end
241
+
242
+ describe 'when there is no child resource and the source is not saved' do
243
+ before :all do
244
+ @return = @car.send(@name)
245
+ end
246
+
247
+ it 'should return a Collection' do
248
+ @return.should be_kind_of(DataMapper::Collection)
249
+ end
250
+
251
+ it 'should return an empty Collection' do
252
+ @return.should be_empty
253
+ end
254
+ end
255
+
256
+ describe 'when there is a child resource' do
257
+ before :all do
258
+ @return = nil
259
+
260
+ @expected = @model.new
261
+ @car.send("#{@name}=", [ @expected ])
262
+
263
+ @return = @car.send(@name)
264
+ end
265
+
266
+ it 'should return a Collection' do
267
+ @return.should be_kind_of(DataMapper::Collection)
268
+ end
269
+
270
+ it 'should return expected Resources' do
271
+ @return.should == [ @expected ]
272
+ end
273
+ end
274
+
275
+ describe 'when the target model is scoped' do
276
+ before :all do
277
+ 2.times { @car.send(@name).new }
278
+ @car.save
279
+
280
+ @expected = @car.send(@name).first
281
+
282
+ # set the model scope to only return the first record
283
+ @model.default_scope.update(@model.key(@repository.name).zip(@expected.key).to_hash)
284
+
285
+ @return = @car.model.get(*@car.key).send(@name)
286
+ end
287
+
288
+ it 'should return a Collection' do
289
+ @return.should be_kind_of(DataMapper::Collection)
290
+ end
291
+
292
+ it 'should return expected Resources' do
293
+ @return.should == [ @expected ]
294
+ end
295
+ end
296
+ end
297
+ end
298
+
299
+ share_examples_for 'it creates a many mutator' do
300
+ describe 'mutator' do
301
+ describe 'when setting an Array of Resources' do
302
+ before :all do
303
+ @expected = [ @model.new ]
304
+
305
+ @return = @car.send("#{@name}=", @expected)
306
+ end
307
+
308
+ it 'should return the expected Collection' do
309
+ @return.should == @expected
310
+ end
311
+
312
+ it 'should set the Collection' do
313
+ @car.send(@name).should == @expected
314
+ @car.send(@name).zip(@expected) { |value, expected| value.should equal(expected) }
315
+ end
316
+
317
+ it 'should relate the associated Collection' do
318
+ pending_if 'TODO', Car.relationships[@name].kind_of?(DataMapper::Associations::ManyToMany::Relationship) do
319
+ @expected.each { |resource| resource.car.should == @car }
320
+ end
321
+ end
322
+
323
+ it 'should persist the Collection' do
324
+ @car.save.should be_true
325
+ @car.model.get(*@car.key).send(@name).should == @expected
326
+ end
327
+
328
+ it 'should persist the associated Resource' do
329
+ @car.save.should be_true
330
+ @expected.each { |resource| resource.should be_saved }
331
+ @expected.each { |resource| resource.model.get(*resource.key).car.should == @car }
332
+ end
333
+ end
334
+
335
+ describe 'when setting an Array of Hashes' do
336
+ before :all do
337
+ attributes = { :id => 11 }
338
+ @hashes = [ attributes ]
339
+ @expected = [ @model.new(attributes) ]
340
+
341
+ @return = @car.send("#{@name}=", @hashes)
342
+ end
343
+
344
+ it 'should return the expected Collection' do
345
+ @return.should == @expected
346
+ end
347
+
348
+ it 'should set the Collection' do
349
+ @car.send(@name).should == @return
350
+ end
351
+
352
+ it 'should relate the associated Collection' do
353
+ pending_if 'TODO', Car.relationships[@name].kind_of?(DataMapper::Associations::ManyToMany::Relationship) do
354
+ @return.each { |resource| resource.car.should == @car }
355
+ end
356
+ end
357
+
358
+ it 'should persist the Collection' do
359
+ @car.save.should be_true
360
+ @car.model.get(*@car.key).send(@name).should == @return
361
+ end
362
+
363
+ it 'should persist the associated Resource' do
364
+ @car.save.should be_true
365
+ @return.each { |resource| resource.should be_saved }
366
+ @return.each { |resource| resource.model.get(*resource.key).car.should == @car }
367
+ end
368
+ end
369
+
370
+ describe 'when setting an empty collection' do
371
+ before :all do
372
+ @car.send("#{@name}=", [ @model.new ])
373
+
374
+ @return = @car.send("#{@name}=", [])
375
+ end
376
+
377
+ it 'should return a Collection' do
378
+ @return.should be_kind_of(DataMapper::Collection)
379
+ end
380
+
381
+ it 'should set an empty Collection' do
382
+ @car.send(@name).should be_empty
383
+ end
384
+
385
+ it 'should persist as an empty Collection' do
386
+ @car.save.should be_true
387
+ @car.model.get(*@car.key).send(@name).should be_empty
388
+ end
389
+ end
390
+
391
+ describe 'when changing an associated collection' do
392
+ before :all do
393
+ @car.send("#{@name}=", [ @model.new ])
394
+
395
+ @expected = [ @model.new ]
396
+
397
+ @return = @car.send("#{@name}=", @expected)
398
+ end
399
+
400
+ it 'should return the expected Resource' do
401
+ @return.should == @expected
402
+ end
403
+
404
+ it 'should set the Resource' do
405
+ @car.send(@name).should == @expected
406
+ @car.send(@name).zip(@expected) { |value, expected| value.should equal(expected) }
407
+ end
408
+
409
+ it 'should relate associated Resource' do
410
+ pending_if 'TODO', Car.relationships[@name].kind_of?(DataMapper::Associations::ManyToMany::Relationship) do
411
+ @expected.each { |resource| resource.car.should == @car }
412
+ end
413
+ end
414
+
415
+ it 'should persist the Resource' do
416
+ @car.save.should be_true
417
+ @car.model.get(*@car.key).send(@name).should == @expected
418
+ end
419
+
420
+ it 'should persist the associated Resource' do
421
+ @car.save.should be_true
422
+ @expected.each { |resource| resource.should be_saved }
423
+ @expected.each { |resource| resource.model.get(*resource.key).car.should == @car }
424
+ end
425
+ end
426
+ end
427
+ end
428
+
429
+ describe DataMapper::Associations do
430
+ before :all do
431
+ class ::Car
432
+ include DataMapper::Resource
433
+
434
+ property :id, Serial
435
+ end
436
+
437
+ class ::Engine
438
+ include DataMapper::Resource
439
+
440
+ property :id, Serial
441
+ end
442
+
443
+ class ::Door
444
+ include DataMapper::Resource
445
+
446
+ property :id, Serial
447
+ end
448
+
449
+ class ::Window
450
+ include DataMapper::Resource
451
+
452
+ property :id, Serial
453
+ end
454
+ end
455
+
456
+ def n
457
+ 1.0/0
458
+ end
459
+
460
+ it { Engine.should respond_to(:belongs_to) }
461
+
462
+ describe '#belongs_to' do
463
+ before :all do
464
+ @model = Engine
465
+ @name = :engine
466
+
467
+ Car.belongs_to(@name, :nullable => true)
468
+ Engine.has(1, :car)
469
+ end
470
+
471
+ supported_by :all do
472
+ before :all do
473
+ @car = Car.new
474
+ end
475
+
476
+ it { @car.should respond_to(@name) }
477
+
478
+ it_should_behave_like 'it creates a one accessor'
479
+
480
+ it { @car.should respond_to("#{@name}=") }
481
+
482
+ it_should_behave_like 'it creates a one mutator'
483
+ end
484
+
485
+ # TODO: refactor these specs into above structure once they pass
486
+ describe 'pending query specs' do
487
+ before :all do
488
+ Car.has(1, :engine)
489
+ Engine.belongs_to(:car)
490
+ end
491
+
492
+ supported_by :all do
493
+ describe 'querying for a parent resource when only the foreign key is set' do
494
+ before :all do
495
+ # create a car that would be returned if the query is not
496
+ # scoped properly to retrieve @car
497
+ Car.create
498
+
499
+ @car = Car.create
500
+ engine = Engine.new(:car_id => @car.id)
501
+
502
+ @return = engine.car
503
+ end
504
+
505
+ it 'should return a Resource' do
506
+ @return.should be_kind_of(DataMapper::Resource)
507
+ end
508
+
509
+ it 'should return expected Resource' do
510
+ @return.should eql(@car)
511
+ end
512
+ end
513
+
514
+ describe 'querying for a parent resource' do
515
+ before :all do
516
+ @car = Car.create
517
+ @engine = Engine.create(:car => @car)
518
+ @resource = @engine.car(:id => @car.id)
519
+ end
520
+
521
+ it 'should return a Resource' do
522
+ @resource.should be_kind_of(DataMapper::Resource)
523
+ end
524
+
525
+ it 'should return expected Resource' do
526
+ @resource.should eql(@car)
527
+ end
528
+ end
529
+
530
+ describe 'querying for a parent resource that does not exist' do
531
+ before :all do
532
+ @car = Car.create
533
+ @engine = Engine.create(:car => @car)
534
+ @resource = @engine.car(:id.not => @car.id)
535
+ end
536
+
537
+ it 'should return nil' do
538
+ @resource.should be_nil
539
+ end
540
+ end
541
+
542
+ describe 'changing the parent resource' do
543
+ before :all do
544
+ @car = Car.create
545
+ @engine = Engine.new
546
+ @engine.car = @car
547
+ end
548
+
549
+ it 'should set the associated foreign key' do
550
+ @engine.car_id.should == @car.id
551
+ end
552
+
553
+ it 'should add the engine object to the car' do
554
+ pending 'Changing a belongs_to parent should add the object to the correct association' do
555
+ @car.engines.should be_include(@engine)
556
+ end
557
+ end
558
+ end
559
+
560
+ describe 'changing the parent foreign key' do
561
+ before :all do
562
+ @car = Car.create
563
+
564
+ @engine = Engine.new
565
+ @engine.car_id = @car.id
566
+ end
567
+
568
+ it 'should set the associated resource' do
569
+ @engine.car.should eql(@car)
570
+ end
571
+ end
572
+
573
+ describe 'changing an existing resource through the relation' do
574
+ before :all do
575
+ @car1 = Car.create
576
+ @car2 = Car.create
577
+ @engine = Engine.create(:car => @car1)
578
+ @engine.car = @car2
579
+ end
580
+
581
+ it 'should also change the foreign key' do
582
+ @engine.car_id.should == @car2.id
583
+ end
584
+
585
+ it 'should add the engine to the car' do
586
+ pending 'Changing a belongs_to parent should add the object to the correct association' do
587
+ @car2.engines.should be_include(@engine)
588
+ end
589
+ end
590
+ end
591
+
592
+ describe 'changing an existing resource through the relation' do
593
+ before :all do
594
+ @car1 = Car.create
595
+ @car2 = Car.create
596
+ @engine = Engine.create(:car => @car1)
597
+ @engine.car_id = @car2.id
598
+ end
599
+
600
+ it 'should also change the foreign key' do
601
+ pending 'a change to the foreign key should also change the related object' do
602
+ @engine.car.should eql(@car2)
603
+ end
604
+ end
605
+
606
+ it 'should add the engine to the car' do
607
+ pending 'a change to the foreign key should also change the related object' do
608
+ @car2.engines.should be_include(@engine)
609
+ end
610
+ end
611
+ end
612
+ end
613
+ end
614
+ end
615
+
616
+ it { Car.should respond_to(:has) }
617
+
618
+ describe '#has' do
619
+ describe '1' do
620
+ before :all do
621
+ @model = Engine
622
+ @name = :engine
623
+
624
+ Car.has(1, @name)
625
+ Engine.belongs_to(:car)
626
+ end
627
+
628
+ supported_by :all do
629
+ before :all do
630
+ @car = Car.new
631
+ end
632
+
633
+ it { @car.should respond_to(@name) }
634
+
635
+ it_should_behave_like 'it creates a one accessor'
636
+
637
+ it { @car.should respond_to("#{@name}=") }
638
+
639
+ it_should_behave_like 'it creates a one mutator'
640
+ end
641
+ end
642
+
643
+ describe '1 through' do
644
+ before :all do
645
+ @model = Engine
646
+ @name = :engine
647
+
648
+ Car.has(1, @name, :through => DataMapper::Resource)
649
+ Engine.has(1, :car, :through => DataMapper::Resource)
650
+ end
651
+
652
+ supported_by :all do
653
+ before :all do
654
+ @no_join = defined?(DataMapper::Adapters::InMemoryAdapter) && @adapter.kind_of?(DataMapper::Adapters::InMemoryAdapter) ||
655
+ defined?(DataMapper::Adapters::YamlAdapter) && @adapter.kind_of?(DataMapper::Adapters::YamlAdapter)
656
+ end
657
+
658
+ before :all do
659
+ @car = Car.new
660
+ end
661
+
662
+ before do
663
+ pending if @no_join
664
+ end
665
+
666
+ it { @car.should respond_to(@name) }
667
+
668
+ it_should_behave_like 'it creates a one accessor'
669
+
670
+ it { @car.should respond_to("#{@name}=") }
671
+
672
+ it_should_behave_like 'it creates a one mutator'
673
+ end
674
+ end
675
+
676
+ describe 'n..n' do
677
+ before :all do
678
+ @model = Door
679
+ @name = :doors
680
+
681
+ Car.has(1..4, @name)
682
+ Door.belongs_to(:car, :nullable => true)
683
+ end
684
+
685
+ supported_by :all do
686
+ before :all do
687
+ @car = Car.new
688
+ end
689
+
690
+ it { @car.should respond_to(@name) }
691
+
692
+ it_should_behave_like 'it creates a many accessor'
693
+
694
+ it { @car.should respond_to("#{@name}=") }
695
+
696
+ it_should_behave_like 'it creates a many mutator'
697
+ end
698
+ end
699
+
700
+ describe 'n..n through' do
701
+ before :all do
702
+ @model = Window
703
+ @name = :windows
704
+
705
+ Window.has(1, :car, :through => DataMapper::Resource)
706
+ Car.has(1..4, :windows, :through => DataMapper::Resource)
707
+ end
708
+
709
+ supported_by :all do
710
+ before :all do
711
+ @no_join = defined?(DataMapper::Adapters::InMemoryAdapter) && @adapter.kind_of?(DataMapper::Adapters::InMemoryAdapter) ||
712
+ defined?(DataMapper::Adapters::YamlAdapter) && @adapter.kind_of?(DataMapper::Adapters::YamlAdapter)
713
+ end
714
+
715
+ before :all do
716
+ @car = Car.new
717
+ end
718
+
719
+ before do
720
+ pending if @no_join
721
+ end
722
+
723
+ it { @car.should respond_to(@name) }
724
+
725
+ it_should_behave_like 'it creates a many accessor'
726
+
727
+ it { @car.should respond_to("#{@name}=") }
728
+
729
+ it_should_behave_like 'it creates a many mutator'
730
+ end
731
+ end
732
+
733
+ describe 'when the 3rd argument is a Model' do
734
+ before :all do
735
+ Car.has(1, :engine, Engine)
736
+ end
737
+
738
+ it 'should set the relationship target model' do
739
+ Car.relationships[:engine].target_model.should == Engine
740
+ end
741
+ end
742
+
743
+ describe 'when the 3rd argument is a String' do
744
+ before :all do
745
+ Car.has(1, :engine, 'Engine')
746
+ end
747
+
748
+ it 'should set the relationship target model' do
749
+ Car.relationships[:engine].target_model.should == Engine
750
+ end
751
+ end
752
+
753
+ it 'should raise an exception if the cardinality is not understood' do
754
+ lambda { Car.has(n..n, :doors) }.should raise_error(ArgumentError)
755
+ end
756
+
757
+ it 'should raise an exception if the minimum constraint is larger than the maximum' do
758
+ lambda { Car.has(2..1, :doors) }.should raise_error(ArgumentError)
759
+ end
760
+ end
761
+
762
+ describe 'property prefix inference' do
763
+ describe 'when a relationship has an inverse' do
764
+ before :all do
765
+ @engine_relationship = Car.has(1, :engine, :inverse => Engine.belongs_to(:sports_car, Car))
766
+ end
767
+
768
+ supported_by :all do
769
+ it 'should have a child key prefix the same as the inverse relationship' do
770
+ @engine_relationship.child_key.map { |property| property.name }.should == [ :sports_car_id ]
771
+ end
772
+ end
773
+ end
774
+
775
+ describe 'when a relationship does not have an inverse' do
776
+ before :all do
777
+ @engine_relationship = Car.has(1, :engine)
778
+ end
779
+
780
+ supported_by :all do
781
+ it 'should have a child key prefix inferred from the source model name' do
782
+ @engine_relationship.child_key.map { |property| property.name }.should == [ :car_id ]
783
+ end
784
+ end
785
+ end
786
+
787
+ describe 'when a relationship is inherited' do
788
+ describe 'has an inverse' do
789
+ before :all do
790
+ Car.property(:type, DataMapper::Types::Discriminator)
791
+
792
+ class ::ElectricCar < Car; end
793
+
794
+ Car.has(1, :engine, :inverse => Engine.belongs_to(:sports_car, Car))
795
+ end
796
+
797
+ supported_by :all do
798
+ before :all do
799
+ @engine_relationship = ElectricCar.relationships(@repository.name)[:engine]
800
+ end
801
+
802
+ it 'should have a source model equal to the descendant' do
803
+ @engine_relationship.source_model.should equal(ElectricCar)
804
+ end
805
+
806
+ it 'should have a child key prefix the same as the inverse relationship' do
807
+ @engine_relationship.child_key.map { |property| property.name }.should == [ :sports_car_id ]
808
+ end
809
+ end
810
+ end
811
+
812
+ describe 'does not have an inverse' do
813
+ before :all do
814
+ Car.property(:type, DataMapper::Types::Discriminator)
815
+
816
+ class ::ElectricCar < Car; end
817
+
818
+ Car.has(1, :engine)
819
+ end
820
+
821
+ supported_by :all do
822
+ before :all do
823
+ @engine_relationship = ElectricCar.relationships(@repository.name)[:engine]
824
+ end
825
+
826
+ it 'should have a source model equal to the descendant' do
827
+ @engine_relationship.source_model.should equal(ElectricCar)
828
+ end
829
+
830
+ it 'should have a child key prefix inferred from the source model name' do
831
+ @engine_relationship.child_key.map { |property| property.name }.should == [ :car_id ]
832
+ end
833
+ end
834
+ end
835
+ end
836
+
837
+ describe "when a subclass defines it's own relationship" do
838
+ describe 'has an inverse' do
839
+ before :all do
840
+ Car.property(:type, DataMapper::Types::Discriminator)
841
+
842
+ class ::ElectricCar < Car; end
843
+
844
+ ElectricCar.has(1, :engine, :inverse => Engine.belongs_to(:sports_car, Car))
845
+ end
846
+
847
+ supported_by :all do
848
+ before :all do
849
+ @engine_relationship = ElectricCar.relationships(@repository.name)[:engine]
850
+ end
851
+
852
+ it 'should have a source model equal to the descendant' do
853
+ @engine_relationship.source_model.should equal(ElectricCar)
854
+ end
855
+
856
+ it 'should have a child key prefix the same as the inverse relationship' do
857
+ @engine_relationship.child_key.map { |property| property.name }.should == [ :sports_car_id ]
858
+ end
859
+ end
860
+ end
861
+
862
+ describe 'does not have an inverse' do
863
+ before :all do
864
+ Car.property(:type, DataMapper::Types::Discriminator)
865
+
866
+ class ::ElectricCar < Car; end
867
+
868
+ ElectricCar.has(1, :engine)
869
+ end
870
+
871
+ supported_by :all do
872
+ before :all do
873
+ @engine_relationship = ElectricCar.relationships(@repository.name)[:engine]
874
+ end
875
+
876
+ it 'should have a source model equal to the descendant' do
877
+ @engine_relationship.source_model.should equal(ElectricCar)
878
+ end
879
+
880
+ it 'should have a child key prefix inferred from the source model name' do
881
+ @engine_relationship.child_key.map { |property| property.name }.should == [ :electric_car_id ]
882
+ end
883
+ end
884
+ end
885
+ end
886
+ end
887
+
888
+ describe 'child is also a parent' do
889
+ before :all do
890
+ class ::Employee
891
+ include DataMapper::Resource
892
+
893
+ property :id, Serial
894
+ property :name, String
895
+
896
+ belongs_to :company
897
+ end
898
+
899
+ class ::Company
900
+ include DataMapper::Resource
901
+
902
+ property :id, Serial
903
+ property :name, String
904
+
905
+ belongs_to :owner, Employee, :nullable => true
906
+ has n, :employees
907
+ end
908
+ end
909
+
910
+ supported_by :all do
911
+ before :all do
912
+ @company = Company.create(:name => 'ACME Inc.')
913
+ @employee = @company.employees.create(:name => 'Wil E. Coyote')
914
+ end
915
+
916
+ it 'should save the child as a parent' do
917
+ lambda {
918
+ @company.owner = @employee
919
+ @company.save.should be_true
920
+ }.should_not raise_error
921
+ end
922
+ end
923
+ end
924
+ end