datamapper-dm-core 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
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 +41 -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 +30 -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 +136 -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 +229 -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 +267 -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 +75 -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 +493 -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.1'
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