rpbertp13-dm-core 0.9.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +26 -0
- data/.gitignore +18 -0
- data/CONTRIBUTING +51 -0
- data/FAQ +92 -0
- data/History.txt +52 -0
- data/MIT-LICENSE +22 -0
- data/Manifest.txt +130 -0
- data/QUICKLINKS +11 -0
- data/README.txt +143 -0
- data/Rakefile +32 -0
- data/SPECS +62 -0
- data/TODO +1 -0
- data/dm-core.gemspec +40 -0
- data/lib/dm-core.rb +217 -0
- data/lib/dm-core/adapters.rb +16 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +209 -0
- data/lib/dm-core/adapters/data_objects_adapter.rb +716 -0
- data/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
- data/lib/dm-core/adapters/mysql_adapter.rb +138 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +189 -0
- data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
- data/lib/dm-core/associations.rb +207 -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 +315 -0
- data/lib/dm-core/associations/one_to_one.rb +61 -0
- data/lib/dm-core/associations/relationship.rb +221 -0
- data/lib/dm-core/associations/relationship_chain.rb +81 -0
- data/lib/dm-core/auto_migrations.rb +105 -0
- data/lib/dm-core/collection.rb +670 -0
- data/lib/dm-core/dependency_queue.rb +32 -0
- data/lib/dm-core/hook.rb +11 -0
- data/lib/dm-core/identity_map.rb +42 -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 +526 -0
- data/lib/dm-core/naming_conventions.rb +84 -0
- data/lib/dm-core/property.rb +676 -0
- data/lib/dm-core/property_set.rb +169 -0
- data/lib/dm-core/query.rb +676 -0
- data/lib/dm-core/repository.rb +167 -0
- data/lib/dm-core/resource.rb +671 -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 +11 -0
- data/lib/dm-core/support/symbol.rb +41 -0
- data/lib/dm-core/transaction.rb +252 -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 +4 -0
- data/script/performance.rb +282 -0
- data/script/profile.rb +87 -0
- data/spec/integration/association_spec.rb +1382 -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 +188 -0
- data/spec/integration/auto_migrations_spec.rb +413 -0
- data/spec/integration/collection_spec.rb +1073 -0
- data/spec/integration/data_objects_adapter_spec.rb +32 -0
- data/spec/integration/dependency_queue_spec.rb +46 -0
- data/spec/integration/model_spec.rb +197 -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 +253 -0
- data/spec/integration/query_spec.rb +514 -0
- data/spec/integration/repository_spec.rb +61 -0
- data/spec/integration/resource_spec.rb +513 -0
- data/spec/integration/sqlite3_adapter_spec.rb +352 -0
- data/spec/integration/sti_spec.rb +273 -0
- data/spec/integration/strategic_eager_loading_spec.rb +156 -0
- data/spec/integration/transaction_spec.rb +60 -0
- data/spec/integration/type_spec.rb +275 -0
- data/spec/lib/logging_helper.rb +18 -0
- data/spec/lib/mock_adapter.rb +27 -0
- data/spec/lib/model_loader.rb +100 -0
- data/spec/lib/publicize_methods.rb +28 -0
- data/spec/models/content.rb +16 -0
- data/spec/models/vehicles.rb +34 -0
- data/spec/models/zoo.rb +48 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +91 -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 +632 -0
- data/spec/unit/adapters/in_memory_adapter_spec.rb +98 -0
- data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
- data/spec/unit/associations/many_to_many_spec.rb +32 -0
- data/spec/unit/associations/many_to_one_spec.rb +159 -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 +321 -0
- data/spec/unit/naming_conventions_spec.rb +36 -0
- data/spec/unit/property_set_spec.rb +90 -0
- data/spec/unit/property_spec.rb +753 -0
- data/spec/unit/query_spec.rb +571 -0
- data/spec/unit/repository_spec.rb +93 -0
- data/spec/unit/resource_spec.rb +649 -0
- data/spec/unit/scope_spec.rb +142 -0
- data/spec/unit/transaction_spec.rb +469 -0
- data/spec/unit/type_map_spec.rb +114 -0
- data/spec/unit/type_spec.rb +119 -0
- data/tasks/ci.rb +36 -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 +215 -0
@@ -0,0 +1,315 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Associations
|
3
|
+
module OneToMany
|
4
|
+
extend Assertions
|
5
|
+
|
6
|
+
# Setup one to many relationship between two models
|
7
|
+
# -
|
8
|
+
# @api private
|
9
|
+
def self.setup(name, model, options = {})
|
10
|
+
assert_kind_of 'name', name, Symbol
|
11
|
+
assert_kind_of 'model', model, Model
|
12
|
+
assert_kind_of 'options', options, Hash
|
13
|
+
|
14
|
+
repository_name = model.repository.name
|
15
|
+
|
16
|
+
model.class_eval <<-EOS, __FILE__, __LINE__
|
17
|
+
def #{name}(query = {})
|
18
|
+
#{name}_association.all(query)
|
19
|
+
end
|
20
|
+
|
21
|
+
def #{name}=(children)
|
22
|
+
#{name}_association.replace(children)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def #{name}_association
|
28
|
+
@#{name}_association ||= begin
|
29
|
+
unless relationship = model.relationships(#{repository_name.inspect})[#{name.inspect}]
|
30
|
+
raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
|
31
|
+
end
|
32
|
+
association = Proxy.new(relationship, self)
|
33
|
+
parent_associations << association
|
34
|
+
association
|
35
|
+
end
|
36
|
+
end
|
37
|
+
EOS
|
38
|
+
|
39
|
+
model.relationships(repository_name)[name] = if options.has_key?(:through)
|
40
|
+
opts = options.dup
|
41
|
+
|
42
|
+
if opts.key?(:class_name) && !opts.key?(:child_key)
|
43
|
+
warn(<<-EOS.margin)
|
44
|
+
You have specified #{model.base_model.name}.has(#{name.inspect}) with :class_name => #{opts[:class_name].inspect}. You probably also want to specify the :child_key option.
|
45
|
+
EOS
|
46
|
+
end
|
47
|
+
|
48
|
+
opts[:child_model] ||= opts.delete(:class_name) || Extlib::Inflection.classify(name)
|
49
|
+
opts[:parent_model] = model
|
50
|
+
opts[:repository_name] = repository_name
|
51
|
+
opts[:near_relationship_name] = opts.delete(:through)
|
52
|
+
opts[:remote_relationship_name] ||= opts.delete(:remote_name) || name
|
53
|
+
opts[:parent_key] = opts[:parent_key]
|
54
|
+
opts[:child_key] = opts[:child_key]
|
55
|
+
|
56
|
+
RelationshipChain.new( opts )
|
57
|
+
else
|
58
|
+
Relationship.new(
|
59
|
+
name,
|
60
|
+
repository_name,
|
61
|
+
options.fetch(:class_name, Extlib::Inflection.classify(name)),
|
62
|
+
model,
|
63
|
+
options
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# TODO: look at making this inherit from Collection. The API is
|
69
|
+
# almost identical, and it would make more sense for the
|
70
|
+
# relationship.get_children method to return a Proxy than a
|
71
|
+
# Collection that is wrapped in a Proxy.
|
72
|
+
class Proxy
|
73
|
+
include Assertions
|
74
|
+
|
75
|
+
instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ class object_id kind_of? respond_to? assert_kind_of should should_not instance_variable_set instance_variable_get ].include?(m.to_s) }
|
76
|
+
|
77
|
+
# FIXME: remove when RelationshipChain#get_children can return a Collection
|
78
|
+
def all(query = {})
|
79
|
+
query.empty? ? self : @relationship.get_children(@parent, query)
|
80
|
+
end
|
81
|
+
|
82
|
+
# FIXME: remove when RelationshipChain#get_children can return a Collection
|
83
|
+
def first(*args)
|
84
|
+
if args.last.respond_to?(:merge)
|
85
|
+
query = args.pop
|
86
|
+
@relationship.get_children(@parent, query, :first, *args)
|
87
|
+
else
|
88
|
+
children.first(*args)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def <<(resource)
|
93
|
+
assert_mutable
|
94
|
+
return self if !resource.new_record? && self.include?(resource)
|
95
|
+
children << resource
|
96
|
+
relate_resource(resource)
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
def push(*resources)
|
101
|
+
assert_mutable
|
102
|
+
resources.reject! { |resource| !resource.new_record? && self.include?(resource) }
|
103
|
+
children.push(*resources)
|
104
|
+
resources.each { |resource| relate_resource(resource) }
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
def unshift(*resources)
|
109
|
+
assert_mutable
|
110
|
+
resources.reject! { |resource| !resource.new_record? && self.include?(resource) }
|
111
|
+
children.unshift(*resources)
|
112
|
+
resources.each { |resource| relate_resource(resource) }
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
def replace(other)
|
117
|
+
assert_mutable
|
118
|
+
each { |resource| orphan_resource(resource) }
|
119
|
+
other = other.map { |resource| resource.kind_of?(Hash) ? new_child(resource) : resource }
|
120
|
+
children.replace(other)
|
121
|
+
other.each { |resource| relate_resource(resource) }
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
def pop
|
126
|
+
assert_mutable
|
127
|
+
orphan_resource(children.pop)
|
128
|
+
end
|
129
|
+
|
130
|
+
def shift
|
131
|
+
assert_mutable
|
132
|
+
orphan_resource(children.shift)
|
133
|
+
end
|
134
|
+
|
135
|
+
def delete(resource)
|
136
|
+
assert_mutable
|
137
|
+
orphan_resource(children.delete(resource))
|
138
|
+
end
|
139
|
+
|
140
|
+
def delete_at(index)
|
141
|
+
assert_mutable
|
142
|
+
orphan_resource(children.delete_at(index))
|
143
|
+
end
|
144
|
+
|
145
|
+
def clear
|
146
|
+
assert_mutable
|
147
|
+
each { |resource| orphan_resource(resource) }
|
148
|
+
children.clear
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
152
|
+
def build(attributes = {})
|
153
|
+
assert_mutable
|
154
|
+
attributes = default_attributes.merge(attributes)
|
155
|
+
resource = children.respond_to?(:build) ? children.build(attributes) : new_child(attributes)
|
156
|
+
resource
|
157
|
+
end
|
158
|
+
|
159
|
+
def new(attributes = {})
|
160
|
+
assert_mutable
|
161
|
+
raise UnsavedParentError, 'You cannot intialize until the parent is saved' if @parent.new_record?
|
162
|
+
attributes = default_attributes.merge(attributes)
|
163
|
+
resource = children.respond_to?(:new) ? children.new(attributes) : @relationship.child_model.new(attributes)
|
164
|
+
self << resource
|
165
|
+
resource
|
166
|
+
end
|
167
|
+
|
168
|
+
def create(attributes = {})
|
169
|
+
assert_mutable
|
170
|
+
raise UnsavedParentError, 'You cannot create until the parent is saved' if @parent.new_record?
|
171
|
+
attributes = default_attributes.merge(attributes)
|
172
|
+
resource = children.respond_to?(:create) ? children.create(attributes) : @relationship.child_model.create(attributes)
|
173
|
+
self << resource
|
174
|
+
resource
|
175
|
+
end
|
176
|
+
|
177
|
+
def update(attributes = {})
|
178
|
+
assert_mutable
|
179
|
+
raise UnsavedParentError, 'You cannot mass-update until the parent is saved' if @parent.new_record?
|
180
|
+
children.update(attributes)
|
181
|
+
end
|
182
|
+
|
183
|
+
def update!(attributes = {})
|
184
|
+
assert_mutable
|
185
|
+
raise UnsavedParentError, 'You cannot mass-update without validations until the parent is saved' if @parent.new_record?
|
186
|
+
children.update!(attributes)
|
187
|
+
end
|
188
|
+
|
189
|
+
def destroy
|
190
|
+
assert_mutable
|
191
|
+
raise UnsavedParentError, 'You cannot mass-delete until the parent is saved' if @parent.new_record?
|
192
|
+
children.destroy
|
193
|
+
end
|
194
|
+
|
195
|
+
def destroy!
|
196
|
+
assert_mutable
|
197
|
+
raise UnsavedParentError, 'You cannot mass-delete without validations until the parent is saved' if @parent.new_record?
|
198
|
+
children.destroy!
|
199
|
+
end
|
200
|
+
|
201
|
+
def reload
|
202
|
+
@children = nil
|
203
|
+
self
|
204
|
+
end
|
205
|
+
|
206
|
+
def save
|
207
|
+
return true if children.frozen?
|
208
|
+
|
209
|
+
# save every resource in the collection
|
210
|
+
each { |resource| save_resource(resource) }
|
211
|
+
|
212
|
+
# save orphan resources
|
213
|
+
@orphans.each do |resource|
|
214
|
+
begin
|
215
|
+
save_resource(resource, nil)
|
216
|
+
rescue
|
217
|
+
children << resource unless children.frozen? || children.include?(resource)
|
218
|
+
raise
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# FIXME: remove when RelationshipChain#get_children can return a Collection
|
223
|
+
# place the children into a Collection if not already
|
224
|
+
if children.kind_of?(Array) && !children.frozen?
|
225
|
+
@children = @relationship.get_children(@parent).replace(children)
|
226
|
+
end
|
227
|
+
|
228
|
+
true
|
229
|
+
end
|
230
|
+
|
231
|
+
def kind_of?(klass)
|
232
|
+
super || children.kind_of?(klass)
|
233
|
+
end
|
234
|
+
|
235
|
+
def respond_to?(method, include_private = false)
|
236
|
+
super || children.respond_to?(method, include_private)
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
def initialize(relationship, parent)
|
242
|
+
assert_kind_of 'relationship', relationship, Relationship
|
243
|
+
assert_kind_of 'parent', parent, Resource
|
244
|
+
|
245
|
+
@relationship = relationship
|
246
|
+
@parent = parent
|
247
|
+
@orphans = []
|
248
|
+
end
|
249
|
+
|
250
|
+
def children
|
251
|
+
@children ||= @relationship.get_children(@parent)
|
252
|
+
end
|
253
|
+
|
254
|
+
def assert_mutable
|
255
|
+
raise ImmutableAssociationError, 'You can not modify this association' if children.frozen?
|
256
|
+
end
|
257
|
+
|
258
|
+
def default_attributes
|
259
|
+
default_attributes = {}
|
260
|
+
|
261
|
+
@relationship.query.each do |attribute, value|
|
262
|
+
next if Query::OPTIONS.include?(attribute) || attribute.kind_of?(Query::Operator)
|
263
|
+
default_attributes[attribute] = value
|
264
|
+
end
|
265
|
+
|
266
|
+
@relationship.child_key.zip(@relationship.parent_key.get(@parent)) do |property,value|
|
267
|
+
default_attributes[property.name] = value
|
268
|
+
end
|
269
|
+
|
270
|
+
default_attributes
|
271
|
+
end
|
272
|
+
|
273
|
+
def add_default_association_values(resource)
|
274
|
+
default_attributes.each do |attribute, value|
|
275
|
+
next if !resource.respond_to?("#{attribute}=") || resource.attribute_loaded?(attribute)
|
276
|
+
resource.send("#{attribute}=", value)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def new_child(attributes)
|
281
|
+
@relationship.child_model.new(default_attributes.merge(attributes))
|
282
|
+
end
|
283
|
+
|
284
|
+
def relate_resource(resource)
|
285
|
+
assert_mutable
|
286
|
+
add_default_association_values(resource)
|
287
|
+
@orphans.delete(resource)
|
288
|
+
resource
|
289
|
+
end
|
290
|
+
|
291
|
+
def orphan_resource(resource)
|
292
|
+
assert_mutable
|
293
|
+
@orphans << resource
|
294
|
+
resource
|
295
|
+
end
|
296
|
+
|
297
|
+
def save_resource(resource, parent = @parent)
|
298
|
+
@relationship.with_repository(resource) do |r|
|
299
|
+
if parent.nil? && resource.model.respond_to?(:many_to_many)
|
300
|
+
resource.destroy
|
301
|
+
else
|
302
|
+
@relationship.attach_parent(resource, parent)
|
303
|
+
resource.save
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def method_missing(method, *args, &block)
|
309
|
+
results = children.send(method, *args, &block)
|
310
|
+
results.equal?(children) ? self : results
|
311
|
+
end
|
312
|
+
end # class Proxy
|
313
|
+
end # module OneToMany
|
314
|
+
end # module Associations
|
315
|
+
end # module DataMapper
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Associations
|
3
|
+
module OneToOne
|
4
|
+
extend Assertions
|
5
|
+
|
6
|
+
# Setup one to one relationship between two models
|
7
|
+
# -
|
8
|
+
# @api private
|
9
|
+
def self.setup(name, model, options = {})
|
10
|
+
assert_kind_of 'name', name, Symbol
|
11
|
+
assert_kind_of 'model', model, Model
|
12
|
+
assert_kind_of 'options', options, Hash
|
13
|
+
|
14
|
+
repository_name = model.repository.name
|
15
|
+
|
16
|
+
model.class_eval <<-EOS, __FILE__, __LINE__
|
17
|
+
def #{name}
|
18
|
+
#{name}_association.first
|
19
|
+
end
|
20
|
+
|
21
|
+
def #{name}=(child_resource)
|
22
|
+
#{name}_association.replace(child_resource.nil? ? [] : [ child_resource ])
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def #{name}_association
|
28
|
+
@#{name}_association ||= begin
|
29
|
+
unless relationship = model.relationships(#{repository_name.inspect})[:#{name}]
|
30
|
+
raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
|
31
|
+
end
|
32
|
+
association = Associations::OneToMany::Proxy.new(relationship, self)
|
33
|
+
parent_associations << association
|
34
|
+
association
|
35
|
+
end
|
36
|
+
end
|
37
|
+
EOS
|
38
|
+
|
39
|
+
model.relationships(repository_name)[name] = if options.has_key?(:through)
|
40
|
+
RelationshipChain.new(
|
41
|
+
:child_model => options.fetch(:class_name, Extlib::Inflection.classify(name)),
|
42
|
+
:parent_model => model,
|
43
|
+
:repository_name => repository_name,
|
44
|
+
:near_relationship_name => options[:through],
|
45
|
+
:remote_relationship_name => options.fetch(:remote_name, name),
|
46
|
+
:parent_key => options[:parent_key],
|
47
|
+
:child_key => options[:child_key]
|
48
|
+
)
|
49
|
+
else
|
50
|
+
Relationship.new(
|
51
|
+
name,
|
52
|
+
repository_name,
|
53
|
+
options.fetch(:class_name, Extlib::Inflection.classify(name)),
|
54
|
+
model,
|
55
|
+
options
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end # module HasOne
|
60
|
+
end # module Associations
|
61
|
+
end # module DataMapper
|
@@ -0,0 +1,221 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Associations
|
3
|
+
class Relationship
|
4
|
+
include Assertions
|
5
|
+
|
6
|
+
OPTIONS = [ :class_name, :child_key, :parent_key, :min, :max, :through ]
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
attr_reader :name, :options, :query
|
10
|
+
|
11
|
+
def child_key
|
12
|
+
child_key = child_model.repository.scope do |r|
|
13
|
+
model_properties = child_model.properties(r.name)
|
14
|
+
|
15
|
+
child_key = parent_key.zip(@child_properties).map do |parent_property,property_name|
|
16
|
+
# TODO: use something similar to DM::NamingConventions to determine the property name
|
17
|
+
parent_name = Extlib::Inflection.underscore(Extlib::Inflection.demodulize(parent_model.base_model.name))
|
18
|
+
property_name ||= "#{parent_name}_#{parent_property.name}".to_sym
|
19
|
+
|
20
|
+
if model_properties.has_property?(property_name)
|
21
|
+
model_properties[property_name]
|
22
|
+
else
|
23
|
+
options = {}
|
24
|
+
|
25
|
+
[ :length, :precision, :scale ].each do |option|
|
26
|
+
options[option] = parent_property.send(option)
|
27
|
+
end
|
28
|
+
|
29
|
+
# NOTE: hack to make each many to many child_key a true key,
|
30
|
+
# until I can figure out a better place for this check
|
31
|
+
if child_model.respond_to?(:many_to_many)
|
32
|
+
options[:key] = true
|
33
|
+
end
|
34
|
+
|
35
|
+
child_model.property(property_name, parent_property.primitive, options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
@child_key = PropertySet.new(child_key)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def parent_key
|
44
|
+
parent_key = parent_model.repository.scope do |r|
|
45
|
+
if @parent_properties
|
46
|
+
parent_model.properties(r.name).slice(*@parent_properties)
|
47
|
+
else
|
48
|
+
parent_model.key
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@parent_key = PropertySet.new(parent_key)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
def parent_model
|
56
|
+
return @parent_model if model_defined?(@parent_model)
|
57
|
+
@parent_model = @child_model.find_const(@parent_model)
|
58
|
+
rescue NameError
|
59
|
+
raise NameError, "Cannot find the parent_model #{@parent_model} for #{@child_model}"
|
60
|
+
end
|
61
|
+
|
62
|
+
# @api private
|
63
|
+
def child_model
|
64
|
+
return @child_model if model_defined?(@child_model)
|
65
|
+
@child_model = @parent_model.find_const(@child_model)
|
66
|
+
rescue NameError
|
67
|
+
raise NameError, "Cannot find the child_model #{@child_model} for #{@parent_model}"
|
68
|
+
end
|
69
|
+
|
70
|
+
# @api private
|
71
|
+
def get_children(parent, options = {}, finder = :all, *args)
|
72
|
+
parent_value = parent_key.get(parent)
|
73
|
+
bind_values = [ parent_value ]
|
74
|
+
|
75
|
+
with_repository(child_model) do |r|
|
76
|
+
parent_identity_map = parent.repository.identity_map(parent_model)
|
77
|
+
|
78
|
+
query_values = parent_identity_map.keys
|
79
|
+
|
80
|
+
bind_values = query_values unless query_values.empty?
|
81
|
+
query = child_key.zip(bind_values.transpose).to_hash
|
82
|
+
|
83
|
+
collection = child_model.send(finder, *(args.dup << @query.merge(options).merge(query)))
|
84
|
+
|
85
|
+
return collection unless collection.kind_of?(Collection) && collection.any?
|
86
|
+
|
87
|
+
grouped_collection = {}
|
88
|
+
collection.each do |resource|
|
89
|
+
child_value = child_key.get(resource)
|
90
|
+
next unless parent_obj = parent_identity_map[child_value]
|
91
|
+
grouped_collection[parent_obj] ||= []
|
92
|
+
grouped_collection[parent_obj] << resource
|
93
|
+
end
|
94
|
+
|
95
|
+
association_accessor = "#{self.name}_association"
|
96
|
+
|
97
|
+
ret = nil
|
98
|
+
grouped_collection.each do |parent, children|
|
99
|
+
association = parent.send(association_accessor)
|
100
|
+
|
101
|
+
query = collection.query.dup
|
102
|
+
query.conditions.map! do |operator, property, bind_value|
|
103
|
+
property = property.property if property.class == DataMapper::Query::Path
|
104
|
+
if operator != :raw && child_key.has_property?(property.name)
|
105
|
+
bind_value = *children.map { |child| property.get(child) }.uniq
|
106
|
+
end
|
107
|
+
[ operator, property, bind_value ]
|
108
|
+
end
|
109
|
+
|
110
|
+
parents_children = Collection.new(query)
|
111
|
+
children.each { |child| parents_children.send(:add, child) }
|
112
|
+
|
113
|
+
if parent_key.get(parent) == parent_value
|
114
|
+
ret = parents_children
|
115
|
+
else
|
116
|
+
association.instance_variable_set(:@children, parents_children)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
ret || child_model.send(finder, *(args.dup << @query.merge(options).merge(child_key.zip([ parent_value ]).to_hash)))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# @api private
|
125
|
+
def get_parent(child, parent = nil)
|
126
|
+
child_value = child_key.get(child)
|
127
|
+
return nil if child_value.any? { |v| v.nil? }
|
128
|
+
|
129
|
+
with_repository(parent || parent_model) do
|
130
|
+
parent_identity_map = (parent || parent_model).repository.identity_map(parent_model.base_model)
|
131
|
+
child_identity_map = child.repository.identity_map(child_model.base_model)
|
132
|
+
|
133
|
+
if parent = parent_identity_map[child_value]
|
134
|
+
return parent
|
135
|
+
end
|
136
|
+
|
137
|
+
children = child_identity_map.values
|
138
|
+
children << child unless child_identity_map[child.key]
|
139
|
+
|
140
|
+
bind_values = children.map { |c| child_key.get(c) }.uniq
|
141
|
+
query_values = bind_values.reject { |k| parent_identity_map[k] }
|
142
|
+
|
143
|
+
bind_values = query_values unless query_values.empty?
|
144
|
+
query = parent_key.zip(bind_values.transpose).to_hash
|
145
|
+
association_accessor = "#{self.name}_association"
|
146
|
+
|
147
|
+
collection = parent_model.send(:all, query)
|
148
|
+
unless collection.empty?
|
149
|
+
collection.send(:lazy_load)
|
150
|
+
children.each do |c|
|
151
|
+
c.send(association_accessor).instance_variable_set(:@parent, collection.get(*child_key.get(c)))
|
152
|
+
end
|
153
|
+
child.send(association_accessor).instance_variable_get(:@parent)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# @api private
|
159
|
+
def with_repository(object = nil)
|
160
|
+
other_model = object.model == child_model ? parent_model : child_model if object.respond_to?(:model)
|
161
|
+
other_model = object == child_model ? parent_model : child_model if object.kind_of?(DataMapper::Resource)
|
162
|
+
|
163
|
+
if other_model && other_model.repository == object.repository && object.repository.name != @repository_name
|
164
|
+
object.repository.scope { |block_args| yield(*block_args) }
|
165
|
+
else
|
166
|
+
repository(@repository_name) { |block_args| yield(*block_args) }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# @api private
|
171
|
+
def attach_parent(child, parent)
|
172
|
+
child_key.set(child, parent && parent_key.get(parent))
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
# +child_model_name and child_properties refers to the FK, parent_model_name
|
178
|
+
# and parent_properties refer to the PK. For more information:
|
179
|
+
# http://edocs.bea.com/kodo/docs41/full/html/jdo_overview_mapping_join.html
|
180
|
+
# I wash my hands of it!
|
181
|
+
def initialize(name, repository_name, child_model, parent_model, options = {})
|
182
|
+
assert_kind_of 'name', name, Symbol
|
183
|
+
assert_kind_of 'repository_name', repository_name, Symbol
|
184
|
+
assert_kind_of 'child_model', child_model, String, Class
|
185
|
+
assert_kind_of 'parent_model', parent_model, String, Class
|
186
|
+
|
187
|
+
unless model_defined?(child_model) || model_defined?(parent_model)
|
188
|
+
raise 'at least one of child_model and parent_model must be a Model object'
|
189
|
+
end
|
190
|
+
|
191
|
+
if child_properties = options[:child_key]
|
192
|
+
assert_kind_of 'options[:child_key]', child_properties, Array
|
193
|
+
end
|
194
|
+
|
195
|
+
if parent_properties = options[:parent_key]
|
196
|
+
assert_kind_of 'options[:parent_key]', parent_properties, Array
|
197
|
+
end
|
198
|
+
|
199
|
+
@name = name
|
200
|
+
@repository_name = repository_name
|
201
|
+
@child_model = child_model
|
202
|
+
@child_properties = child_properties # may be nil
|
203
|
+
@query = options.reject { |k,v| OPTIONS.include?(k) }
|
204
|
+
@parent_model = parent_model
|
205
|
+
@parent_properties = parent_properties # may be nil
|
206
|
+
@options = options
|
207
|
+
|
208
|
+
# attempt to load the child_key if the parent and child model constants are defined
|
209
|
+
if model_defined?(@child_model) && model_defined?(@parent_model)
|
210
|
+
child_key
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# @api private
|
215
|
+
def model_defined?(model)
|
216
|
+
# TODO: figure out other ways to see if the model is loaded
|
217
|
+
model.kind_of?(Class)
|
218
|
+
end
|
219
|
+
end # class Relationship
|
220
|
+
end # module Associations
|
221
|
+
end # module DataMapper
|