sam-dm-core 0.9.6

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