rpbertp13-dm-core 0.9.11.1

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 (131) hide show
  1. data/.autotest +26 -0
  2. data/.gitignore +18 -0
  3. data/CONTRIBUTING +51 -0
  4. data/FAQ +92 -0
  5. data/History.txt +52 -0
  6. data/MIT-LICENSE +22 -0
  7. data/Manifest.txt +130 -0
  8. data/QUICKLINKS +11 -0
  9. data/README.txt +143 -0
  10. data/Rakefile +32 -0
  11. data/SPECS +62 -0
  12. data/TODO +1 -0
  13. data/dm-core.gemspec +40 -0
  14. data/lib/dm-core.rb +217 -0
  15. data/lib/dm-core/adapters.rb +16 -0
  16. data/lib/dm-core/adapters/abstract_adapter.rb +209 -0
  17. data/lib/dm-core/adapters/data_objects_adapter.rb +716 -0
  18. data/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
  19. data/lib/dm-core/adapters/mysql_adapter.rb +138 -0
  20. data/lib/dm-core/adapters/postgres_adapter.rb +189 -0
  21. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  22. data/lib/dm-core/associations.rb +207 -0
  23. data/lib/dm-core/associations/many_to_many.rb +147 -0
  24. data/lib/dm-core/associations/many_to_one.rb +107 -0
  25. data/lib/dm-core/associations/one_to_many.rb +315 -0
  26. data/lib/dm-core/associations/one_to_one.rb +61 -0
  27. data/lib/dm-core/associations/relationship.rb +221 -0
  28. data/lib/dm-core/associations/relationship_chain.rb +81 -0
  29. data/lib/dm-core/auto_migrations.rb +105 -0
  30. data/lib/dm-core/collection.rb +670 -0
  31. data/lib/dm-core/dependency_queue.rb +32 -0
  32. data/lib/dm-core/hook.rb +11 -0
  33. data/lib/dm-core/identity_map.rb +42 -0
  34. data/lib/dm-core/is.rb +16 -0
  35. data/lib/dm-core/logger.rb +232 -0
  36. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  37. data/lib/dm-core/migrator.rb +29 -0
  38. data/lib/dm-core/model.rb +526 -0
  39. data/lib/dm-core/naming_conventions.rb +84 -0
  40. data/lib/dm-core/property.rb +676 -0
  41. data/lib/dm-core/property_set.rb +169 -0
  42. data/lib/dm-core/query.rb +676 -0
  43. data/lib/dm-core/repository.rb +167 -0
  44. data/lib/dm-core/resource.rb +671 -0
  45. data/lib/dm-core/scope.rb +58 -0
  46. data/lib/dm-core/support.rb +7 -0
  47. data/lib/dm-core/support/array.rb +13 -0
  48. data/lib/dm-core/support/assertions.rb +8 -0
  49. data/lib/dm-core/support/errors.rb +23 -0
  50. data/lib/dm-core/support/kernel.rb +11 -0
  51. data/lib/dm-core/support/symbol.rb +41 -0
  52. data/lib/dm-core/transaction.rb +252 -0
  53. data/lib/dm-core/type.rb +160 -0
  54. data/lib/dm-core/type_map.rb +80 -0
  55. data/lib/dm-core/types.rb +19 -0
  56. data/lib/dm-core/types/boolean.rb +7 -0
  57. data/lib/dm-core/types/discriminator.rb +34 -0
  58. data/lib/dm-core/types/object.rb +24 -0
  59. data/lib/dm-core/types/paranoid_boolean.rb +34 -0
  60. data/lib/dm-core/types/paranoid_datetime.rb +33 -0
  61. data/lib/dm-core/types/serial.rb +9 -0
  62. data/lib/dm-core/types/text.rb +10 -0
  63. data/lib/dm-core/version.rb +3 -0
  64. data/script/all +4 -0
  65. data/script/performance.rb +282 -0
  66. data/script/profile.rb +87 -0
  67. data/spec/integration/association_spec.rb +1382 -0
  68. data/spec/integration/association_through_spec.rb +203 -0
  69. data/spec/integration/associations/many_to_many_spec.rb +449 -0
  70. data/spec/integration/associations/many_to_one_spec.rb +163 -0
  71. data/spec/integration/associations/one_to_many_spec.rb +188 -0
  72. data/spec/integration/auto_migrations_spec.rb +413 -0
  73. data/spec/integration/collection_spec.rb +1073 -0
  74. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  75. data/spec/integration/dependency_queue_spec.rb +46 -0
  76. data/spec/integration/model_spec.rb +197 -0
  77. data/spec/integration/mysql_adapter_spec.rb +85 -0
  78. data/spec/integration/postgres_adapter_spec.rb +731 -0
  79. data/spec/integration/property_spec.rb +253 -0
  80. data/spec/integration/query_spec.rb +514 -0
  81. data/spec/integration/repository_spec.rb +61 -0
  82. data/spec/integration/resource_spec.rb +513 -0
  83. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  84. data/spec/integration/sti_spec.rb +273 -0
  85. data/spec/integration/strategic_eager_loading_spec.rb +156 -0
  86. data/spec/integration/transaction_spec.rb +60 -0
  87. data/spec/integration/type_spec.rb +275 -0
  88. data/spec/lib/logging_helper.rb +18 -0
  89. data/spec/lib/mock_adapter.rb +27 -0
  90. data/spec/lib/model_loader.rb +100 -0
  91. data/spec/lib/publicize_methods.rb +28 -0
  92. data/spec/models/content.rb +16 -0
  93. data/spec/models/vehicles.rb +34 -0
  94. data/spec/models/zoo.rb +48 -0
  95. data/spec/spec.opts +3 -0
  96. data/spec/spec_helper.rb +91 -0
  97. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  98. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  99. data/spec/unit/adapters/data_objects_adapter_spec.rb +632 -0
  100. data/spec/unit/adapters/in_memory_adapter_spec.rb +98 -0
  101. data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
  102. data/spec/unit/associations/many_to_many_spec.rb +32 -0
  103. data/spec/unit/associations/many_to_one_spec.rb +159 -0
  104. data/spec/unit/associations/one_to_many_spec.rb +393 -0
  105. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  106. data/spec/unit/associations/relationship_spec.rb +71 -0
  107. data/spec/unit/associations_spec.rb +242 -0
  108. data/spec/unit/auto_migrations_spec.rb +111 -0
  109. data/spec/unit/collection_spec.rb +182 -0
  110. data/spec/unit/data_mapper_spec.rb +35 -0
  111. data/spec/unit/identity_map_spec.rb +126 -0
  112. data/spec/unit/is_spec.rb +80 -0
  113. data/spec/unit/migrator_spec.rb +33 -0
  114. data/spec/unit/model_spec.rb +321 -0
  115. data/spec/unit/naming_conventions_spec.rb +36 -0
  116. data/spec/unit/property_set_spec.rb +90 -0
  117. data/spec/unit/property_spec.rb +753 -0
  118. data/spec/unit/query_spec.rb +571 -0
  119. data/spec/unit/repository_spec.rb +93 -0
  120. data/spec/unit/resource_spec.rb +649 -0
  121. data/spec/unit/scope_spec.rb +142 -0
  122. data/spec/unit/transaction_spec.rb +469 -0
  123. data/spec/unit/type_map_spec.rb +114 -0
  124. data/spec/unit/type_spec.rb +119 -0
  125. data/tasks/ci.rb +36 -0
  126. data/tasks/dm.rb +63 -0
  127. data/tasks/doc.rb +20 -0
  128. data/tasks/gemspec.rb +23 -0
  129. data/tasks/hoe.rb +46 -0
  130. data/tasks/install.rb +20 -0
  131. metadata +215 -0
