datamapper-dm-core 0.9.11 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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