datamapper-dm-core 0.9.11 → 0.10.0
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 +17 -14
- data/.gitignore +3 -1
- data/FAQ +6 -5
- data/History.txt +5 -39
- data/Manifest.txt +67 -76
- data/QUICKLINKS +1 -1
- data/README.txt +21 -15
- data/Rakefile +16 -15
- data/SPECS +2 -29
- data/TODO +1 -1
- data/dm-core.gemspec +11 -15
- data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
- data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
- data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
- data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
- data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
- data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
- data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
- data/lib/dm-core/adapters.rb +135 -16
- data/lib/dm-core/associations/many_to_many.rb +372 -90
- data/lib/dm-core/associations/many_to_one.rb +220 -73
- data/lib/dm-core/associations/one_to_many.rb +319 -255
- data/lib/dm-core/associations/one_to_one.rb +66 -53
- data/lib/dm-core/associations/relationship.rb +560 -158
- data/lib/dm-core/collection.rb +1104 -381
- data/lib/dm-core/core_ext/kernel.rb +12 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +4 -34
- data/lib/dm-core/migrations.rb +1283 -0
- data/lib/dm-core/model/descendant_set.rb +81 -0
- data/lib/dm-core/model/hook.rb +45 -0
- data/lib/dm-core/model/is.rb +32 -0
- data/lib/dm-core/model/property.rb +248 -0
- data/lib/dm-core/model/relationship.rb +335 -0
- data/lib/dm-core/model/scope.rb +90 -0
- data/lib/dm-core/model.rb +570 -369
- data/lib/dm-core/property.rb +753 -280
- data/lib/dm-core/property_set.rb +141 -98
- data/lib/dm-core/query/conditions/comparison.rb +814 -0
- data/lib/dm-core/query/conditions/operation.rb +247 -0
- data/lib/dm-core/query/direction.rb +43 -0
- data/lib/dm-core/query/operator.rb +42 -0
- data/lib/dm-core/query/path.rb +102 -0
- data/lib/dm-core/query/sort.rb +45 -0
- data/lib/dm-core/query.rb +974 -492
- data/lib/dm-core/repository.rb +147 -107
- data/lib/dm-core/resource.rb +644 -429
- data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
- data/lib/dm-core/support/chainable.rb +20 -0
- data/lib/dm-core/support/deprecate.rb +12 -0
- data/lib/dm-core/support/equalizer.rb +23 -0
- data/lib/dm-core/support/logger.rb +13 -0
- data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
- data/lib/dm-core/transaction.rb +333 -92
- data/lib/dm-core/type.rb +98 -60
- data/lib/dm-core/types/boolean.rb +1 -1
- data/lib/dm-core/types/discriminator.rb +34 -20
- data/lib/dm-core/types/object.rb +7 -4
- data/lib/dm-core/types/paranoid_boolean.rb +11 -9
- data/lib/dm-core/types/paranoid_datetime.rb +11 -9
- data/lib/dm-core/types/serial.rb +3 -3
- data/lib/dm-core/types/text.rb +3 -4
- data/lib/dm-core/version.rb +1 -1
- data/lib/dm-core.rb +106 -110
- data/script/performance.rb +102 -109
- data/script/profile.rb +169 -38
- data/spec/lib/adapter_helpers.rb +105 -0
- data/spec/lib/collection_helpers.rb +18 -0
- data/spec/lib/counter_adapter.rb +34 -0
- data/spec/lib/pending_helpers.rb +27 -0
- data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
- data/spec/public/associations/many_to_many_spec.rb +193 -0
- data/spec/public/associations/many_to_one_spec.rb +73 -0
- data/spec/public/associations/one_to_many_spec.rb +77 -0
- data/spec/public/associations/one_to_one_spec.rb +156 -0
- data/spec/public/collection_spec.rb +65 -0
- data/spec/public/model/relationship_spec.rb +924 -0
- data/spec/public/model_spec.rb +159 -0
- data/spec/public/property_spec.rb +829 -0
- data/spec/public/resource_spec.rb +71 -0
- data/spec/public/sel_spec.rb +44 -0
- data/spec/public/setup_spec.rb +145 -0
- data/spec/public/shared/association_collection_shared_spec.rb +317 -0
- data/spec/public/shared/collection_shared_spec.rb +1723 -0
- data/spec/public/shared/finder_shared_spec.rb +1619 -0
- data/spec/public/shared/resource_shared_spec.rb +924 -0
- data/spec/public/shared/sel_shared_spec.rb +112 -0
- data/spec/public/transaction_spec.rb +129 -0
- data/spec/public/types/discriminator_spec.rb +130 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
- data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
- data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
- data/spec/semipublic/associations/relationship_spec.rb +194 -0
- data/spec/semipublic/associations_spec.rb +177 -0
- data/spec/semipublic/collection_spec.rb +142 -0
- data/spec/semipublic/property_spec.rb +61 -0
- data/spec/semipublic/query/conditions_spec.rb +528 -0
- data/spec/semipublic/query/path_spec.rb +443 -0
- data/spec/semipublic/query_spec.rb +2626 -0
- data/spec/semipublic/resource_spec.rb +47 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
- data/spec/spec.opts +3 -1
- data/spec/spec_helper.rb +80 -57
- data/tasks/ci.rb +19 -31
- data/tasks/dm.rb +43 -48
- data/tasks/doc.rb +8 -11
- data/tasks/gemspec.rb +5 -5
- data/tasks/hoe.rb +15 -16
- data/tasks/install.rb +8 -10
- metadata +72 -93
- data/lib/dm-core/associations/relationship_chain.rb +0 -81
- data/lib/dm-core/associations.rb +0 -207
- data/lib/dm-core/auto_migrations.rb +0 -105
- data/lib/dm-core/dependency_queue.rb +0 -32
- data/lib/dm-core/hook.rb +0 -11
- data/lib/dm-core/is.rb +0 -16
- data/lib/dm-core/logger.rb +0 -232
- data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
- data/lib/dm-core/migrator.rb +0 -29
- data/lib/dm-core/scope.rb +0 -58
- data/lib/dm-core/support/array.rb +0 -13
- data/lib/dm-core/support/assertions.rb +0 -8
- data/lib/dm-core/support/errors.rb +0 -23
- data/lib/dm-core/support/kernel.rb +0 -11
- data/lib/dm-core/support/symbol.rb +0 -41
- data/lib/dm-core/support.rb +0 -7
- data/lib/dm-core/type_map.rb +0 -80
- data/lib/dm-core/types.rb +0 -19
- data/script/all +0 -4
- data/spec/integration/association_spec.rb +0 -1382
- data/spec/integration/association_through_spec.rb +0 -203
- data/spec/integration/associations/many_to_many_spec.rb +0 -449
- data/spec/integration/associations/many_to_one_spec.rb +0 -163
- data/spec/integration/associations/one_to_many_spec.rb +0 -188
- data/spec/integration/auto_migrations_spec.rb +0 -413
- data/spec/integration/collection_spec.rb +0 -1073
- data/spec/integration/data_objects_adapter_spec.rb +0 -32
- data/spec/integration/dependency_queue_spec.rb +0 -46
- data/spec/integration/model_spec.rb +0 -197
- data/spec/integration/mysql_adapter_spec.rb +0 -85
- data/spec/integration/postgres_adapter_spec.rb +0 -731
- data/spec/integration/property_spec.rb +0 -253
- data/spec/integration/query_spec.rb +0 -514
- data/spec/integration/repository_spec.rb +0 -61
- data/spec/integration/resource_spec.rb +0 -513
- data/spec/integration/sqlite3_adapter_spec.rb +0 -352
- data/spec/integration/sti_spec.rb +0 -273
- data/spec/integration/strategic_eager_loading_spec.rb +0 -156
- data/spec/integration/transaction_spec.rb +0 -75
- data/spec/integration/type_spec.rb +0 -275
- data/spec/lib/logging_helper.rb +0 -18
- data/spec/lib/mock_adapter.rb +0 -27
- data/spec/lib/model_loader.rb +0 -100
- data/spec/lib/publicize_methods.rb +0 -28
- data/spec/models/content.rb +0 -16
- data/spec/models/vehicles.rb +0 -34
- data/spec/models/zoo.rb +0 -48
- data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
- data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
- data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
- data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
- data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
- data/spec/unit/associations/many_to_many_spec.rb +0 -32
- data/spec/unit/associations/many_to_one_spec.rb +0 -159
- data/spec/unit/associations/one_to_many_spec.rb +0 -393
- data/spec/unit/associations/one_to_one_spec.rb +0 -7
- data/spec/unit/associations/relationship_spec.rb +0 -71
- data/spec/unit/associations_spec.rb +0 -242
- data/spec/unit/auto_migrations_spec.rb +0 -111
- data/spec/unit/collection_spec.rb +0 -182
- data/spec/unit/data_mapper_spec.rb +0 -35
- data/spec/unit/identity_map_spec.rb +0 -126
- data/spec/unit/is_spec.rb +0 -80
- data/spec/unit/migrator_spec.rb +0 -33
- data/spec/unit/model_spec.rb +0 -321
- data/spec/unit/naming_conventions_spec.rb +0 -36
- data/spec/unit/property_set_spec.rb +0 -90
- data/spec/unit/property_spec.rb +0 -753
- data/spec/unit/query_spec.rb +0 -571
- data/spec/unit/repository_spec.rb +0 -93
- data/spec/unit/resource_spec.rb +0 -649
- data/spec/unit/scope_spec.rb +0 -142
- data/spec/unit/transaction_spec.rb +0 -493
- data/spec/unit/type_map_spec.rb +0 -114
- data/spec/unit/type_spec.rb +0 -119
|
@@ -1,147 +1,429 @@
|
|
|
1
|
-
require File.join(File.dirname(__FILE__), "one_to_many")
|
|
2
1
|
module DataMapper
|
|
3
2
|
module Associations
|
|
4
|
-
module ManyToMany
|
|
5
|
-
|
|
3
|
+
module ManyToMany #:nodoc:
|
|
4
|
+
class Relationship < Associations::OneToMany::Relationship
|
|
5
|
+
extend Chainable
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
# -
|
|
9
|
-
# @api private
|
|
10
|
-
def self.setup(name, model, options = {})
|
|
11
|
-
assert_kind_of 'name', name, Symbol
|
|
12
|
-
assert_kind_of 'model', model, Model
|
|
13
|
-
assert_kind_of 'options', options, Hash
|
|
7
|
+
OPTIONS = superclass::OPTIONS.dup << :through << :via
|
|
14
8
|
|
|
15
|
-
|
|
9
|
+
# Returns a set of keys that identify the target model
|
|
10
|
+
#
|
|
11
|
+
# @return [DataMapper::PropertySet]
|
|
12
|
+
# a set of properties that identify the target model
|
|
13
|
+
#
|
|
14
|
+
# @api semipublic
|
|
15
|
+
def child_key
|
|
16
|
+
return @child_key if defined?(@child_key)
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
repository_name = child_repository_name || parent_repository_name
|
|
19
|
+
properties = child_model.properties(repository_name)
|
|
20
|
+
|
|
21
|
+
@child_key = if @child_properties
|
|
22
|
+
child_key = properties.values_at(*@child_properties)
|
|
23
|
+
properties.class.new(child_key).freeze
|
|
24
|
+
else
|
|
25
|
+
properties.key
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# TODO: document
|
|
30
|
+
# @api semipublic
|
|
31
|
+
alias target_key child_key
|
|
32
|
+
|
|
33
|
+
# Intermediate association for through model
|
|
34
|
+
# relationships
|
|
35
|
+
#
|
|
36
|
+
# Example: for :bugs association in
|
|
37
|
+
#
|
|
38
|
+
# class Software::Engineer
|
|
39
|
+
# include DataMapper::Resource
|
|
40
|
+
#
|
|
41
|
+
# has n, :missing_tests
|
|
42
|
+
# has n, :bugs, :through => :missing_tests
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# through is :missing_tests
|
|
46
|
+
#
|
|
47
|
+
# TODO: document a case when
|
|
48
|
+
# through option is a model and
|
|
49
|
+
# not an association name
|
|
50
|
+
#
|
|
51
|
+
# @api semipublic
|
|
52
|
+
def through
|
|
53
|
+
return @through if defined?(@through)
|
|
54
|
+
|
|
55
|
+
if options[:through].kind_of?(Associations::Relationship)
|
|
56
|
+
return @through = options[:through]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
repository_name = source_repository_name
|
|
60
|
+
relationships = source_model.relationships(repository_name)
|
|
61
|
+
name = through_relationship_name
|
|
62
|
+
|
|
63
|
+
@through = relationships[name] ||
|
|
64
|
+
DataMapper.repository(repository_name) do
|
|
65
|
+
source_model.has(min..max, name, through_model, one_to_many_options)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
@through.child_key
|
|
69
|
+
|
|
70
|
+
@through
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# TODO: document
|
|
74
|
+
# @api semipublic
|
|
75
|
+
def via
|
|
76
|
+
return @via if defined?(@via)
|
|
77
|
+
|
|
78
|
+
if options[:via].kind_of?(Associations::Relationship)
|
|
79
|
+
return @via = options[:via]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
repository_name = through.relative_target_repository_name
|
|
83
|
+
through_model = through.target_model
|
|
84
|
+
relationships = through_model.relationships(repository_name)
|
|
85
|
+
singular_name = name.to_s.singularize.to_sym
|
|
86
|
+
|
|
87
|
+
@via = relationships[options[:via]] ||
|
|
88
|
+
relationships[name] ||
|
|
89
|
+
relationships[singular_name]
|
|
90
|
+
|
|
91
|
+
@via ||= if anonymous_through_model?
|
|
92
|
+
DataMapper.repository(repository_name) do
|
|
93
|
+
through_model.belongs_to(singular_name, target_model, many_to_one_options)
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
raise UnknownRelationshipError, "No relationships named #{name} or #{singular_name} in #{through_model}"
|
|
20
97
|
end
|
|
21
98
|
|
|
22
|
-
|
|
23
|
-
|
|
99
|
+
@via.child_key
|
|
100
|
+
|
|
101
|
+
@via
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# TODO: document
|
|
105
|
+
# @api semipublic
|
|
106
|
+
def links
|
|
107
|
+
return @links if defined?(@links)
|
|
108
|
+
|
|
109
|
+
@links = []
|
|
110
|
+
links = [ through, via ]
|
|
111
|
+
|
|
112
|
+
while relationship = links.shift
|
|
113
|
+
if relationship.respond_to?(:links)
|
|
114
|
+
links.unshift(*relationship.links)
|
|
115
|
+
else
|
|
116
|
+
@links << relationship
|
|
117
|
+
end
|
|
24
118
|
end
|
|
25
119
|
|
|
26
|
-
|
|
120
|
+
@links.freeze
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# TODO: document
|
|
124
|
+
# @api private
|
|
125
|
+
def source_scope(source)
|
|
126
|
+
{ through.inverse => source }
|
|
127
|
+
end
|
|
27
128
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
129
|
+
# TODO: document
|
|
130
|
+
# @api private
|
|
131
|
+
def query
|
|
132
|
+
# TODO: consider making this a query_for method, so that ManyToMany::Relationship#query only
|
|
133
|
+
# returns the query supplied in the definition
|
|
134
|
+
@many_to_many_query ||= super.merge(:links => links).freeze
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Eager load the collection using the source as a base
|
|
138
|
+
#
|
|
139
|
+
# @param [Resource, Collection] source
|
|
140
|
+
# the source to query with
|
|
141
|
+
# @param [Query, Hash] other_query
|
|
142
|
+
# optional query to restrict the collection
|
|
143
|
+
#
|
|
144
|
+
# @return [ManyToMany::Collection]
|
|
145
|
+
# the loaded collection for the source
|
|
146
|
+
#
|
|
147
|
+
# @api private
|
|
148
|
+
def eager_load(source, other_query = nil)
|
|
149
|
+
# FIXME: enable SEL for m:m relationships
|
|
150
|
+
source.model.all(query_for(source, other_query))
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
# TODO: document
|
|
156
|
+
# @api private
|
|
157
|
+
def through_model
|
|
158
|
+
namespace, name = through_model_namespace_name
|
|
159
|
+
|
|
160
|
+
if namespace.const_defined?(name)
|
|
161
|
+
namespace.const_get(name)
|
|
162
|
+
else
|
|
163
|
+
model = Model.new do
|
|
164
|
+
# all properties added to the anonymous through model are keys by default
|
|
165
|
+
def property(name, type, options = {})
|
|
166
|
+
options[:key] = true unless options.key?(:key)
|
|
167
|
+
options.delete(:index)
|
|
168
|
+
super
|
|
32
169
|
end
|
|
33
|
-
association = Proxy.new(relationship, self)
|
|
34
|
-
parent_associations << association
|
|
35
|
-
association
|
|
36
170
|
end
|
|
171
|
+
|
|
172
|
+
namespace.const_set(name, model)
|
|
37
173
|
end
|
|
38
|
-
|
|
174
|
+
end
|
|
39
175
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
opts[:remote_relationship_name] ||= opts.delete(:remote_name) || Extlib::Inflection.tableize(opts[:child_model])
|
|
46
|
-
opts[:parent_key] = opts[:parent_key]
|
|
47
|
-
opts[:child_key] = opts[:child_key]
|
|
48
|
-
opts[:mutable] = true
|
|
176
|
+
# TODO: document
|
|
177
|
+
# @api private
|
|
178
|
+
def through_model_namespace_name
|
|
179
|
+
target_parts = target_model.base_model.name.split('::')
|
|
180
|
+
source_parts = source_model.base_model.name.split('::')
|
|
49
181
|
|
|
50
|
-
|
|
51
|
-
model_name = names.join.gsub("::", "")
|
|
52
|
-
storage_name = Extlib::Inflection.tableize(Extlib::Inflection.pluralize(names[0]) + names[1])
|
|
182
|
+
name = [ target_parts.pop, source_parts.pop ].sort.join
|
|
53
183
|
|
|
54
|
-
|
|
184
|
+
namespace = Object
|
|
55
185
|
|
|
56
|
-
|
|
186
|
+
# find the common namespace between the target_model and source_model
|
|
187
|
+
target_parts.zip(source_parts) do |target_part, source_part|
|
|
188
|
+
break if target_part != source_part
|
|
189
|
+
namespace = namespace.const_get(target_part)
|
|
190
|
+
end
|
|
57
191
|
|
|
58
|
-
|
|
192
|
+
return namespace, name
|
|
193
|
+
end
|
|
59
194
|
|
|
60
|
-
|
|
61
|
-
|
|
195
|
+
# TODO: document
|
|
196
|
+
# @api private
|
|
197
|
+
def through_relationship_name
|
|
198
|
+
if anonymous_through_model?
|
|
199
|
+
namespace = through_model_namespace_name.first
|
|
200
|
+
relationship_name = Extlib::Inflection.underscore(through_model.name.sub(/\A#{namespace.name}::/, '')).tr('/', '_')
|
|
201
|
+
relationship_name.pluralize.to_sym
|
|
202
|
+
else
|
|
203
|
+
options[:through]
|
|
204
|
+
end
|
|
205
|
+
end
|
|
62
206
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
207
|
+
# Check if the :through association uses an anonymous model
|
|
208
|
+
#
|
|
209
|
+
# An anonymous model means that DataMapper creates the model
|
|
210
|
+
# in-memory, and sets the relationships to join the source
|
|
211
|
+
# and the target model.
|
|
212
|
+
#
|
|
213
|
+
# @return [Boolean]
|
|
214
|
+
# true if the through model is anonymous
|
|
215
|
+
#
|
|
216
|
+
# @api private
|
|
217
|
+
def anonymous_through_model?
|
|
218
|
+
options[:through] == Resource
|
|
219
|
+
end
|
|
68
220
|
|
|
69
|
-
|
|
70
|
-
|
|
221
|
+
# TODO: document
|
|
222
|
+
# @api semipublic
|
|
223
|
+
chainable do
|
|
224
|
+
def many_to_one_options
|
|
225
|
+
{ :parent_key => target_key.map { |property| property.name } }
|
|
71
226
|
end
|
|
227
|
+
end
|
|
72
228
|
|
|
73
|
-
|
|
229
|
+
# TODO: document
|
|
230
|
+
# @api semipublic
|
|
231
|
+
chainable do
|
|
232
|
+
def one_to_many_options
|
|
233
|
+
{ :parent_key => source_key.map { |property| property.name } }
|
|
234
|
+
end
|
|
74
235
|
end
|
|
75
236
|
|
|
76
|
-
relationship
|
|
77
|
-
|
|
237
|
+
# Returns the inverse relationship class
|
|
238
|
+
#
|
|
239
|
+
# @api private
|
|
240
|
+
def inverse_class
|
|
241
|
+
self.class
|
|
242
|
+
end
|
|
78
243
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
orphan_resource(super)
|
|
244
|
+
# TODO: document
|
|
245
|
+
# @api private
|
|
246
|
+
def invert
|
|
247
|
+
inverse_class.new(inverse_name, parent_model, child_model, inverted_options)
|
|
84
248
|
end
|
|
85
249
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
250
|
+
# TODO: document
|
|
251
|
+
# @api private
|
|
252
|
+
def inverted_options
|
|
253
|
+
links = self.links.dup
|
|
254
|
+
through = links.pop.inverse
|
|
255
|
+
|
|
256
|
+
links.reverse_each do |relationship|
|
|
257
|
+
inverse = relationship.inverse
|
|
258
|
+
|
|
259
|
+
through = self.class.new(
|
|
260
|
+
inverse.name,
|
|
261
|
+
inverse.child_model,
|
|
262
|
+
inverse.parent_model,
|
|
263
|
+
inverse.options.merge(:through => through)
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
options.only(*OPTIONS - [ :min, :max ]).update(
|
|
268
|
+
:through => through,
|
|
269
|
+
:child_key => options[:parent_key],
|
|
270
|
+
:parent_key => options[:child_key],
|
|
271
|
+
:inverse => self
|
|
272
|
+
)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Loads association targets and sets resulting value on
|
|
276
|
+
# given source resource
|
|
277
|
+
#
|
|
278
|
+
# @param [Resource] source
|
|
279
|
+
# the source resource for the association
|
|
280
|
+
#
|
|
281
|
+
# @return [undefined]
|
|
282
|
+
#
|
|
283
|
+
# @api private
|
|
284
|
+
def lazy_load(source)
|
|
285
|
+
# FIXME: delegate to super once SEL is enabled
|
|
286
|
+
set!(source, collection_for(source))
|
|
89
287
|
end
|
|
90
288
|
|
|
289
|
+
# Returns collection class used by this type of
|
|
290
|
+
# relationship
|
|
291
|
+
#
|
|
292
|
+
# @api private
|
|
293
|
+
def collection_class
|
|
294
|
+
ManyToMany::Collection
|
|
295
|
+
end
|
|
296
|
+
end # class Relationship
|
|
297
|
+
|
|
298
|
+
class Collection < Associations::OneToMany::Collection
|
|
299
|
+
# Remove every Resource in the m:m Collection from the repository
|
|
300
|
+
#
|
|
301
|
+
# This performs a deletion of each Resource in the Collection from
|
|
302
|
+
# the repository and clears the Collection.
|
|
303
|
+
#
|
|
304
|
+
# @return [Boolean]
|
|
305
|
+
# true if the resources were successfully destroyed
|
|
306
|
+
#
|
|
307
|
+
# @api public
|
|
91
308
|
def destroy
|
|
92
|
-
|
|
309
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
310
|
+
|
|
311
|
+
# make sure the records are loaded so they can be found when
|
|
312
|
+
# the intermediaries are removed
|
|
313
|
+
lazy_load
|
|
314
|
+
|
|
315
|
+
unless intermediaries.destroy
|
|
316
|
+
return false
|
|
317
|
+
end
|
|
318
|
+
|
|
93
319
|
super
|
|
94
320
|
end
|
|
95
321
|
|
|
96
|
-
|
|
322
|
+
# Remove every Resource in the m:m Collection from the repository, bypassing validation
|
|
323
|
+
#
|
|
324
|
+
# This performs a deletion of each Resource in the Collection from
|
|
325
|
+
# the repository and clears the Collection while skipping
|
|
326
|
+
# validation.
|
|
327
|
+
#
|
|
328
|
+
# @return [Boolean]
|
|
329
|
+
# true if the resources were successfully destroyed
|
|
330
|
+
#
|
|
331
|
+
# @api public
|
|
332
|
+
def destroy!
|
|
333
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
334
|
+
|
|
335
|
+
# make sure the records are loaded so they can be found when
|
|
336
|
+
# the intermediaries are removed
|
|
337
|
+
lazy_load
|
|
338
|
+
|
|
339
|
+
unless intermediaries.destroy!
|
|
340
|
+
return false
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
super
|
|
97
344
|
end
|
|
98
345
|
|
|
99
346
|
private
|
|
100
347
|
|
|
101
|
-
|
|
102
|
-
|
|
348
|
+
# TODO: document
|
|
349
|
+
# @api private
|
|
350
|
+
def _create(safe, attributes)
|
|
351
|
+
if via.respond_to?(:resource_for)
|
|
352
|
+
resource = super
|
|
353
|
+
if create_intermediary(safe, via => resource)
|
|
354
|
+
resource
|
|
355
|
+
end
|
|
356
|
+
else
|
|
357
|
+
if intermediary = create_intermediary(safe)
|
|
358
|
+
super(safe, attributes.merge(via.inverse => intermediary))
|
|
359
|
+
end
|
|
360
|
+
end
|
|
103
361
|
end
|
|
104
362
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
363
|
+
# TODO: document
|
|
364
|
+
# @api private
|
|
365
|
+
def _save(safe)
|
|
366
|
+
# delete only intermediaries linked to the removed targets
|
|
367
|
+
unless @removed.empty? || intermediaries(@removed).send(safe ? :destroy : :destroy!)
|
|
368
|
+
return false
|
|
369
|
+
end
|
|
109
370
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
371
|
+
if via.respond_to?(:resource_for)
|
|
372
|
+
super
|
|
373
|
+
loaded_entries.all? { |resource| create_intermediary(safe, via => resource) }
|
|
374
|
+
else
|
|
375
|
+
if intermediary = create_intermediary(safe)
|
|
376
|
+
inverse = via.inverse
|
|
377
|
+
loaded_entries.map { |resource| inverse.set(resource, intermediary) }
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
super
|
|
115
381
|
end
|
|
116
|
-
|
|
117
|
-
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# TODO: document
|
|
385
|
+
# @api private
|
|
386
|
+
def intermediaries(targets = self)
|
|
387
|
+
intermediaries = if through.loaded?(source)
|
|
388
|
+
through.get!(source)
|
|
389
|
+
else
|
|
390
|
+
through.set!(source, through.collection_for(source))
|
|
118
391
|
end
|
|
119
|
-
near_association << through_resource
|
|
120
392
|
|
|
121
|
-
|
|
393
|
+
intermediaries.all(via => targets)
|
|
122
394
|
end
|
|
123
395
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
396
|
+
# TODO: document
|
|
397
|
+
# @api private
|
|
398
|
+
def create_intermediary(safe, attributes = {})
|
|
399
|
+
collection = intermediaries
|
|
400
|
+
|
|
401
|
+
return unless collection.send(safe ? :save : :save!)
|
|
402
|
+
|
|
403
|
+
intermediary = collection.first(attributes) ||
|
|
404
|
+
collection.send(safe ? :create : :create!, attributes)
|
|
129
405
|
|
|
130
|
-
|
|
406
|
+
return intermediary if intermediary.saved?
|
|
131
407
|
end
|
|
132
408
|
|
|
133
|
-
|
|
134
|
-
|
|
409
|
+
# TODO: document
|
|
410
|
+
# @api private
|
|
411
|
+
def through
|
|
412
|
+
relationship.through
|
|
135
413
|
end
|
|
136
414
|
|
|
137
|
-
|
|
138
|
-
|
|
415
|
+
# TODO: document
|
|
416
|
+
# @api private
|
|
417
|
+
def via
|
|
418
|
+
relationship.via
|
|
139
419
|
end
|
|
140
420
|
|
|
141
|
-
|
|
142
|
-
|
|
421
|
+
# TODO: document
|
|
422
|
+
# @api private
|
|
423
|
+
def inverse_set(*)
|
|
424
|
+
# do nothing
|
|
143
425
|
end
|
|
144
|
-
end # class
|
|
426
|
+
end # class Collection
|
|
145
427
|
end # module ManyToMany
|
|
146
428
|
end # module Associations
|
|
147
429
|
end # module DataMapper
|