dm-core 0.9.2 → 0.9.3
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/{CHANGELOG → History.txt} +78 -77
- data/Manifest.txt +123 -0
- data/{README → README.txt} +0 -0
- data/Rakefile +29 -0
- data/SPECS +63 -0
- data/TODO +1 -0
- data/lib/dm-core.rb +6 -1
- data/lib/dm-core/adapters/data_objects_adapter.rb +29 -32
- data/lib/dm-core/adapters/mysql_adapter.rb +1 -1
- data/lib/dm-core/adapters/postgres_adapter.rb +1 -1
- data/lib/dm-core/adapters/sqlite3_adapter.rb +2 -2
- data/lib/dm-core/associations.rb +26 -0
- data/lib/dm-core/associations/many_to_many.rb +34 -25
- data/lib/dm-core/associations/many_to_one.rb +4 -4
- data/lib/dm-core/associations/one_to_many.rb +48 -13
- data/lib/dm-core/associations/one_to_one.rb +4 -4
- data/lib/dm-core/associations/relationship.rb +144 -42
- data/lib/dm-core/associations/relationship_chain.rb +31 -24
- data/lib/dm-core/auto_migrations.rb +0 -4
- data/lib/dm-core/collection.rb +40 -7
- data/lib/dm-core/dependency_queue.rb +31 -0
- data/lib/dm-core/hook.rb +2 -2
- data/lib/dm-core/is.rb +2 -2
- data/lib/dm-core/logger.rb +10 -10
- data/lib/dm-core/model.rb +94 -41
- data/lib/dm-core/property.rb +72 -41
- data/lib/dm-core/property_set.rb +8 -14
- data/lib/dm-core/query.rb +34 -9
- data/lib/dm-core/repository.rb +0 -0
- data/lib/dm-core/resource.rb +13 -13
- data/lib/dm-core/scope.rb +25 -2
- data/lib/dm-core/type.rb +3 -3
- data/lib/dm-core/types/discriminator.rb +10 -8
- data/lib/dm-core/types/object.rb +4 -0
- data/lib/dm-core/types/paranoid_boolean.rb +15 -4
- data/lib/dm-core/types/paranoid_datetime.rb +15 -4
- data/lib/dm-core/version.rb +3 -0
- data/script/all +5 -0
- data/script/performance.rb +191 -0
- data/script/profile.rb +86 -0
- data/spec/integration/association_spec.rb +288 -204
- data/spec/integration/association_through_spec.rb +9 -3
- data/spec/integration/associations/many_to_many_spec.rb +97 -31
- data/spec/integration/associations/many_to_one_spec.rb +41 -6
- data/spec/integration/associations/one_to_many_spec.rb +18 -2
- data/spec/integration/auto_migrations_spec.rb +0 -0
- data/spec/integration/collection_spec.rb +89 -42
- data/spec/integration/dependency_queue_spec.rb +58 -0
- data/spec/integration/model_spec.rb +67 -8
- data/spec/integration/postgres_adapter_spec.rb +19 -20
- data/spec/integration/property_spec.rb +17 -8
- data/spec/integration/query_spec.rb +273 -191
- data/spec/integration/resource_spec.rb +108 -10
- data/spec/integration/strategic_eager_loading_spec.rb +138 -0
- data/spec/integration/transaction_spec.rb +3 -3
- data/spec/integration/type_spec.rb +121 -0
- data/spec/lib/logging_helper.rb +18 -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 +48 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +25 -62
- data/spec/unit/adapters/data_objects_adapter_spec.rb +1 -0
- data/spec/unit/associations/many_to_many_spec.rb +3 -0
- data/spec/unit/associations/many_to_one_spec.rb +9 -1
- data/spec/unit/associations/one_to_many_spec.rb +12 -4
- data/spec/unit/associations/relationship_spec.rb +19 -15
- data/spec/unit/associations_spec.rb +37 -0
- data/spec/unit/collection_spec.rb +8 -0
- data/spec/unit/data_mapper_spec.rb +14 -0
- data/spec/unit/model_spec.rb +2 -2
- data/spec/unit/property_set_spec.rb +0 -13
- data/spec/unit/property_spec.rb +92 -21
- data/spec/unit/query_spec.rb +49 -4
- data/spec/unit/resource_spec.rb +122 -60
- data/spec/unit/scope_spec.rb +11 -0
- data/tasks/ci.rb +68 -0
- data/tasks/dm.rb +63 -0
- data/tasks/doc.rb +20 -0
- data/tasks/hoe.rb +38 -0
- data/tasks/install.rb +20 -0
- metadata +63 -22
@@ -1,4 +1,4 @@
|
|
1
|
-
gem 'do_sqlite3', '=0.9.
|
1
|
+
gem 'do_sqlite3', '=0.9.3'
|
2
2
|
require 'do_sqlite3'
|
3
3
|
|
4
4
|
module DataMapper
|
@@ -41,7 +41,7 @@ module DataMapper
|
|
41
41
|
end
|
42
42
|
|
43
43
|
module SQL
|
44
|
-
private
|
44
|
+
# private ## This cannot be private for current migrations
|
45
45
|
|
46
46
|
# TODO: move to dm-more/dm-migrations
|
47
47
|
def supports_serial?
|
data/lib/dm-core/associations.rb
CHANGED
@@ -17,6 +17,32 @@ module DataMapper
|
|
17
17
|
class UnsavedParentError < RuntimeError
|
18
18
|
end
|
19
19
|
|
20
|
+
# Returns all relationships that are many-to-one for this model.
|
21
|
+
#
|
22
|
+
# Used to find the relationships that require properties in any Repository.
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
# class Plur
|
26
|
+
# include DataMapper::Resource
|
27
|
+
# def self.default_repository_name
|
28
|
+
# :plur_db
|
29
|
+
# end
|
30
|
+
# repository(:plupp_db) do
|
31
|
+
# has 1, :plupp
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# This resource has a many-to-one to the Plupp resource residing in the :plupp_db repository,
|
36
|
+
# but the Plur resource needs the plupp_id property no matter what repository itself lives in,
|
37
|
+
# ie we need to create that property when we migrate etc.
|
38
|
+
#
|
39
|
+
# Used in DataMapper::Model.properties_with_subclasses
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
def many_to_one_relationships
|
43
|
+
@relationships.values.collect do |rels| rels.values end.flatten.select do |relationship| relationship.child_model == self end
|
44
|
+
end
|
45
|
+
|
20
46
|
def relationships(repository_name = default_repository_name)
|
21
47
|
@relationships ||= Hash.new { |h,k| h[k] = k == Repository.default_name ? {} : h[Repository.default_name].dup }
|
22
48
|
@relationships[repository_name]
|
@@ -28,7 +28,7 @@ module DataMapper
|
|
28
28
|
def #{name}_association
|
29
29
|
@#{name}_association ||= begin
|
30
30
|
unless relationship = model.relationships(#{repository_name.inspect})[#{name.inspect}]
|
31
|
-
raise ArgumentError,
|
31
|
+
raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
|
32
32
|
end
|
33
33
|
association = Proxy.new(relationship, self)
|
34
34
|
parent_associations << association
|
@@ -40,14 +40,14 @@ module DataMapper
|
|
40
40
|
opts = options.dup
|
41
41
|
opts.delete(:through)
|
42
42
|
opts[:child_model] ||= opts.delete(:class_name) || Extlib::Inflection.classify(name)
|
43
|
-
opts[:parent_model] = model
|
43
|
+
opts[:parent_model] = model
|
44
44
|
opts[:repository_name] = repository_name
|
45
45
|
opts[:remote_relationship_name] ||= opts.delete(:remote_name) || name
|
46
46
|
opts[:parent_key] = opts[:parent_key]
|
47
47
|
opts[:child_key] = opts[:child_key]
|
48
48
|
opts[:mutable] = true
|
49
49
|
|
50
|
-
names = [ opts[:child_model], opts[:parent_model] ].sort
|
50
|
+
names = [ opts[:child_model], opts[:parent_model].name ].sort
|
51
51
|
model_name = names.join
|
52
52
|
storage_name = Extlib::Inflection.tableize(Extlib::Inflection.pluralize(names[0]) + names[1])
|
53
53
|
|
@@ -77,39 +77,50 @@ module DataMapper
|
|
77
77
|
end
|
78
78
|
|
79
79
|
class Proxy < DataMapper::Associations::OneToMany::Proxy
|
80
|
-
|
81
|
-
def <<(resource)
|
82
|
-
resource.save if resource.new_record?
|
83
|
-
through = @relationship.child_model.new
|
84
|
-
@relationship.child_key.each_with_index do |key, index|
|
85
|
-
through.send("#{key.name}=", @relationship.parent_key.key[index].get(@parent))
|
86
|
-
end
|
87
|
-
remote_relationship.child_key.each_with_index do |key, index|
|
88
|
-
through.send("#{key.name}=", remote_relationship.parent_key.key[index].get(resource))
|
89
|
-
end
|
90
|
-
near_model << through
|
91
|
-
super
|
92
|
-
end
|
93
|
-
|
94
80
|
def delete(resource)
|
95
|
-
through =
|
96
|
-
|
81
|
+
through = near_association.get(*(@parent.key + resource.key))
|
82
|
+
near_association.delete(through)
|
97
83
|
orphan_resource(super)
|
98
84
|
end
|
99
85
|
|
100
86
|
def clear
|
101
|
-
|
87
|
+
near_association.clear
|
102
88
|
super
|
103
89
|
end
|
104
90
|
|
105
91
|
def destroy
|
106
|
-
|
92
|
+
near_association.destroy
|
107
93
|
super
|
108
94
|
end
|
109
95
|
|
110
96
|
def save
|
111
97
|
end
|
112
98
|
|
99
|
+
private
|
100
|
+
|
101
|
+
def new_child(attributes)
|
102
|
+
remote_relationship.parent_model.new(attributes)
|
103
|
+
end
|
104
|
+
|
105
|
+
def relate_resource(resource)
|
106
|
+
assert_mutable
|
107
|
+
add_default_association_values(resource)
|
108
|
+
@orphans.delete(resource)
|
109
|
+
|
110
|
+
# TODO: fix this so it does not automatically save on append, if possible
|
111
|
+
resource.save if resource.new_record?
|
112
|
+
through_resource = @relationship.child_model.new
|
113
|
+
@relationship.child_key.zip(@relationship.parent_key) do |child_key,parent_key|
|
114
|
+
through_resource.send("#{child_key.name}=", parent_key.get(@parent))
|
115
|
+
end
|
116
|
+
remote_relationship.child_key.zip(remote_relationship.parent_key) do |child_key,parent_key|
|
117
|
+
through_resource.send("#{child_key.name}=", parent_key.get(resource))
|
118
|
+
end
|
119
|
+
near_association << through_resource
|
120
|
+
|
121
|
+
resource
|
122
|
+
end
|
123
|
+
|
113
124
|
def orphan_resource(resource)
|
114
125
|
assert_mutable
|
115
126
|
@orphans << resource
|
@@ -119,14 +130,12 @@ module DataMapper
|
|
119
130
|
def assert_mutable
|
120
131
|
end
|
121
132
|
|
122
|
-
private
|
123
|
-
|
124
133
|
def remote_relationship
|
125
134
|
@remote_relationship ||= @relationship.send(:remote_relationship)
|
126
135
|
end
|
127
136
|
|
128
|
-
def
|
129
|
-
@
|
137
|
+
def near_association
|
138
|
+
@near_association ||= @parent.send(near_relationship_name)
|
130
139
|
end
|
131
140
|
|
132
141
|
def near_relationship_name
|
@@ -27,7 +27,7 @@ module DataMapper
|
|
27
27
|
def #{name}_association
|
28
28
|
@#{name}_association ||= begin
|
29
29
|
unless relationship = model.relationships(#{repository_name.inspect})[:#{name}]
|
30
|
-
raise ArgumentError,
|
30
|
+
raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
|
31
31
|
end
|
32
32
|
association = Proxy.new(relationship, self)
|
33
33
|
child_associations << association
|
@@ -39,7 +39,7 @@ module DataMapper
|
|
39
39
|
model.relationships(repository_name)[name] = Relationship.new(
|
40
40
|
name,
|
41
41
|
repository_name,
|
42
|
-
model
|
42
|
+
model,
|
43
43
|
options.fetch(:class_name, Extlib::Inflection.classify(name)),
|
44
44
|
options
|
45
45
|
)
|
@@ -48,7 +48,7 @@ module DataMapper
|
|
48
48
|
class Proxy
|
49
49
|
include Assertions
|
50
50
|
|
51
|
-
instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ class kind_of? respond_to? assert_kind_of should should_not ].include?(m) }
|
51
|
+
instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ class kind_of? respond_to? assert_kind_of should should_not instance_variable_set instance_variable_get ].include?(m) }
|
52
52
|
|
53
53
|
def replace(parent)
|
54
54
|
@parent = parent
|
@@ -60,7 +60,7 @@ module DataMapper
|
|
60
60
|
return false if @parent.nil?
|
61
61
|
return true unless parent.new_record?
|
62
62
|
|
63
|
-
|
63
|
+
@relationship.with_repository(parent) do
|
64
64
|
parent.save
|
65
65
|
end
|
66
66
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
|
3
1
|
module DataMapper
|
4
2
|
module Associations
|
5
3
|
module OneToMany
|
@@ -29,7 +27,7 @@ module DataMapper
|
|
29
27
|
def #{name}_association
|
30
28
|
@#{name}_association ||= begin
|
31
29
|
unless relationship = model.relationships(#{repository_name.inspect})[#{name.inspect}]
|
32
|
-
raise ArgumentError,
|
30
|
+
raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
|
33
31
|
end
|
34
32
|
association = Proxy.new(relationship, self)
|
35
33
|
parent_associations << association
|
@@ -41,8 +39,14 @@ module DataMapper
|
|
41
39
|
model.relationships(repository_name)[name] = if options.has_key?(:through)
|
42
40
|
opts = options.dup
|
43
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
|
+
|
44
48
|
opts[:child_model] ||= opts.delete(:class_name) || Extlib::Inflection.classify(name)
|
45
|
-
opts[:parent_model] = model
|
49
|
+
opts[:parent_model] = model
|
46
50
|
opts[:repository_name] = repository_name
|
47
51
|
opts[:near_relationship_name] = opts.delete(:through)
|
48
52
|
opts[:remote_relationship_name] ||= opts.delete(:remote_name) || name
|
@@ -52,10 +56,10 @@ module DataMapper
|
|
52
56
|
RelationshipChain.new( opts )
|
53
57
|
else
|
54
58
|
Relationship.new(
|
55
|
-
|
59
|
+
name,
|
56
60
|
repository_name,
|
57
61
|
options.fetch(:class_name, Extlib::Inflection.classify(name)),
|
58
|
-
model
|
62
|
+
model,
|
59
63
|
options
|
60
64
|
)
|
61
65
|
end
|
@@ -68,7 +72,7 @@ module DataMapper
|
|
68
72
|
class Proxy
|
69
73
|
include Assertions
|
70
74
|
|
71
|
-
instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ class kind_of? respond_to? assert_kind_of should should_not ].include?(m) }
|
75
|
+
instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ class kind_of? respond_to? assert_kind_of should should_not instance_variable_set instance_variable_get ].include?(m) }
|
72
76
|
|
73
77
|
# FIXME: remove when RelationshipChain#get_children can return a Collection
|
74
78
|
def all(query = {})
|
@@ -87,6 +91,7 @@ module DataMapper
|
|
87
91
|
|
88
92
|
def <<(resource)
|
89
93
|
assert_mutable
|
94
|
+
return self if !resource.new_record? && self.include?(resource)
|
90
95
|
super
|
91
96
|
relate_resource(resource)
|
92
97
|
self
|
@@ -94,6 +99,7 @@ module DataMapper
|
|
94
99
|
|
95
100
|
def push(*resources)
|
96
101
|
assert_mutable
|
102
|
+
resources.reject { |resource| !resource.new_record? && self.include?(resource) }
|
97
103
|
super
|
98
104
|
resources.each { |resource| relate_resource(resource) }
|
99
105
|
self
|
@@ -101,6 +107,7 @@ module DataMapper
|
|
101
107
|
|
102
108
|
def unshift(*resources)
|
103
109
|
assert_mutable
|
110
|
+
resources.reject { |resource| !resource.new_record? && self.include?(resource) }
|
104
111
|
super
|
105
112
|
resources.each { |resource| relate_resource(resource) }
|
106
113
|
self
|
@@ -109,7 +116,7 @@ module DataMapper
|
|
109
116
|
def replace(other)
|
110
117
|
assert_mutable
|
111
118
|
each { |resource| orphan_resource(resource) }
|
112
|
-
other = other.map { |resource| Hash
|
119
|
+
other = other.map { |resource| resource.kind_of?(Hash) ? new_child(resource) : resource }
|
113
120
|
super
|
114
121
|
other.each { |resource| relate_resource(resource) }
|
115
122
|
self
|
@@ -142,10 +149,21 @@ module DataMapper
|
|
142
149
|
self
|
143
150
|
end
|
144
151
|
|
152
|
+
def build(attributes = {})
|
153
|
+
assert_mutable
|
154
|
+
attributes = default_attributes.merge(attributes)
|
155
|
+
resource = children.respond_to?(:build) ? super(attributes) : new_child(attributes)
|
156
|
+
self << resource
|
157
|
+
resource
|
158
|
+
end
|
159
|
+
|
145
160
|
def create(attributes = {})
|
146
161
|
assert_mutable
|
147
162
|
raise UnsavedParentError, 'You cannot create until the parent is saved' if @parent.new_record?
|
148
|
-
|
163
|
+
attributes = default_attributes.merge(attributes)
|
164
|
+
resource = children.respond_to?(:create) ? super(attributes) : @relationship.child_model.create(attributes)
|
165
|
+
self << resource
|
166
|
+
resource
|
149
167
|
end
|
150
168
|
|
151
169
|
def update(attributes = {})
|
@@ -229,15 +247,32 @@ module DataMapper
|
|
229
247
|
raise ImmutableAssociationError, 'You can not modify this assocation' if children.frozen?
|
230
248
|
end
|
231
249
|
|
232
|
-
def
|
233
|
-
|
250
|
+
def default_attributes
|
251
|
+
default_attributes = {}
|
234
252
|
|
235
253
|
@relationship.query.each do |attribute, value|
|
236
|
-
next if Query::OPTIONS.include?(attribute) || attribute.kind_of?(Query::Operator)
|
254
|
+
next if Query::OPTIONS.include?(attribute) || attribute.kind_of?(Query::Operator)
|
255
|
+
default_attributes[attribute] = value
|
256
|
+
end
|
257
|
+
|
258
|
+
@relationship.child_key.zip(@relationship.parent_key.get(@parent)) do |property,value|
|
259
|
+
default_attributes[property.name] = value
|
260
|
+
end
|
261
|
+
|
262
|
+
default_attributes
|
263
|
+
end
|
264
|
+
|
265
|
+
def add_default_association_values(resource)
|
266
|
+
default_attributes.each do |attribute, value|
|
267
|
+
next if !resource.respond_to?("#{attribute}=") || resource.attribute_loaded?(attribute)
|
237
268
|
resource.send("#{attribute}=", value)
|
238
269
|
end
|
239
270
|
end
|
240
271
|
|
272
|
+
def new_child(attributes)
|
273
|
+
@relationship.child_model.new(default_attributes.merge(attributes))
|
274
|
+
end
|
275
|
+
|
241
276
|
def relate_resource(resource)
|
242
277
|
assert_mutable
|
243
278
|
add_default_association_values(resource)
|
@@ -252,7 +287,7 @@ module DataMapper
|
|
252
287
|
end
|
253
288
|
|
254
289
|
def save_resource(resource, parent = @parent)
|
255
|
-
|
290
|
+
@relationship.with_repository(resource) do |r|
|
256
291
|
if parent.nil? && resource.model.respond_to?(:many_to_many)
|
257
292
|
resource.destroy
|
258
293
|
else
|
@@ -27,7 +27,7 @@ module DataMapper
|
|
27
27
|
def #{name}_association
|
28
28
|
@#{name}_association ||= begin
|
29
29
|
unless relationship = model.relationships(#{repository_name.inspect})[:#{name}]
|
30
|
-
raise ArgumentError,
|
30
|
+
raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
|
31
31
|
end
|
32
32
|
association = Associations::OneToMany::Proxy.new(relationship, self)
|
33
33
|
parent_associations << association
|
@@ -39,7 +39,7 @@ module DataMapper
|
|
39
39
|
model.relationships(repository_name)[name] = if options.has_key?(:through)
|
40
40
|
RelationshipChain.new(
|
41
41
|
:child_model => options.fetch(:class_name, Extlib::Inflection.classify(name)),
|
42
|
-
:parent_model => model
|
42
|
+
:parent_model => model,
|
43
43
|
:repository_name => repository_name,
|
44
44
|
:near_relationship_name => options[:through],
|
45
45
|
:remote_relationship_name => options.fetch(:remote_name, name),
|
@@ -48,10 +48,10 @@ module DataMapper
|
|
48
48
|
)
|
49
49
|
else
|
50
50
|
Relationship.new(
|
51
|
-
|
51
|
+
name,
|
52
52
|
repository_name,
|
53
53
|
options.fetch(:class_name, Extlib::Inflection.classify(name)),
|
54
|
-
model
|
54
|
+
model,
|
55
55
|
options
|
56
56
|
)
|
57
57
|
end
|
@@ -5,75 +5,166 @@ module DataMapper
|
|
5
5
|
|
6
6
|
OPTIONS = [ :class_name, :child_key, :parent_key, :min, :max, :through ]
|
7
7
|
|
8
|
-
|
8
|
+
# @api private
|
9
|
+
attr_reader :name, :options, :query
|
9
10
|
|
11
|
+
# @api private
|
10
12
|
def child_key
|
11
13
|
@child_key ||= begin
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
14
|
+
child_key = nil
|
15
|
+
child_model.repository.scope do |r|
|
16
|
+
model_properties = child_model.properties(r.name)
|
17
|
+
|
18
|
+
child_key = parent_key.zip(@child_properties || []).map do |parent_property,property_name|
|
19
|
+
# TODO: use something similar to DM::NamingConventions to determine the property name
|
20
|
+
parent_name = Extlib::Inflection.underscore(Extlib::Inflection.demodulize(parent_model.base_model.name))
|
21
|
+
property_name ||= "#{parent_name}_#{parent_property.name}".to_sym
|
22
|
+
|
23
|
+
if model_properties.has_property?(property_name)
|
24
|
+
model_properties[property_name]
|
25
|
+
else
|
26
|
+
options = {}
|
27
|
+
|
28
|
+
[ :length, :precision, :scale ].each do |option|
|
29
|
+
options[option] = parent_property.send(option)
|
30
|
+
end
|
31
|
+
|
32
|
+
# NOTE: hack to make each many to many child_key a true key,
|
33
|
+
# until I can figure out a better place for this check
|
34
|
+
if child_model.respond_to?(:many_to_many)
|
35
|
+
options[:key] = true
|
36
|
+
end
|
37
|
+
|
38
|
+
child_model.property(property_name, parent_property.primitive, options)
|
29
39
|
end
|
30
|
-
|
31
|
-
child_model.property(property_name, parent_property.primitive, attributes)
|
32
40
|
end
|
33
41
|
end
|
34
42
|
PropertySet.new(child_key)
|
35
43
|
end
|
36
44
|
end
|
37
45
|
|
46
|
+
# @api private
|
38
47
|
def parent_key
|
39
48
|
@parent_key ||= begin
|
40
|
-
parent_key =
|
41
|
-
|
42
|
-
|
43
|
-
|
49
|
+
parent_key = nil
|
50
|
+
parent_model.repository.scope do |r|
|
51
|
+
parent_key = if @parent_properties
|
52
|
+
parent_model.properties(r.name).slice(*@parent_properties)
|
53
|
+
else
|
54
|
+
parent_model.key
|
55
|
+
end
|
44
56
|
end
|
45
|
-
|
46
57
|
PropertySet.new(parent_key)
|
47
58
|
end
|
48
59
|
end
|
49
60
|
|
61
|
+
# @api private
|
50
62
|
def parent_model
|
51
|
-
Class === @parent_model ? @parent_model :
|
63
|
+
Class === @parent_model ? @parent_model : (Class === @child_model ? @child_model.find_const(@parent_model) : Object.find_const(@parent_model))
|
64
|
+
rescue NameError
|
65
|
+
raise NameError, "Cannot find the parent_model #{@parent_model} for #{@child_model}"
|
52
66
|
end
|
53
67
|
|
68
|
+
# @api private
|
54
69
|
def child_model
|
55
|
-
Class === @child_model ? @child_model :
|
70
|
+
Class === @child_model ? @child_model : (Class === @parent_model ? @parent_model.find_const(@child_model) : Object.find_const(@child_model))
|
71
|
+
rescue NameError
|
72
|
+
raise NameError, "Cannot find the child_model #{@child_model} for #{@parent_model}"
|
56
73
|
end
|
57
74
|
|
58
75
|
# @api private
|
59
76
|
def get_children(parent, options = {}, finder = :all, *args)
|
60
|
-
bind_values
|
61
|
-
|
62
|
-
|
77
|
+
bind_values = parent_key.get(parent)
|
78
|
+
parent_values = bind_values.dup
|
79
|
+
|
80
|
+
with_repository(child_model) do |r|
|
81
|
+
parent_identity_map = parent.repository.identity_map(parent_model)
|
82
|
+
child_identity_map = r.identity_map(child_model)
|
83
|
+
|
84
|
+
query_values = parent_identity_map.keys.flatten
|
85
|
+
query_values.reject! { |k| child_identity_map[[k]] }
|
86
|
+
|
87
|
+
bind_values = query_values unless query_values.empty?
|
88
|
+
query = child_key.map { |k| [ k, bind_values ] }.to_hash
|
89
|
+
|
90
|
+
collection = child_model.send(finder, *(args.dup << @query.merge(options).merge(query)))
|
91
|
+
return collection unless collection.kind_of?(Collection) && collection.any?
|
92
|
+
|
93
|
+
grouped_collection = Hash.new { |h,k| h[k] = [] }
|
94
|
+
collection.each do |resource|
|
95
|
+
grouped_collection[get_parent(resource, parent)] << resource
|
96
|
+
end
|
97
|
+
|
98
|
+
association_accessor = "#{self.name}_association"
|
99
|
+
|
100
|
+
ret = nil
|
101
|
+
grouped_collection.each do |parent, children|
|
102
|
+
association = parent.send(association_accessor)
|
103
|
+
|
104
|
+
query = collection.query.dup
|
105
|
+
query.conditions.map! do |operator, property, bind_value|
|
106
|
+
if child_key.has_property?(property.name)
|
107
|
+
bind_value = *children.map { |child| property.get(child) }.uniq
|
108
|
+
end
|
109
|
+
[ operator, property, bind_value ]
|
110
|
+
end
|
111
|
+
|
112
|
+
parents_children = Collection.new(query) do |collection|
|
113
|
+
children.each { |child| collection.send(:add, child) }
|
114
|
+
end
|
115
|
+
|
116
|
+
if parent_key.get(parent) == parent_values
|
117
|
+
ret = parents_children
|
118
|
+
else
|
119
|
+
association.instance_variable_set(:@children, parents_children)
|
120
|
+
end
|
121
|
+
end
|
63
122
|
|
64
|
-
|
65
|
-
child_model.send(finder, *(args << @query.merge(options).merge(query)))
|
123
|
+
ret || child_model.send(finder, *(args.dup << @query.merge(options).merge(child_key.zip(parent_values).to_hash)))
|
66
124
|
end
|
67
125
|
end
|
68
126
|
|
69
127
|
# @api private
|
70
|
-
def get_parent(child)
|
71
|
-
|
72
|
-
return nil
|
73
|
-
|
128
|
+
def get_parent(child, parent = nil)
|
129
|
+
child_value = child_key.get(child)
|
130
|
+
return nil unless child_value.nitems == child_value.size
|
131
|
+
|
132
|
+
with_repository(parent || parent_model) do
|
133
|
+
parent_identity_map = (parent || parent_model).repository.identity_map(parent_model)
|
134
|
+
child_identity_map = child.repository.identity_map(child_model)
|
135
|
+
|
136
|
+
if parent = parent_identity_map[child_value]
|
137
|
+
return parent
|
138
|
+
end
|
139
|
+
|
140
|
+
children = child_identity_map.values
|
141
|
+
|
142
|
+
bind_values = *children.map { |c| child_key.get(c) }.uniq
|
143
|
+
query_values = bind_values.reject { |k| parent_identity_map[[k]] }
|
144
|
+
|
145
|
+
bind_values = query_values unless query_values.empty?
|
146
|
+
query = parent_key.map { |k| [ k, bind_values ] }.to_hash
|
74
147
|
|
75
|
-
|
76
|
-
|
148
|
+
association_accessor = "#{self.name}_association"
|
149
|
+
|
150
|
+
collection = parent_model.send(:all, query)
|
151
|
+
collection.send(:lazy_load)
|
152
|
+
children.each do |c|
|
153
|
+
c.send(association_accessor).instance_variable_set(:@parent, collection.get(*child_key.get(c)))
|
154
|
+
end
|
155
|
+
child.send(association_accessor).instance_variable_get(:@parent)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# @api private
|
160
|
+
def with_repository(object = nil, &block)
|
161
|
+
other_model = object.model == child_model ? parent_model : child_model if object.respond_to?(:model)
|
162
|
+
other_model = object == child_model ? parent_model : child_model if object.kind_of?(DataMapper::Resource)
|
163
|
+
|
164
|
+
if other_model && other_model.repository == object.repository && object.repository.name != @repository_name
|
165
|
+
object.repository.scope(&block)
|
166
|
+
else
|
167
|
+
repository(@repository_name, &block)
|
77
168
|
end
|
78
169
|
end
|
79
170
|
|
@@ -89,10 +180,10 @@ module DataMapper
|
|
89
180
|
# http://edocs.bea.com/kodo/docs41/full/html/jdo_overview_mapping_join.html
|
90
181
|
# I wash my hands of it!
|
91
182
|
def initialize(name, repository_name, child_model, parent_model, options = {})
|
92
|
-
assert_kind_of 'name',
|
93
|
-
assert_kind_of 'repository_name',
|
94
|
-
assert_kind_of 'child_model',
|
95
|
-
assert_kind_of 'parent_model',
|
183
|
+
assert_kind_of 'name', name, Symbol
|
184
|
+
assert_kind_of 'repository_name', repository_name, Symbol
|
185
|
+
assert_kind_of 'child_model', child_model, String, Class
|
186
|
+
assert_kind_of 'parent_model', parent_model, String, Class
|
96
187
|
|
97
188
|
if child_properties = options[:child_key]
|
98
189
|
assert_kind_of 'options[:child_key]', child_properties, Array
|
@@ -110,6 +201,17 @@ module DataMapper
|
|
110
201
|
@parent_model = parent_model
|
111
202
|
@parent_properties = parent_properties # may be nil
|
112
203
|
@options = options
|
204
|
+
|
205
|
+
# attempt to load the child_key if the parent and child model constants are defined
|
206
|
+
if model_defined?(@child_model) && model_defined?(@parent_model)
|
207
|
+
child_key
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# @api private
|
212
|
+
def model_defined?(model)
|
213
|
+
# TODO: figure out other ways to see if the model is loaded
|
214
|
+
model.kind_of?(Class)
|
113
215
|
end
|
114
216
|
end # class Relationship
|
115
217
|
end # module Associations
|