dm-core 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|