data/script/profile.rb ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'dm-core')
4
+
5
+ require 'rubygems'
6
+
7
+ gem 'ruby-prof', '~>0.7.3'
8
+ require 'ruby-prof'
9
+
10
+ gem 'faker', '~>0.3.1'
11
+ require 'faker'
12
+
13
+ OUTPUT = DataMapper.root / 'profile_results.txt'
14
+ #OUTPUT = DataMapper.root / 'profile_results.html'
15
+
16
+ SOCKET_FILE = Pathname.glob(%w[
17
+ /opt/local/var/run/mysql5/mysqld.sock
18
+ /tmp/mysqld.sock
19
+ /tmp/mysql.sock
20
+ /var/mysql/mysql.sock
21
+ /var/run/mysqld/mysqld.sock
22
+ ]).find { |path| path.socket? }
23
+
24
+ DataMapper::Logger.new(DataMapper.root / 'log' / 'dm.log', :debug)
25
+ DataMapper.setup(:default, "mysql://root@localhost/data_mapper_1?socket=#{SOCKET_FILE}")
26
+
27
+ class Exhibit
28
+ include DataMapper::Resource
29
+
30
+ property :id, Serial
31
+ property :name, String
32
+ property :zoo_id, Integer
33
+ property :notes, Text, :lazy => true
34
+ property :created_on, Date
35
+ # property :updated_at, DateTime
36
+
37
+ auto_migrate!
38
+ create # create one row for testing
39
+ end
40
+
41
+ touch_attributes = lambda do |exhibits|
42
+ [*exhibits].each do |exhibit|
43
+ exhibit.id
44
+ exhibit.name
45
+ exhibit.created_on
46
+ exhibit.updated_at
47
+ end
48
+ end
49
+
50
+ # RubyProf, making profiling Ruby pretty since 1899!
51
+ def profile(&b)
52
+ result = RubyProf.profile &b
53
+ printer = RubyProf::FlatPrinter.new(result)
54
+ #printer = RubyProf::GraphHtmlPrinter.new(result)
55
+ printer.print(OUTPUT.open('w+'))
56
+ end
57
+
58
+ profile do
59
+ # 10_000.times { touch_attributes[Exhibit.get(1)] }
60
+ #
61
+ # repository(:default) do
62
+ # 10_000.times { touch_attributes[Exhibit.get(1)] }
63
+ # end
64
+ #
65
+ # 1000.times { touch_attributes[Exhibit.all(:limit => 100)] }
66
+ #
67
+ # repository(:default) do
68
+ # 1000.times { touch_attributes[Exhibit.all(:limit => 100)] }
69
+ # end
70
+ #
71
+ # 10.times { touch_attributes[Exhibit.all(:limit => 10_000)] }
72
+ #
73
+ # repository(:default) do
74
+ # 10.times { touch_attributes[Exhibit.all(:limit => 10_000)] }
75
+ # end
76
+
77
+ create_exhibit = {
78
+ :name => Faker::Company.name,
79
+ :zoo_id => rand(10).ceil,
80
+ :notes => Faker::Lorem.paragraphs.join($/),
81
+ :created_on => Date.today
82
+ }
83
+
84
+ 1000.times { Exhibit.create(create_exhibit) }
85
+ end
86
+
87
+ puts "Done!"
@@ -0,0 +1,1382 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ if HAS_SQLITE3
4
+ describe DataMapper::Associations do
5
+ before :all do
6
+ db1 = File.expand_path(File.join(File.dirname(__FILE__), "custom_db1_sqlite3.db"))
7
+ db2 = File.expand_path(File.join(File.dirname(__FILE__), "custom_db2_sqlite3.db"))
8
+ FileUtils.touch(db1)
9
+ FileUtils.touch(db2)
10
+ DataMapper.setup(:custom_db1, "sqlite3://#{db1}")
11
+ DataMapper.setup(:custom_db2, "sqlite3://#{db2}")
12
+ class ::CustomParent
13
+ include DataMapper::Resource
14
+ def self.default_repository_name
15
+ :custom_db1
16
+ end
17
+ property :id, Serial
18
+ property :name, String
19
+ repository(:custom_db2) do
20
+ has n, :custom_childs
21
+ end
22
+ end
23
+ class ::CustomChild
24
+ include DataMapper::Resource
25
+ def self.default_repository_name
26
+ :custom_db2
27
+ end
28
+ property :id, Serial
29
+ property :name, String
30
+ repository(:custom_db1) do
31
+ belongs_to :custom_parent
32
+ end
33
+ end
34
+
35
+ end
36
+ before :each do
37
+ [ CustomChild, CustomParent ].each { |m| m.auto_migrate! }
38
+
39
+ parent = CustomParent.create(:name => "mother")
40
+ child1 = parent.custom_childs.create(:name => "son")
41
+ child2 = parent.custom_childs.create(:name => "daughter")
42
+
43
+ @parent = CustomParent.first(:name => "mother")
44
+ @child1 = CustomChild.first(:name => "son")
45
+ @child2 = CustomChild.first(:name => "daughter")
46
+ end
47
+ it "should be able to handle has_many relationships to other repositories" do
48
+ @parent.custom_childs.size.should == 2
49
+ @parent.custom_childs.include?(@child1).should == true
50
+ @parent.custom_childs.include?(@child2).should == true
51
+ @parent.custom_childs.delete(@child1)
52
+ @parent.custom_childs.save
53
+ @parent.reload
54
+ @parent.custom_childs.size.should == 1
55
+ @parent.custom_childs.include?(@child2).should == true
56
+ end
57
+ it "should be able to handle belongs_to relationships to other repositories" do
58
+ @child1.custom_parent.should == @parent
59
+ @child2.custom_parent.should == @parent
60
+ @child1.custom_parent = nil
61
+ @child1.save
62
+ @child1.reload
63
+ @child1.custom_parent.should == nil
64
+ @parent.reload
65
+ @parent.custom_childs.size.should == 1
66
+ @parent.custom_childs.include?(@child2).should == true
67
+ end
68
+ end
69
+ end
70
+
71
+ if ADAPTER
72
+ repository(ADAPTER) do
73
+ class ::Machine
74
+ include DataMapper::Resource
75
+
76
+ def self.default_repository_name
77
+ ADAPTER
78
+ end
79
+
80
+ property :id, Serial
81
+ property :name, String
82
+
83
+ has n, :areas
84
+ has n, :fussy_areas, :class_name => 'Area', :rating.gte => 3, :type => 'particular'
85
+ end
86
+
87
+ class ::Area
88
+ include DataMapper::Resource
89
+
90
+ def self.default_repository_name
91
+ ADAPTER
92
+ end
93
+
94
+ property :id, Serial
95
+ property :name, String
96
+ property :rating, Integer
97
+ property :type, String
98
+
99
+ belongs_to :machine
100
+ end
101
+
102
+ class ::Pie
103
+ include DataMapper::Resource
104
+
105
+ def self.default_repository_name
106
+ ADAPTER
107
+ end
108
+
109
+ property :id, Serial
110
+ property :name, String
111
+
112
+ belongs_to :sky
113
+ end
114
+
115
+ class ::Sky
116
+ include DataMapper::Resource
117
+
118
+ def self.default_repository_name
119
+ ADAPTER
120
+ end
121
+
122
+ property :id, Serial
123
+ property :name, String
124
+
125
+ has 1, :pie
126
+ end
127
+
128
+ class ::Ultrahost
129
+ include DataMapper::Resource
130
+
131
+ def self.default_repository_name
132
+ ADAPTER
133
+ end
134
+
135
+ property :id, Serial
136
+ property :name, String
137
+
138
+ has n, :ultraslices, :order => [:id.desc]
139
+ end
140
+
141
+ class ::Ultraslice
142
+ include DataMapper::Resource
143
+
144
+ def self.default_repository_name
145
+ ADAPTER
146
+ end
147
+
148
+ property :id, Serial
149
+ property :name, String
150
+
151
+ belongs_to :ultrahost
152
+ end
153
+
154
+ class ::Node
155
+ include DataMapper::Resource
156
+
157
+ def self.default_repository_name
158
+ ADAPTER
159
+ end
160
+
161
+ property :id, Serial
162
+ property :name, String
163
+
164
+ has n, :children, :class_name => 'Node', :child_key => [ :parent_id ]
165
+ belongs_to :parent, :class_name => 'Node', :child_key => [ :parent_id ]
166
+ end
167
+
168
+ class ::MadeUpThing
169
+ include DataMapper::Resource
170
+
171
+ def self.default_repository_name
172
+ ADAPTER
173
+ end
174
+
175
+ property :id, Serial
176
+ property :name, String
177
+ belongs_to :area
178
+ belongs_to :machine
179
+ end
180
+
181
+ module ::Models
182
+ class Project
183
+ include DataMapper::Resource
184
+
185
+ def self.default_repository_name
186
+ ADAPTER
187
+ end
188
+
189
+ property :title, String, :length => 255, :key => true
190
+ property :summary, DataMapper::Types::Text
191
+
192
+ has n, :tasks
193
+ has 1, :goal
194
+ end
195
+
196
+ class Goal
197
+ include DataMapper::Resource
198
+
199
+ def self.default_repository_name
200
+ ADAPTER
201
+ end
202
+
203
+ property :title, String, :length => 255, :key => true
204
+ property :summary, DataMapper::Types::Text
205
+
206
+ belongs_to :project
207
+ end
208
+
209
+ class Task
210
+ include DataMapper::Resource
211
+
212
+ def self.default_repository_name
213
+ ADAPTER
214
+ end
215
+
216
+ property :title, String, :length => 255, :key => true
217
+ property :description, DataMapper::Types::Text
218
+
219
+ belongs_to :project
220
+ end
221
+ end
222
+
223
+ class ::Galaxy
224
+ include DataMapper::Resource
225
+
226
+ def self.default_repository_name
227
+ ADAPTER
228
+ end
229
+
230
+ property :name, String, :key => true, :length => 255
231
+ property :size, Float, :key => true, :precision => 15, :scale => 6
232
+ end
233
+
234
+ class ::Star
235
+ include DataMapper::Resource
236
+
237
+ def self.default_repository_name
238
+ ADAPTER
239
+ end
240
+
241
+ belongs_to :galaxy
242
+ end
243
+
244
+ end
245
+
246
+ describe DataMapper::Associations do
247
+ describe 'namespaced associations' do
248
+ before do
249
+ Models::Project.auto_migrate!(ADAPTER)
250
+ Models::Task.auto_migrate!(ADAPTER)
251
+ Models::Goal.auto_migrate!(ADAPTER)
252
+ end
253
+
254
+ it 'should allow namespaced classes in parent and child for many <=> one' do
255
+ m = Models::Project.new(:title => 'p1', :summary => 'sum1')
256
+ m.tasks << Models::Task.new(:title => 't1', :description => 'desc 1')
257
+ m.save
258
+
259
+ t = Models::Task.first(:title => 't1')
260
+
261
+ t.project.should_not be_nil
262
+ t.project.title.should == 'p1'
263
+ t.project.tasks.size.should == 1
264
+
265
+ p = Models::Project.first(:title => 'p1')
266
+
267
+ p.tasks.size.should == 1
268
+ p.tasks[0].title.should == 't1'
269
+ end
270
+
271
+ it 'should allow namespaced classes in parent and child for one <=> one' do
272
+ g = Models::Goal.new(:title => "g2", :summary => "desc 2")
273
+ p = Models::Project.create(:title => "p2", :summary => "sum 2", :goal => g)
274
+
275
+ pp = Models::Project.first(:title => 'p2')
276
+ pp.goal.title.should == "g2"
277
+
278
+ g = Models::Goal.first(:title => "g2")
279
+
280
+ g.project.should_not be_nil
281
+ g.project.title.should == 'p2'
282
+
283
+ g.project.goal.should_not be_nil
284
+ end
285
+ end
286
+
287
+ describe 'many to one associations' do
288
+ before do
289
+ Machine.auto_migrate!(ADAPTER)
290
+ Area.auto_migrate!(ADAPTER)
291
+ MadeUpThing.auto_migrate!(ADAPTER)
292
+
293
+ machine1 = Machine.create(:name => 'machine1')
294
+ machine2 = Machine.create(:name => 'machine2')
295
+ area1 = Area.create(:name => 'area1', :machine => machine1)
296
+ area2 = Area.create(:name => 'area2')
297
+ end
298
+
299
+ it '#belongs_to' do
300
+ area = Area.new
301
+ area.should respond_to(:machine)
302
+ area.should respond_to(:machine=)
303
+ end
304
+
305
+ it 'should create the foreign key property immediately' do
306
+ class ::Duck
307
+ include DataMapper::Resource
308
+ property :id, Serial
309
+ belongs_to :sky
310
+ end
311
+ Duck.properties.slice(:sky_id).compact.should_not be_empty
312
+ duck = Duck.new
313
+ duck.should respond_to(:sky_id)
314
+ duck.should respond_to(:sky_id=)
315
+ end
316
+
317
+ it 'should load without the parent'
318
+
319
+ it 'should allow substituting the parent' do
320
+ area1 = Area.first(:name => 'area1')
321
+ machine2 = Machine.first(:name => 'machine2')
322
+
323
+ area1.machine = machine2
324
+ area1.save
325
+ Area.first(:name => 'area1').machine.should == machine2
326
+ end
327
+
328
+ it 'should save both the object and parent if both are new' do
329
+ area1 = Area.new(:name => 'area1')
330
+ area1.machine = Machine.new(:name => 'machine1')
331
+ area1.save
332
+ area1.machine_id.should == area1.machine.id
333
+ end
334
+
335
+ it '#belongs_to with namespaced models' do
336
+ repository(ADAPTER) do
337
+ module ::FlightlessBirds
338
+ class Ostrich
339
+ include DataMapper::Resource
340
+ property :id, Serial
341
+ property :name, String
342
+ belongs_to :sky # there's something sad about this :'(
343
+ end
344
+ end
345
+
346
+ FlightlessBirds::Ostrich.properties(ADAPTER).slice(:sky_id).compact.should_not be_empty
347
+ end
348
+ end
349
+
350
+ it 'should load the associated instance' do
351
+ machine1 = Machine.first(:name => 'machine1')
352
+ Area.first(:name => 'area1').machine.should == machine1
353
+ end
354
+
355
+ it 'should save the association key in the child' do
356
+ machine2 = Machine.first(:name => 'machine2')
357
+
358
+ Area.create(:name => 'area3', :machine => machine2)
359
+ Area.first(:name => 'area3').machine.should == machine2
360
+ end
361
+
362
+ it 'should set the association key immediately' do
363
+ machine = Machine.first(:name => 'machine1')
364
+ Area.new(:machine => machine).machine_id.should == machine.id
365
+ end
366
+
367
+ it "should be able to set an association obtained from another association" do
368
+ machine1 = Machine.first(:name => 'machine1')
369
+ area1 = Area.first(:name => 'area1')
370
+ area1.machine = machine1
371
+
372
+ m = MadeUpThing.create(:machine => area1.machine, :name => "Weird")
373
+
374
+ m.machine_id.should == machine1.id
375
+ end
376
+
377
+ it 'should save the parent upon saving of child' do
378
+ e = Machine.new(:name => 'machine10')
379
+ y = Area.create(:name => 'area10', :machine => e)
380
+
381
+ y.machine.name.should == 'machine10'
382
+ Machine.first(:name => 'machine10').should_not be_nil
383
+ end
384
+
385
+ it 'should set and retrieve associations on not yet saved objects' do
386
+ e = Machine.create(:name => 'machine10')
387
+ y = e.areas.build(:name => 'area10')
388
+
389
+ y.machine.name.should == 'machine10'
390
+ end
391
+
392
+ it 'should convert NULL parent ids into nils' do
393
+ Area.first(:name => 'area2').machine.should be_nil
394
+ end
395
+
396
+ it 'should save nil parents as NULL ids' do
397
+ y1 = Area.create(:id => 20, :name => 'area20')
398
+ y2 = Area.create(:id => 30, :name => 'area30', :machine => nil)
399
+
400
+ y1.id.should == 20
401
+ y1.machine.should be_nil
402
+ y2.id.should == 30
403
+ y2.machine.should be_nil
404
+ end
405
+
406
+ it 'should respect length on foreign keys' do
407
+ property = Star.relationships[:galaxy].child_key[:galaxy_name]
408
+ property.length.should == 255
409
+ end
410
+
411
+ it 'should respect precision and scale on foreign keys' do
412
+ property = Star.relationships[:galaxy].child_key[:galaxy_size]
413
+ property.precision.should == 15
414
+ property.scale.should == 6
415
+ end
416
+
417
+ it 'should be reloaded when calling Resource#reload' do
418
+ e = Machine.new(:name => 'machine40')
419
+ y = Area.create(:name => 'area40', :machine => e)
420
+
421
+ y.send(:machine_association).should_receive(:reload).once
422
+
423
+ lambda { y.reload }.should_not raise_error
424
+ end
425
+
426
+ it "should have machine when created using machine_id" do
427
+ m = Machine.create(:name => 'machineX')
428
+ a = Area.new(:machine_id => m.id)
429
+ a.machine.should == m
430
+ end
431
+
432
+ it "should not have a machine when orphaned" do
433
+ a = Area.new(:machine_id => 42)
434
+ a.machine.should be_nil
435
+ end
436
+ end
437
+
438
+ describe 'one to one associations' do
439
+ before do
440
+ Sky.auto_migrate!(ADAPTER)
441
+ Pie.auto_migrate!(ADAPTER)
442
+
443
+ pie1 = Pie.create(:name => 'pie1')
444
+ pie2 = Pie.create(:name => 'pie2')
445
+ sky1 = Sky.create(:name => 'sky1', :pie => pie1)
446
+ end
447
+
448
+ it '#has 1' do
449
+ s = Sky.new
450
+ s.should respond_to(:pie)
451
+ s.should respond_to(:pie=)
452
+ end
453
+
454
+ it 'should allow substituting the child' do
455
+ sky1 = Sky.first(:name => 'sky1')
456
+ pie1 = Pie.first(:name => 'pie1')
457
+ pie2 = Pie.first(:name => 'pie2')
458
+
459
+ sky1.pie.should == pie1
460
+ pie2.sky.should be_nil
461
+
462
+ sky1.pie = pie2
463
+ sky1.save
464
+
465
+ pie2.sky.should == sky1
466
+ pie1.reload.sky.should be_nil
467
+ end
468
+
469
+ it 'should load the associated instance' do
470
+ sky1 = Sky.first(:name => 'sky1')
471
+ pie1 = Pie.first(:name => 'pie1')
472
+
473
+ sky1.pie.should == pie1
474
+ end
475
+
476
+ it 'should save the association key in the child' do
477
+ pie2 = Pie.first(:name => 'pie2')
478
+
479
+ sky2 = Sky.create(:id => 2, :name => 'sky2', :pie => pie2)
480
+ pie2.sky.should == sky2
481
+ end
482
+
483
+ it 'should save the children upon saving of parent' do
484
+ p = Pie.new(:id => 10, :name => 'pie10')
485
+ s = Sky.create(:id => 10, :name => 'sky10', :pie => p)
486
+
487
+ p.sky.should == s
488
+
489
+ Pie.first(:name => 'pie10').should_not be_nil
490
+ end
491
+
492
+ it 'should save nil parents as NULL ids' do
493
+ p1 = Pie.create(:id => 20, :name => 'pie20')
494
+ p2 = Pie.create(:id => 30, :name => 'pie30', :sky => nil)
495
+
496
+ p1.id.should == 20
497
+ p1.sky.should be_nil
498
+ p2.id.should == 30
499
+ p2.sky.should be_nil
500
+ end
501
+
502
+ it 'should be reloaded when calling Resource#reload' do
503
+ pie = Pie.first(:name => 'pie1')
504
+ pie.send(:sky_association).should_receive(:reload).once
505
+ lambda { pie.reload }.should_not raise_error
506
+ end
507
+ end
508
+
509
+ describe 'one to many associations' do
510
+ before do
511
+ Ultrahost.auto_migrate!(ADAPTER)
512
+ Ultraslice.auto_migrate!(ADAPTER)
513
+ Machine.auto_migrate!(ADAPTER)
514
+ Area.auto_migrate!(ADAPTER)
515
+
516
+ ultrahost1 = Ultrahost.create(:name => 'ultrahost1')
517
+ ultrahost2 = Ultrahost.create(:name => 'ultrahost2')
518
+ ultraslice1 = Ultraslice.create(:name => 'ultraslice1', :ultrahost => ultrahost1)
519
+ ultraslice2 = Ultraslice.create(:name => 'ultraslice2', :ultrahost => ultrahost1)
520
+ ultraslice3 = Ultraslice.create(:name => 'ultraslice3')
521
+ end
522
+
523
+ it '#has n' do
524
+ h = Ultrahost.new
525
+ h.should respond_to(:ultraslices)
526
+ end
527
+
528
+ it 'should allow removal of a child through a loaded association' do
529
+ ultrahost1 = Ultrahost.first(:name => 'ultrahost1')
530
+ ultraslice2 = ultrahost1.ultraslices.first
531
+
532
+ ultrahost1.ultraslices.size.should == 2
533
+ ultrahost1.ultraslices.delete(ultraslice2)
534
+ ultrahost1.ultraslices.size.should == 1
535
+
536
+ ultraslice2 = Ultraslice.first(:name => 'ultraslice2')
537
+ ultraslice2.ultrahost.should_not be_nil
538
+
539
+ ultrahost1.save
540
+
541
+ ultraslice2.reload.ultrahost.should be_nil
542
+ end
543
+
544
+ it 'should use the IdentityMap correctly' do
545
+ repository(ADAPTER) do
546
+ ultrahost1 = Ultrahost.first(:name => 'ultrahost1')
547
+
548
+ ultraslice = ultrahost1.ultraslices.first
549
+ ultraslice2 = ultrahost1.ultraslices(:order => [:id]).last # should be the same as 1
550
+ ultraslice3 = Ultraslice.get(2) # should be the same as 1
551
+
552
+ ultraslice.object_id.should == ultraslice2.object_id
553
+ ultraslice.object_id.should == ultraslice3.object_id
554
+ end
555
+ end
556
+
557
+ it '#<< should add exactly the parameters' do
558
+ machine = Machine.new(:name => 'my machine')
559
+ 4.times do |i|
560
+ machine.areas << Area.new(:name => "area nr #{i}")
561
+ end
562
+ machine.save
563
+ machine.areas.size.should == 4
564
+ 4.times do |i|
565
+ machine.areas.any? do |area|
566
+ area.name == "area nr #{i}"
567
+ end.should == true
568
+ end
569
+ machine = Machine.get!(machine.id)
570
+ machine.areas.size.should == 4
571
+ 4.times do |i|
572
+ machine.areas.any? do |area|
573
+ area.name == "area nr #{i}"
574
+ end.should == true
575
+ end
576
+ end
577
+
578
+ it "#<< should add the correct number of elements if they are created" do
579
+ machine = Machine.create(:name => 'my machine')
580
+ 4.times do |i|
581
+ machine.areas << Area.create(:name => "area nr #{i}", :machine => machine)
582
+ end
583
+ machine.areas.size.should == 4
584
+ end
585
+
586
+ it "#build should add exactly one instance of the built record" do
587
+ machine = Machine.create(:name => 'my machine')
588
+
589
+ original_size = machine.areas.size
590
+ machine.areas.build(:name => "an area", :machine => machine)
591
+
592
+ machine.areas.size.should == original_size + 1
593
+ end
594
+
595
+ it '#<< should add default values for relationships that have conditions' do
596
+ # it should add default values
597
+ machine = Machine.new(:name => 'my machine')
598
+ machine.fussy_areas << Area.new(:name => 'area 1', :rating => 4 )
599
+ machine.save
600
+ Area.first(:name => 'area 1').type.should == 'particular'
601
+ # it should not add default values if the condition's property already has a value
602
+ machine.fussy_areas << Area.new(:name => 'area 2', :rating => 4, :type => 'not particular')
603
+ machine.save
604
+ Area.first(:name => 'area 2').type.should == 'not particular'
605
+ # it should ignore non :eql conditions
606
+ machine.fussy_areas << Area.new(:name => 'area 3')
607
+ machine.save
608
+ Area.first(:name => 'area 3').rating.should == nil
609
+ end
610
+
611
+ it 'should load the associated instances, in the correct order' do
612
+ ultrahost1 = Ultrahost.first(:name => 'ultrahost1')
613
+
614
+ ultrahost1.ultraslices.should_not be_nil
615
+ ultrahost1.ultraslices.size.should == 2
616
+ ultrahost1.ultraslices.first.name.should == 'ultraslice2' # ordered by [:id.desc]
617
+ ultrahost1.ultraslices.last.name.should == 'ultraslice1'
618
+
619
+ ultraslice3 = Ultraslice.first(:name => 'ultraslice3')
620
+
621
+ ultraslice3.ultrahost.should be_nil
622
+ end
623
+
624
+ it 'should add and save the associated instance' do
625
+ ultrahost1 = Ultrahost.first(:name => 'ultrahost1')
626
+ ultrahost1.ultraslices << Ultraslice.new(:id => 4, :name => 'ultraslice4')
627
+ ultrahost1.save
628
+
629
+ Ultraslice.first(:name => 'ultraslice4').ultrahost.should == ultrahost1
630
+ end
631
+
632
+ it 'should not save the associated instance if the parent is not saved' do
633
+ h = Ultrahost.new(:id => 10, :name => 'ultrahost10')
634
+ h.ultraslices << Ultraslice.new(:id => 10, :name => 'ultraslice10')
635
+
636
+ Ultraslice.first(:name => 'ultraslice10').should be_nil
637
+ end
638
+
639
+ it 'should save the associated instance upon saving of parent' do
640
+ h = Ultrahost.new(:id => 10, :name => 'ultrahost10')
641
+ h.ultraslices << Ultraslice.new(:id => 10, :name => 'ultraslice10')
642
+ h.save
643
+
644
+ s = Ultraslice.first(:name => 'ultraslice10')
645
+
646
+ s.should_not be_nil
647
+ s.ultrahost.should == h
648
+ end
649
+
650
+ it 'should save the associated instances upon saving of parent when mass-assigned' do
651
+ h = Ultrahost.create(:id => 10, :name => 'ultrahost10', :ultraslices => [ Ultraslice.new(:id => 10, :name => 'ultraslice10') ])
652
+
653
+ s = Ultraslice.first(:name => 'ultraslice10')
654
+
655
+ s.should_not be_nil
656
+ s.ultrahost.should == h
657
+ end
658
+
659
+ it 'should have finder-functionality' do
660
+ h = Ultrahost.first(:name => 'ultrahost1')
661
+
662
+ h.ultraslices.should have(2).entries
663
+
664
+ s = h.ultraslices.all(:name => 'ultraslice2')
665
+
666
+ s.should have(1).entries
667
+ s.first.id.should == 2
668
+
669
+ h.ultraslices.first(:name => 'ultraslice2').should == s.first
670
+ end
671
+
672
+ it 'should be reloaded when calling Resource#reload' do
673
+ ultrahost = Ultrahost.first(:name => 'ultrahost1')
674
+ ultrahost.send(:ultraslices_association).should_receive(:reload).once
675
+ lambda { ultrahost.reload }.should_not raise_error
676
+ end
677
+ end
678
+
679
+ describe 'many-to-one and one-to-many associations combined' do
680
+ before do
681
+ Node.auto_migrate!(ADAPTER)
682
+
683
+ Node.create(:name => 'r1')
684
+ Node.create(:name => 'r2')
685
+ Node.create(:name => 'r1c1', :parent_id => 1)
686
+ Node.create(:name => 'r1c2', :parent_id => 1)
687
+ Node.create(:name => 'r1c3', :parent_id => 1)
688
+ Node.create(:name => 'r1c1c1', :parent_id => 3)
689
+ end
690
+
691
+ it 'should properly set #parent' do
692
+ r1 = Node.get 1
693
+ r1.parent.should be_nil
694
+
695
+ n3 = Node.get 3
696
+ n3.parent.should == r1
697
+
698
+ n6 = Node.get 6
699
+ n6.parent.should == n3
700
+ end
701
+
702
+ it 'should properly set #children' do
703
+ r1 = Node.get(1)
704
+ off = r1.children
705
+ off.size.should == 3
706
+ off.include?(Node.get(3)).should be_true
707
+ off.include?(Node.get(4)).should be_true
708
+ off.include?(Node.get(5)).should be_true
709
+ end
710
+
711
+ it 'should allow to create root nodes' do
712
+ r = Node.create(:name => 'newroot')
713
+ r.parent.should be_nil
714
+ r.children.size.should == 0
715
+ end
716
+
717
+ it 'should properly delete nodes' do
718
+ r1 = Node.get 1
719
+
720
+ r1.children.size.should == 3
721
+ r1.children.delete(Node.get(4))
722
+ r1.save
723
+ Node.get(4).parent.should be_nil
724
+ r1.children.size.should == 2
725
+ end
726
+ end
727
+
728
+ describe 'through-associations' do
729
+ before :all do
730
+ repository(ADAPTER) do
731
+ module ::Sweets
732
+ class Shop
733
+ include DataMapper::Resource
734
+ def self.default_repository_name
735
+ ADAPTER
736
+ end
737
+ property :id, Serial
738
+ property :name, String
739
+ has n, :cakes # has n
740
+ has n, :recipes, :through => :cakes # has n => has 1
741
+ has n, :ingredients, :through => :cakes # has n => has 1 => has n
742
+ has n, :creators, :through => :cakes # has n => has 1 => has 1
743
+ has n, :ultraslices, :through => :cakes # has n => has n
744
+ has n, :bites, :through => :cakes # has n => has n => has n
745
+ has n, :shapes, :through => :cakes # has n => has n => has 1
746
+ has n, :customers, :through => :cakes # has n => belongs_to (pending)
747
+ has 1, :shop_owner # has 1
748
+ has 1, :wife, :through => :shop_owner # has 1 => has 1
749
+ has 1, :ring, :through => :shop_owner # has 1 => has 1 => has 1
750
+ has n, :coats, :through => :shop_owner # has 1 => has 1 => has n
751
+ has n, :children, :through => :shop_owner # has 1 => has n
752
+ has n, :toys, :through => :shop_owner # has 1 => has n => has n
753
+ has n, :boogers, :through => :shop_owner # has 1 => has n => has 1
754
+ end
755
+
756
+ class ShopOwner
757
+ include DataMapper::Resource
758
+ def self.default_repository_name
759
+ ADAPTER
760
+ end
761
+ property :id, Serial
762
+ property :name, String
763
+ belongs_to :shop
764
+ has 1, :wife
765
+ has n, :children
766
+ has n, :toys, :through => :children
767
+ has n, :boogers, :through => :children
768
+ has n, :coats, :through => :wife
769
+ has 1, :ring, :through => :wife
770
+ has n, :schools, :through => :children
771
+ end
772
+
773
+ class Wife
774
+ include DataMapper::Resource
775
+ def self.default_repository_name
776
+ ADAPTER
777
+ end
778
+ property :id, Serial
779
+ property :name, String
780
+ belongs_to :shop_owner
781
+ has 1, :ring
782
+ has n, :coats
783
+ end
784
+
785
+ class Coat
786
+ include DataMapper::Resource
787
+ def self.default_repository_name
788
+ ADAPTER
789
+ end
790
+ property :id, Serial
791
+ property :name, String
792
+ belongs_to :wife
793
+ end
794
+
795
+ class Ring
796
+ include DataMapper::Resource
797
+ def self.default_repository_name
798
+ ADAPTER
799
+ end
800
+ property :id, Serial
801
+ property :name, String
802
+ belongs_to :wife
803
+ end
804
+
805
+ class Child
806
+ include DataMapper::Resource
807
+ def self.default_repository_name
808
+ ADAPTER
809
+ end
810
+ property :id, Serial
811
+ property :name, String
812
+ belongs_to :shop_owner
813
+ has n, :toys
814
+ has 1, :booger
815
+ end
816
+
817
+ class Booger
818
+ include DataMapper::Resource
819
+ def self.default_repository_name
820
+ ADAPTER
821
+ end
822
+ property :id, Serial
823
+ property :name, String
824
+ belongs_to :child
825
+ end
826
+
827
+ class Toy
828
+ include DataMapper::Resource
829
+ def self.default_repository_name
830
+ ADAPTER
831
+ end
832
+ property :id, Serial
833
+ property :name, String
834
+ belongs_to :child
835
+ end
836
+
837
+ class Cake
838
+ include DataMapper::Resource
839
+ def self.default_repository_name
840
+ ADAPTER
841
+ end
842
+ property :id, Serial
843
+ property :name, String
844
+ belongs_to :shop
845
+ belongs_to :customer
846
+ has n, :ultraslices
847
+ has n, :bites, :through => :ultraslices
848
+ has 1, :recipe
849
+ has n, :ingredients, :through => :recipe
850
+ has 1, :creator, :through => :recipe
851
+ has n, :shapes, :through => :ultraslices
852
+ end
853
+
854
+ class Recipe
855
+ include DataMapper::Resource
856
+ def self.default_repository_name
857
+ ADAPTER
858
+ end
859
+ property :id, Serial
860
+ property :name, String
861
+ belongs_to :cake
862
+ has n, :ingredients
863
+ has 1, :creator
864
+ end
865
+
866
+ class Customer
867
+ include DataMapper::Resource
868
+ def self.default_repository_name
869
+ ADAPTER
870
+ end
871
+ property :id, Serial
872
+ property :name, String
873
+ has n, :cakes
874
+ end
875
+
876
+ class Creator
877
+ include DataMapper::Resource
878
+ def self.default_repository_name
879
+ ADAPTER
880
+ end
881
+ property :id, Serial
882
+ property :name, String
883
+ belongs_to :recipe
884
+ end
885
+
886
+ class Ingredient
887
+ include DataMapper::Resource
888
+ def self.default_repository_name
889
+ ADAPTER
890
+ end
891
+ property :id, Serial
892
+ property :name, String
893
+ belongs_to :recipe
894
+ end
895
+
896
+ class Ultraslice
897
+ include DataMapper::Resource
898
+ def self.default_repository_name
899
+ ADAPTER
900
+ end
901
+ property :id, Serial
902
+ property :size, Integer
903
+ belongs_to :cake
904
+ has n, :bites
905
+ has 1, :shape
906
+ end
907
+
908
+ class Shape
909
+ include DataMapper::Resource
910
+ def self.default_repository_name
911
+ ADAPTER
912
+ end
913
+ property :id, Serial
914
+ property :name, String
915
+ belongs_to :ultraslice
916
+ end
917
+
918
+ class Bite
919
+ include DataMapper::Resource
920
+ def self.default_repository_name
921
+ ADAPTER
922
+ end
923
+ property :id, Serial
924
+ property :name, String
925
+ belongs_to :ultraslice
926
+ end
927
+
928
+ DataMapper::Resource.descendants.each do |descendant|
929
+ descendant.auto_migrate!(ADAPTER) if descendant.name =~ /^Sweets::/
930
+ end
931
+
932
+ betsys = Shop.new(:name => "Betsy's")
933
+ betsys.save
934
+
935
+ #
936
+ # has n
937
+ #
938
+
939
+ german_chocolate = Cake.new(:name => 'German Chocolate')
940
+ betsys.cakes << german_chocolate
941
+ german_chocolate.save
942
+ short_cake = Cake.new(:name => 'Short Cake')
943
+ betsys.cakes << short_cake
944
+ short_cake.save
945
+
946
+ # has n => belongs_to
947
+
948
+ old_customer = Customer.new(:name => 'John Johnsen')
949
+ old_customer.cakes << german_chocolate
950
+ old_customer.cakes << short_cake
951
+ german_chocolate.save
952
+ short_cake.save
953
+ old_customer.save
954
+
955
+ # has n => has 1
956
+
957
+ schwarzwald = Recipe.new(:name => 'Schwarzwald Cake')
958
+ schwarzwald.save
959
+ german_chocolate.recipe = schwarzwald
960
+ german_chocolate.save
961
+ shortys_special = Recipe.new(:name => "Shorty's Special")
962
+ shortys_special.save
963
+ short_cake.recipe = shortys_special
964
+ short_cake.save
965
+
966
+ # has n => has 1 => has 1
967
+
968
+ runar = Creator.new(:name => 'Runar')
969
+ schwarzwald.creator = runar
970
+ runar.save
971
+ berit = Creator.new(:name => 'Berit')
972
+ shortys_special.creator = berit
973
+ berit.save
974
+
975
+ # has n => has 1 => has n
976
+
977
+ 4.times do |i| schwarzwald.ingredients << Ingredient.new(:name => "Secret ingredient nr #{i}") end
978
+ 6.times do |i| shortys_special.ingredients << Ingredient.new(:name => "Well known ingredient nr #{i}") end
979
+
980
+ # has n => has n
981
+
982
+ 10.times do |i| german_chocolate.ultraslices << Ultraslice.new(:size => i) end
983
+ 5.times do |i| short_cake.ultraslices << Ultraslice.new(:size => i) end
984
+ german_chocolate.ultraslices.size.should == 10
985
+ # has n => has n => has 1
986
+
987
+ german_chocolate.ultraslices.each do |ultraslice|
988
+ shape = Shape.new(:name => 'square')
989
+ ultraslice.shape = shape
990
+ shape.save
991
+ end
992
+ short_cake.ultraslices.each do |ultraslice|
993
+ shape = Shape.new(:name => 'round')
994
+ ultraslice.shape = shape
995
+ shape.save
996
+ end
997
+
998
+ # has n => has n => has n
999
+ german_chocolate.ultraslices.each do |ultraslice|
1000
+ 6.times do |i|
1001
+ ultraslice.bites << Bite.new(:name => "Big bite nr #{i}")
1002
+ end
1003
+ end
1004
+ short_cake.ultraslices.each do |ultraslice|
1005
+ 3.times do |i|
1006
+ ultraslice.bites << Bite.new(:name => "Small bite nr #{i}")
1007
+ end
1008
+ end
1009
+
1010
+ #
1011
+ # has 1
1012
+ #
1013
+
1014
+ betsy = ShopOwner.new(:name => 'Betsy')
1015
+ betsys.shop_owner = betsy
1016
+ betsys.save
1017
+
1018
+ # has 1 => has 1
1019
+
1020
+ barry = Wife.new(:name => 'Barry')
1021
+ betsy.wife = barry
1022
+ barry.save
1023
+
1024
+ # has 1 => has 1 => has 1
1025
+
1026
+ golden = Ring.new(:name => 'golden')
1027
+ barry.ring = golden
1028
+ golden.save
1029
+
1030
+ # has 1 => has 1 => has n
1031
+
1032
+ 3.times do |i|
1033
+ barry.coats << Coat.new(:name => "Fancy coat nr #{i}")
1034
+ end
1035
+ barry.save
1036
+
1037
+ # has 1 => has n
1038
+
1039
+ 5.times do |i|
1040
+ betsy.children << Child.new(:name => "Snotling nr #{i}")
1041
+ end
1042
+ betsy.save
1043
+
1044
+ # has 1 => has n => has n
1045
+
1046
+ betsy.children.each do |child|
1047
+ 4.times do |i|
1048
+ child.toys << Toy.new(:name => "Cheap toy nr #{i}")
1049
+ end
1050
+ child.save
1051
+ end
1052
+
1053
+ # has 1 => has n => has 1
1054
+
1055
+ betsy.children.each do |child|
1056
+ booger = Booger.new(:name => 'Nasty booger')
1057
+ child.booger = booger
1058
+ child.save
1059
+ end
1060
+ end
1061
+ end
1062
+ end
1063
+
1064
+ #
1065
+ # has n
1066
+ #
1067
+
1068
+ it 'should return the right children for has n => has n relationships' do
1069
+ Sweets::Shop.first.ultraslices.size.should == 15
1070
+ 10.times do |i|
1071
+ Sweets::Shop.first.ultraslices.select do |ultraslice|
1072
+ ultraslice.cake == Sweets::Cake.first(:name => 'German Chocolate') && ultraslice.size == i
1073
+ end
1074
+ end
1075
+ end
1076
+
1077
+ it 'should return the right children for has n => has n => has 1' do
1078
+ Sweets::Shop.first.shapes.size.should == 15
1079
+ Sweets::Shop.first.shapes.select do |shape|
1080
+ shape.name == 'square'
1081
+ end.size.should == 10
1082
+ Sweets::Shop.first.shapes.select do |shape|
1083
+ shape.name == 'round'
1084
+ end.size.should == 5
1085
+ end
1086
+
1087
+ it 'should return the right children for has n => has n => has n' do
1088
+ Sweets::Shop.first.bites.size.should == 75
1089
+ Sweets::Shop.first.bites.select do |bite|
1090
+ bite.ultraslice.cake == Sweets::Cake.first(:name => 'German Chocolate')
1091
+ end.size.should == 60
1092
+ Sweets::Shop.first.bites.select do |bite|
1093
+ bite.ultraslice.cake == Sweets::Cake.first(:name => 'Short Cake')
1094
+ end.size.should == 15
1095
+ end
1096
+
1097
+ it 'should return the right children for has n => belongs_to relationships' do
1098
+ Sweets::Customer.first.cakes.size.should == 2
1099
+ customers = Sweets::Shop.first.customers.select do |customer|
1100
+ customer.name == 'John Johnsen'
1101
+ end
1102
+ customers.size.should == 1
1103
+ end
1104
+
1105
+ it 'should return the right children for has n => has 1 relationships' do
1106
+ Sweets::Shop.first.recipes.size.should == 2
1107
+ Sweets::Shop.first.recipes.select do |recipe|
1108
+ recipe.name == 'Schwarzwald Cake'
1109
+ end.size.should == 1
1110
+ Sweets::Shop.first.recipes.select do |recipe|
1111
+ recipe.name == "Shorty's Special"
1112
+ end.size.should == 1
1113
+ end
1114
+
1115
+ it 'should return the right children for has n => has 1 => has 1 relationships' do
1116
+ Sweets::Shop.first.creators.size.should == 2
1117
+ Sweets::Shop.first.creators.any? do |creator|
1118
+ creator.name == 'Runar'
1119
+ end.should == true
1120
+ Sweets::Shop.first.creators.any? do |creator|
1121
+ creator.name == 'Berit'
1122
+ end.should == true
1123
+ end
1124
+
1125
+ it 'should return the right children for has n => has 1 => has n relationships' do
1126
+ Sweets::Shop.first.ingredients.size.should == 10
1127
+ 4.times do |i|
1128
+ Sweets::Shop.first.ingredients.any? do |ingredient|
1129
+ ingredient.name == "Secret ingredient nr #{i}" && ingredient.recipe.cake == Sweets::Cake.first(:name => 'German Chocolate')
1130
+ end.should == true
1131
+ end
1132
+ 6.times do |i|
1133
+ Sweets::Shop.first.ingredients.any? do |ingredient|
1134
+ ingredient.name == "Well known ingredient nr #{i}" && ingredient.recipe.cake == Sweets::Cake.first(:name => 'Short Cake')
1135
+ end.should == true
1136
+ end
1137
+ end
1138
+
1139
+ #
1140
+ # has 1
1141
+ #
1142
+
1143
+ it 'should return the right children for has 1 => has 1 relationships' do
1144
+ Sweets::Shop.first.wife.should == Sweets::Wife.first
1145
+ end
1146
+
1147
+ it 'should return the right children for has 1 => has 1 => has 1 relationships' do
1148
+ Sweets::Shop.first.ring.should == Sweets::Ring.first
1149
+ end
1150
+
1151
+ it 'should return the right children for has 1 => has 1 => has n relationships' do
1152
+ Sweets::Shop.first.coats.size.should == 3
1153
+ 3.times do |i|
1154
+ Sweets::Shop.first.coats.any? do |coat|
1155
+ coat.name == "Fancy coat nr #{i}"
1156
+ end.should == true
1157
+ end
1158
+ end
1159
+
1160
+ it 'should return the right children for has 1 => has n relationships' do
1161
+ Sweets::Shop.first.children.size.should == 5
1162
+ 5.times do |i|
1163
+ Sweets::Shop.first.children.any? do |child|
1164
+ child.name == "Snotling nr #{i}"
1165
+ end.should == true
1166
+ end
1167
+ end
1168
+
1169
+ it 'should return the right children for has 1 => has n => has 1 relationships' do
1170
+ Sweets::Shop.first.boogers.size.should == 5
1171
+ Sweets::Shop.first.boogers.inject(Set.new) do |sum, booger|
1172
+ sum << booger.child_id
1173
+ end.size.should == 5
1174
+ end
1175
+
1176
+ it 'should return the right children for has 1 => has n => has n relationships' do
1177
+ Sweets::Shop.first.toys.size.should == 20
1178
+ 5.times do |child_nr|
1179
+ 4.times do |toy_nr|
1180
+ Sweets::Shop.first.toys.any? do |toy|
1181
+ toy.name == "Cheap toy nr #{toy_nr}" && toy.child = Sweets::Child.first(:name => "Snotling nr #{child_nr}")
1182
+ end.should == true
1183
+ end
1184
+ end
1185
+ end
1186
+
1187
+ #
1188
+ # misc
1189
+ #
1190
+
1191
+ it 'should join tables in the right order during has 1 => has n => has 1 queries' do
1192
+ child = Sweets::Shop.first.children(:name => 'Snotling nr 3').booger(:name.like => 'Nasty booger')
1193
+ child.should_not be_nil
1194
+ child.size.should eql(1)
1195
+ child.first.name.should eql("Nasty booger")
1196
+ end
1197
+
1198
+ it 'should join tables in the right order for belongs_to relations' do
1199
+ wife = Sweets::Wife.first(Sweets::Wife.shop_owner.name => "Betsy", Sweets::Wife.shop_owner.shop.name => "Betsy's")
1200
+ wife.should_not be_nil
1201
+ wife.name.should eql("Barry")
1202
+ end
1203
+
1204
+ it 'should raise exception if you try to change it' do
1205
+ lambda do
1206
+ Sweets::Shop.first.wife = Sweets::Wife.new(:name => 'Larry')
1207
+ end.should raise_error(DataMapper::Associations::ImmutableAssociationError)
1208
+ end
1209
+
1210
+ it 'should be reloaded when calling Resource#reload' do
1211
+ betsys = Sweets::Shop.first(:name => "Betsy's")
1212
+ betsys.send(:customers_association).should_receive(:reload).once
1213
+ lambda { betsys.reload }.should_not raise_error
1214
+ end
1215
+
1216
+ end
1217
+
1218
+ if false # Many to many not yet implemented
1219
+ describe "many to many associations" do
1220
+ before(:all) do
1221
+ class ::RightItem
1222
+ include DataMapper::Resource
1223
+
1224
+ def self.default_repository_name
1225
+ ADAPTER
1226
+ end
1227
+
1228
+ property :id, Serial
1229
+ property :name, String
1230
+
1231
+ has n..n, :left_items
1232
+ end
1233
+
1234
+ class ::LeftItem
1235
+ include DataMapper::Resource
1236
+
1237
+ def self.default_repository_name
1238
+ ADAPTER
1239
+ end
1240
+
1241
+ property :id, Serial
1242
+ property :name, String
1243
+
1244
+ has n..n, :right_items
1245
+ end
1246
+
1247
+ RightItem.auto_migrate!
1248
+ LeftItem.auto_migrate!
1249
+ end
1250
+
1251
+ def create_item_pair(number)
1252
+ @ri = RightItem.new(:name => "ri#{number}")
1253
+ @li = LeftItem.new(:name => "li#{number}")
1254
+ end
1255
+
1256
+ it "should add to the association from the left" do
1257
+ pending "Waiting on Many To Many to be implemented" do
1258
+ create_item_pair "0000"
1259
+ @ri.save; @li.save
1260
+ @ri.should_not be_new_record
1261
+ @li.should_not be_new_record
1262
+
1263
+ @li.right_items << @ri
1264
+ @li.right_items.should include(@ri)
1265
+ @li.reload
1266
+ @ri.reload
1267
+ @li.right_items.should include(@ri)
1268
+ end
1269
+ end
1270
+
1271
+ it "should add to the association from the right" do
1272
+ create_item_pair "0010"
1273
+ @ri.save; @li.save
1274
+ @ri.should_not be_new_record
1275
+ @li.should_not be_new_record
1276
+
1277
+ @ri.left_items << @li
1278
+ @ri.left_items.should include(@li)
1279
+ @li.reload
1280
+ @ri.reload
1281
+ @ri.left_items.should include(@li)
1282
+ end
1283
+
1284
+ it "should load the associated collection from the either side" do
1285
+ pending "Waiting on Many To Many to be implemented" do
1286
+ create_item_pair "0020"
1287
+ @ri.save; @li.save
1288
+ @ri.left_items << @li
1289
+ @ri.reload; @li.reload
1290
+
1291
+ @ri.left_items.should include(@li)
1292
+ @li.right_items.should include(@ri)
1293
+ end
1294
+ end
1295
+
1296
+ it "should load the associated collection from the right" do
1297
+ pending "Waiting on Many To Many to be implemented" do
1298
+ create_item_pair "0030"
1299
+ @ri.save; @li.save
1300
+ @li.right_items << @li
1301
+ @ri.reload; @li.reload
1302
+
1303
+ @ri.left_items.should include(@li)
1304
+ @li.right_items.should include(@ri)
1305
+ end
1306
+ end
1307
+
1308
+ it "should save the left side of the association if new record" do
1309
+ pending "Waiting on Many To Many to be implemented" do
1310
+ create_item_pair "0040"
1311
+ @ri.save
1312
+ @li.should be_new_record
1313
+ @ri.left_items << @li
1314
+ @li.should_not be_new_record
1315
+ end
1316
+ end
1317
+
1318
+ it "should save the right side of the association if new record" do
1319
+ pending "Waiting on Many To Many to be implemented" do
1320
+ create_item_pair "0050"
1321
+ @li.save
1322
+ @ri.should be_new_record
1323
+ @li.right_items << @ri
1324
+ @ri.should_not be_new_record
1325
+ end
1326
+ end
1327
+
1328
+ it "should save both side of the association if new record" do
1329
+ pending "Waiting on Many To Many to be implemented" do
1330
+ create_item_pair "0060"
1331
+ @li.should be_new_record
1332
+ @ri.should be_new_record
1333
+ @ri.left_items << @li
1334
+ @ri.should_not be_new_record
1335
+ @li.should_not be_new_record
1336
+ end
1337
+ end
1338
+
1339
+ it "should remove an item from the left collection without destroying the item" do
1340
+ pending "Waiting on Many To Many to be implemented" do
1341
+ create_item_pair "0070"
1342
+ @li.save; @ri.save
1343
+ @ri.left_items << @li
1344
+ @ri.reload; @li.reload
1345
+ @ri.left_items.should include(@li)
1346
+ @ri.left_items.delete(@li)
1347
+ @ri.left_items.should_not include(@li)
1348
+ @li.reload
1349
+ LeftItem.get(@li.id).should_not be_nil
1350
+ end
1351
+ end
1352
+
1353
+ it "should remove an item from the right collection without destroying the item" do
1354
+ pending "Waiting on Many To Many to be implemented" do
1355
+ create_item_pair "0080"
1356
+ @li.save; @ri.save
1357
+ @li.right_items << @ri
1358
+ @li.reload; @ri.reload
1359
+ @li.right_items.should include(@ri)
1360
+ @li.right_items.delete(@ri)
1361
+ @li.right_items.should_not include(@ri)
1362
+ @ri.reload
1363
+ RightItem.get(@ri.id).should_not be_nil
1364
+ end
1365
+ end
1366
+
1367
+ it "should remove the item from the collection when an item is deleted" do
1368
+ pending "Waiting on Many To Many to be implemented" do
1369
+ create_item_pair "0090"
1370
+ @li.save; @ri.save
1371
+ @ri.left_items << @li
1372
+ @ri.reload; @li.reload
1373
+ @ri.left_items.should include(@li)
1374
+ @li.destroy
1375
+ @ri.reload
1376
+ @ri.left_items.should_not include(@li)
1377
+ end
1378
+ end
1379
+ end
1380
+ end
1381
+ end
1382
+ end