sam-dm-core 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +26 -0
- data/CONTRIBUTING +51 -0
- data/FAQ +92 -0
- data/History.txt +145 -0
- data/MIT-LICENSE +22 -0
- data/Manifest.txt +125 -0
- data/QUICKLINKS +12 -0
- data/README.txt +143 -0
- data/Rakefile +30 -0
- data/SPECS +63 -0
- data/TODO +1 -0
- data/lib/dm-core.rb +224 -0
- data/lib/dm-core/adapters.rb +4 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
- data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
- data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
- data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
- data/lib/dm-core/associations.rb +199 -0
- data/lib/dm-core/associations/many_to_many.rb +147 -0
- data/lib/dm-core/associations/many_to_one.rb +107 -0
- data/lib/dm-core/associations/one_to_many.rb +309 -0
- data/lib/dm-core/associations/one_to_one.rb +61 -0
- data/lib/dm-core/associations/relationship.rb +218 -0
- data/lib/dm-core/associations/relationship_chain.rb +81 -0
- data/lib/dm-core/auto_migrations.rb +113 -0
- data/lib/dm-core/collection.rb +638 -0
- data/lib/dm-core/dependency_queue.rb +31 -0
- data/lib/dm-core/hook.rb +11 -0
- data/lib/dm-core/identity_map.rb +45 -0
- data/lib/dm-core/is.rb +16 -0
- data/lib/dm-core/logger.rb +232 -0
- data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
- data/lib/dm-core/migrator.rb +29 -0
- data/lib/dm-core/model.rb +471 -0
- data/lib/dm-core/naming_conventions.rb +84 -0
- data/lib/dm-core/property.rb +673 -0
- data/lib/dm-core/property_set.rb +162 -0
- data/lib/dm-core/query.rb +625 -0
- data/lib/dm-core/repository.rb +159 -0
- data/lib/dm-core/resource.rb +637 -0
- data/lib/dm-core/scope.rb +58 -0
- data/lib/dm-core/support.rb +7 -0
- data/lib/dm-core/support/array.rb +13 -0
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/errors.rb +23 -0
- data/lib/dm-core/support/kernel.rb +7 -0
- data/lib/dm-core/support/symbol.rb +41 -0
- data/lib/dm-core/transaction.rb +267 -0
- data/lib/dm-core/type.rb +160 -0
- data/lib/dm-core/type_map.rb +80 -0
- data/lib/dm-core/types.rb +19 -0
- data/lib/dm-core/types/boolean.rb +7 -0
- data/lib/dm-core/types/discriminator.rb +34 -0
- data/lib/dm-core/types/object.rb +24 -0
- data/lib/dm-core/types/paranoid_boolean.rb +34 -0
- data/lib/dm-core/types/paranoid_datetime.rb +33 -0
- data/lib/dm-core/types/serial.rb +9 -0
- data/lib/dm-core/types/text.rb +10 -0
- data/lib/dm-core/version.rb +3 -0
- data/script/all +5 -0
- data/script/performance.rb +203 -0
- data/script/profile.rb +87 -0
- data/spec/integration/association_spec.rb +1371 -0
- data/spec/integration/association_through_spec.rb +203 -0
- data/spec/integration/associations/many_to_many_spec.rb +449 -0
- data/spec/integration/associations/many_to_one_spec.rb +163 -0
- data/spec/integration/associations/one_to_many_spec.rb +151 -0
- data/spec/integration/auto_migrations_spec.rb +398 -0
- data/spec/integration/collection_spec.rb +1069 -0
- data/spec/integration/data_objects_adapter_spec.rb +32 -0
- data/spec/integration/dependency_queue_spec.rb +58 -0
- data/spec/integration/model_spec.rb +127 -0
- data/spec/integration/mysql_adapter_spec.rb +85 -0
- data/spec/integration/postgres_adapter_spec.rb +731 -0
- data/spec/integration/property_spec.rb +233 -0
- data/spec/integration/query_spec.rb +506 -0
- data/spec/integration/repository_spec.rb +57 -0
- data/spec/integration/resource_spec.rb +475 -0
- data/spec/integration/sqlite3_adapter_spec.rb +352 -0
- data/spec/integration/sti_spec.rb +208 -0
- data/spec/integration/strategic_eager_loading_spec.rb +138 -0
- data/spec/integration/transaction_spec.rb +75 -0
- data/spec/integration/type_spec.rb +271 -0
- data/spec/lib/logging_helper.rb +18 -0
- data/spec/lib/mock_adapter.rb +27 -0
- data/spec/lib/model_loader.rb +91 -0
- data/spec/lib/publicize_methods.rb +28 -0
- data/spec/models/vehicles.rb +34 -0
- data/spec/models/zoo.rb +47 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +86 -0
- data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
- data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
- data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
- data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
- data/spec/unit/associations/many_to_many_spec.rb +17 -0
- data/spec/unit/associations/many_to_one_spec.rb +152 -0
- data/spec/unit/associations/one_to_many_spec.rb +393 -0
- data/spec/unit/associations/one_to_one_spec.rb +7 -0
- data/spec/unit/associations/relationship_spec.rb +71 -0
- data/spec/unit/associations_spec.rb +242 -0
- data/spec/unit/auto_migrations_spec.rb +111 -0
- data/spec/unit/collection_spec.rb +182 -0
- data/spec/unit/data_mapper_spec.rb +35 -0
- data/spec/unit/identity_map_spec.rb +126 -0
- data/spec/unit/is_spec.rb +80 -0
- data/spec/unit/migrator_spec.rb +33 -0
- data/spec/unit/model_spec.rb +339 -0
- data/spec/unit/naming_conventions_spec.rb +36 -0
- data/spec/unit/property_set_spec.rb +83 -0
- data/spec/unit/property_spec.rb +753 -0
- data/spec/unit/query_spec.rb +530 -0
- data/spec/unit/repository_spec.rb +93 -0
- data/spec/unit/resource_spec.rb +626 -0
- data/spec/unit/scope_spec.rb +142 -0
- data/spec/unit/transaction_spec.rb +493 -0
- data/spec/unit/type_map_spec.rb +114 -0
- data/spec/unit/type_spec.rb +119 -0
- data/tasks/ci.rb +68 -0
- data/tasks/dm.rb +63 -0
- data/tasks/doc.rb +20 -0
- data/tasks/gemspec.rb +23 -0
- data/tasks/hoe.rb +46 -0
- data/tasks/install.rb +20 -0
- metadata +216 -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.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
|