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,315 +1,379 @@
|
|
|
1
1
|
module DataMapper
|
|
2
2
|
module Associations
|
|
3
|
-
module OneToMany
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
3
|
+
module OneToMany #:nodoc:
|
|
4
|
+
class Relationship < Associations::Relationship
|
|
5
|
+
# TODO: document
|
|
6
|
+
# @api semipublic
|
|
7
|
+
alias target_repository_name child_repository_name
|
|
20
8
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
# TODO: document
|
|
10
|
+
# @api semipublic
|
|
11
|
+
alias target_model child_model
|
|
24
12
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
13
|
+
# TODO: document
|
|
14
|
+
# @api semipublic
|
|
15
|
+
alias source_repository_name parent_repository_name
|
|
38
16
|
|
|
39
|
-
|
|
40
|
-
|
|
17
|
+
# TODO: document
|
|
18
|
+
# @api semipublic
|
|
19
|
+
alias source_model parent_model
|
|
41
20
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
EOS
|
|
46
|
-
end
|
|
21
|
+
# TODO: document
|
|
22
|
+
# @api semipublic
|
|
23
|
+
alias source_key parent_key
|
|
47
24
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
)
|
|
25
|
+
# TODO: document
|
|
26
|
+
# @api semipublic
|
|
27
|
+
def child_key
|
|
28
|
+
inverse.child_key
|
|
65
29
|
end
|
|
66
|
-
end
|
|
67
30
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# Collection that is wrapped in a Proxy.
|
|
72
|
-
class Proxy
|
|
73
|
-
include Assertions
|
|
31
|
+
# TODO: document
|
|
32
|
+
# @api semipublic
|
|
33
|
+
alias target_key child_key
|
|
74
34
|
|
|
75
|
-
|
|
35
|
+
# Returns a Collection for this relationship with a given source
|
|
36
|
+
#
|
|
37
|
+
# @param [Resource] source
|
|
38
|
+
# A Resource to scope the collection with
|
|
39
|
+
# @param [Query] other_query (optional)
|
|
40
|
+
# A Query to further scope the collection with
|
|
41
|
+
#
|
|
42
|
+
# @return [Collection]
|
|
43
|
+
# The collection scoped to the relationship, source and query
|
|
44
|
+
#
|
|
45
|
+
# @api private
|
|
46
|
+
def collection_for(source, other_query = nil)
|
|
47
|
+
query = query_for(source, other_query)
|
|
76
48
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
end
|
|
49
|
+
collection = collection_class.new(query)
|
|
50
|
+
collection.relationship = self
|
|
51
|
+
collection.source = source
|
|
81
52
|
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
53
|
+
# make the collection empty if the source is not saved
|
|
54
|
+
collection.replace([]) unless source.saved?
|
|
91
55
|
|
|
92
|
-
|
|
93
|
-
assert_mutable
|
|
94
|
-
return self if !resource.new_record? && self.include?(resource)
|
|
95
|
-
children << resource
|
|
96
|
-
relate_resource(resource)
|
|
97
|
-
self
|
|
56
|
+
collection
|
|
98
57
|
end
|
|
99
58
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
end
|
|
59
|
+
# Loads and returns association targets (ex.: articles) for given source resource
|
|
60
|
+
# (ex.: author)
|
|
61
|
+
#
|
|
62
|
+
# @api semipublic
|
|
63
|
+
def get(source, other_query = nil)
|
|
64
|
+
assert_kind_of 'source', source, source_model
|
|
107
65
|
|
|
108
|
-
|
|
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
|
|
66
|
+
lazy_load(source) unless loaded?(source)
|
|
115
67
|
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
68
|
+
collection = get!(source)
|
|
69
|
+
other_query.nil? ? collection : collection.all(other_query)
|
|
123
70
|
end
|
|
124
71
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
72
|
+
# Sets value of association targets (ex.: paragraphs) for given source resource
|
|
73
|
+
# (ex.: article)
|
|
74
|
+
#
|
|
75
|
+
# @api semipublic
|
|
76
|
+
def set(source, targets)
|
|
77
|
+
assert_kind_of 'source', source, source_model
|
|
78
|
+
assert_kind_of 'targets', targets, Array
|
|
129
79
|
|
|
130
|
-
|
|
131
|
-
assert_mutable
|
|
132
|
-
orphan_resource(children.shift)
|
|
133
|
-
end
|
|
80
|
+
lazy_load(source) unless loaded?(source)
|
|
134
81
|
|
|
135
|
-
|
|
136
|
-
assert_mutable
|
|
137
|
-
orphan_resource(children.delete(resource))
|
|
82
|
+
get!(source).replace(targets)
|
|
138
83
|
end
|
|
139
84
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
85
|
+
# TODO: document
|
|
86
|
+
# @api private
|
|
87
|
+
def inherited_by(model)
|
|
88
|
+
model.relationships(source_repository_name)[name] ||
|
|
89
|
+
self.class.new(name, child_model_name, model, options_with_inverse)
|
|
143
90
|
end
|
|
144
91
|
|
|
145
|
-
|
|
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) }
|
|
92
|
+
private
|
|
211
93
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
94
|
+
# TODO: document
|
|
95
|
+
# @api semipublic
|
|
96
|
+
def initialize(name, target_model, source_model, options = {})
|
|
97
|
+
target_model ||= Extlib::Inflection.camelize(name.to_s.singular)
|
|
98
|
+
options = { :min => 0, :max => source_model.n }.update(options)
|
|
99
|
+
super
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Dynamically defines reader method for source side of association
|
|
103
|
+
# (for instance, method paragraphs for model Article)
|
|
104
|
+
#
|
|
105
|
+
# @api semipublic
|
|
106
|
+
def create_reader
|
|
107
|
+
return if source_model.resource_method_defined?(name.to_s)
|
|
108
|
+
|
|
109
|
+
source_model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
110
|
+
def #{name}(query = nil) # def paragraphs(query = nil)
|
|
111
|
+
relationships[#{name.inspect}].get(self, query) # relationships[:paragraphs].get(self, query)
|
|
112
|
+
end # end
|
|
113
|
+
RUBY
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Dynamically defines reader method for source side of association
|
|
117
|
+
# (for instance, method paragraphs= for model Article)
|
|
118
|
+
#
|
|
119
|
+
# @api semipublic
|
|
120
|
+
def create_writer
|
|
121
|
+
writer_name = "#{name}="
|
|
122
|
+
|
|
123
|
+
return if source_model.resource_method_defined?(writer_name)
|
|
124
|
+
|
|
125
|
+
source_model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
126
|
+
def #{writer_name}(targets) # def paragraphs=(targets)
|
|
127
|
+
relationships[#{name.inspect}].set(self, targets) # relationships[:paragraphs].set(self, targets)
|
|
128
|
+
end # end
|
|
129
|
+
RUBY
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Loads association targets and sets resulting value on
|
|
133
|
+
# given source resource
|
|
134
|
+
#
|
|
135
|
+
# @param [Resource] source
|
|
136
|
+
# the source resource for the association
|
|
137
|
+
#
|
|
138
|
+
# @return [undefined]
|
|
139
|
+
#
|
|
140
|
+
# @api private
|
|
141
|
+
def lazy_load(source)
|
|
142
|
+
# SEL: load all related resources in the source collection
|
|
143
|
+
if source.saved? && source.collection.size > 1
|
|
144
|
+
eager_load(source.collection)
|
|
220
145
|
end
|
|
221
146
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if children.kind_of?(Array) && !children.frozen?
|
|
225
|
-
@children = @relationship.get_children(@parent).replace(children)
|
|
147
|
+
unless loaded?(source)
|
|
148
|
+
set!(source, collection_for(source))
|
|
226
149
|
end
|
|
227
|
-
|
|
228
|
-
true
|
|
229
150
|
end
|
|
230
151
|
|
|
231
|
-
|
|
232
|
-
|
|
152
|
+
# Sets the association targets in the resource
|
|
153
|
+
#
|
|
154
|
+
# @param [Resource] source
|
|
155
|
+
# the source to set
|
|
156
|
+
# @param [Array<Resource>] targets
|
|
157
|
+
# the target collection for the association
|
|
158
|
+
# @param [Query, Hash] query
|
|
159
|
+
# the query to scope the association with
|
|
160
|
+
#
|
|
161
|
+
# @return [undefined]
|
|
162
|
+
#
|
|
163
|
+
# @api private
|
|
164
|
+
def eager_load_targets(source, targets, query)
|
|
165
|
+
# TODO: figure out an alternative approach to using a
|
|
166
|
+
# private method call collection_replace
|
|
167
|
+
association = collection_for(source, query)
|
|
168
|
+
association.send(:collection_replace, targets)
|
|
169
|
+
set!(source, association)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Returns collection class used by this type of
|
|
173
|
+
# relationship
|
|
174
|
+
#
|
|
175
|
+
# @api private
|
|
176
|
+
def collection_class
|
|
177
|
+
OneToMany::Collection
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Returns the inverse relationship class
|
|
181
|
+
#
|
|
182
|
+
# @api private
|
|
183
|
+
def inverse_class
|
|
184
|
+
ManyToOne::Relationship
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Returns the inverse relationship name
|
|
188
|
+
#
|
|
189
|
+
# @api private
|
|
190
|
+
def inverse_name
|
|
191
|
+
super || Extlib::Inflection.underscore(Extlib::Inflection.demodulize(source_model.name)).to_sym
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# TODO: document
|
|
195
|
+
# @api private
|
|
196
|
+
def child_properties
|
|
197
|
+
super || parent_key.map do |parent_property|
|
|
198
|
+
"#{inverse_name}_#{parent_property.name}".to_sym
|
|
199
|
+
end
|
|
233
200
|
end
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
201
|
+
end # class Relationship
|
|
202
|
+
|
|
203
|
+
class Collection < DataMapper::Collection
|
|
204
|
+
# TODO: document
|
|
205
|
+
# @api private
|
|
206
|
+
attr_accessor :relationship
|
|
207
|
+
|
|
208
|
+
# TODO: document
|
|
209
|
+
# @api private
|
|
210
|
+
attr_accessor :source
|
|
211
|
+
|
|
212
|
+
# TODO: document
|
|
213
|
+
# @api public
|
|
214
|
+
def reload(*)
|
|
215
|
+
assert_source_saved 'The source must be saved before reloading the collection'
|
|
216
|
+
super
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Replace the Resources within the 1:m Collection
|
|
220
|
+
#
|
|
221
|
+
# @param [Enumerable] other
|
|
222
|
+
# List of other Resources to replace with
|
|
223
|
+
#
|
|
224
|
+
# @return [Collection]
|
|
225
|
+
# self
|
|
226
|
+
#
|
|
227
|
+
# @api public
|
|
228
|
+
def replace(*)
|
|
229
|
+
lazy_load # lazy load so that targets are always orphaned
|
|
230
|
+
super
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Removes all Resources from the 1:m Collection
|
|
234
|
+
#
|
|
235
|
+
# This should remove and orphan each Resource from the 1:m Collection.
|
|
236
|
+
#
|
|
237
|
+
# @return [Collection]
|
|
238
|
+
# self
|
|
239
|
+
#
|
|
240
|
+
# @api public
|
|
241
|
+
def clear
|
|
242
|
+
lazy_load # lazy load so that targets are always orphaned
|
|
243
|
+
super
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Update every Resource in the 1:m Collection
|
|
247
|
+
#
|
|
248
|
+
# @param [Hash] attributes
|
|
249
|
+
# attributes to update with
|
|
250
|
+
#
|
|
251
|
+
# @return [Boolean]
|
|
252
|
+
# true if the resources were successfully updated
|
|
253
|
+
#
|
|
254
|
+
# @api public
|
|
255
|
+
def update(*)
|
|
256
|
+
assert_source_saved 'The source must be saved before mass-updating the collection'
|
|
257
|
+
super
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Update every Resource in the 1:m Collection, bypassing validation
|
|
261
|
+
#
|
|
262
|
+
# @param [Hash] attributes
|
|
263
|
+
# attributes to update
|
|
264
|
+
#
|
|
265
|
+
# @return [Boolean]
|
|
266
|
+
# true if the resources were successfully updated
|
|
267
|
+
#
|
|
268
|
+
# @api public
|
|
269
|
+
def update!(*)
|
|
270
|
+
assert_source_saved 'The source must be saved before mass-updating the collection'
|
|
271
|
+
super
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Remove every Resource in the 1:m Collection from the repository
|
|
275
|
+
#
|
|
276
|
+
# This performs a deletion of each Resource in the Collection from
|
|
277
|
+
# the repository and clears the Collection.
|
|
278
|
+
#
|
|
279
|
+
# @return [Boolean]
|
|
280
|
+
# true if the resources were successfully destroyed
|
|
281
|
+
#
|
|
282
|
+
# @api public
|
|
283
|
+
def destroy
|
|
284
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
285
|
+
super
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Remove every Resource in the 1:m Collection from the repository, bypassing validation
|
|
289
|
+
#
|
|
290
|
+
# This performs a deletion of each Resource in the Collection from
|
|
291
|
+
# the repository and clears the Collection while skipping
|
|
292
|
+
# validation.
|
|
293
|
+
#
|
|
294
|
+
# @return [Boolean]
|
|
295
|
+
# true if the resources were successfully destroyed
|
|
296
|
+
#
|
|
297
|
+
# @api public
|
|
298
|
+
def destroy!
|
|
299
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
300
|
+
super
|
|
237
301
|
end
|
|
238
302
|
|
|
239
303
|
private
|
|
240
304
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
@parent = parent
|
|
247
|
-
@orphans = []
|
|
305
|
+
# TODO: document
|
|
306
|
+
# @api private
|
|
307
|
+
def _create(*)
|
|
308
|
+
assert_source_saved 'The source must be saved before creating a resource'
|
|
309
|
+
super
|
|
248
310
|
end
|
|
249
311
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
312
|
+
# TODO: document
|
|
313
|
+
# @api private
|
|
314
|
+
def _save(safe)
|
|
315
|
+
assert_source_saved 'The source must be saved before saving the collection'
|
|
253
316
|
|
|
254
|
-
|
|
255
|
-
|
|
317
|
+
# update removed resources to not reference the source
|
|
318
|
+
@removed.all? { |resource| resource.send(safe ? :save : :save!) } && super
|
|
256
319
|
end
|
|
257
320
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
default_attributes[attribute] = value
|
|
321
|
+
# TODO: document
|
|
322
|
+
# @api private
|
|
323
|
+
def lazy_load
|
|
324
|
+
if source.saved?
|
|
325
|
+
super
|
|
264
326
|
end
|
|
327
|
+
end
|
|
265
328
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
329
|
+
# TODO: document
|
|
330
|
+
# @api private
|
|
331
|
+
def new_collection(query, resources = nil, &block)
|
|
332
|
+
collection = self.class.new(query, &block)
|
|
269
333
|
|
|
270
|
-
|
|
271
|
-
|
|
334
|
+
collection.relationship = relationship
|
|
335
|
+
collection.source = source
|
|
272
336
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
337
|
+
resources ||= filter(query) if loaded?
|
|
338
|
+
|
|
339
|
+
# set the resources after the relationship and source are set
|
|
340
|
+
if resources
|
|
341
|
+
collection.send(:collection_replace, resources)
|
|
277
342
|
end
|
|
278
|
-
end
|
|
279
343
|
|
|
280
|
-
|
|
281
|
-
@relationship.child_model.new(default_attributes.merge(attributes))
|
|
344
|
+
collection
|
|
282
345
|
end
|
|
283
346
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
347
|
+
# TODO: document
|
|
348
|
+
# @api private
|
|
349
|
+
def resource_added(resource)
|
|
350
|
+
inverse_set(resource, source)
|
|
351
|
+
super
|
|
289
352
|
end
|
|
290
353
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
resource
|
|
354
|
+
# TODO: document
|
|
355
|
+
# @api private
|
|
356
|
+
def resource_removed(resource)
|
|
357
|
+
inverse_set(resource, nil)
|
|
358
|
+
super
|
|
295
359
|
end
|
|
296
360
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
@relationship.attach_parent(resource, parent)
|
|
303
|
-
resource.save
|
|
304
|
-
end
|
|
361
|
+
# TODO: document
|
|
362
|
+
# @api private
|
|
363
|
+
def inverse_set(source, target)
|
|
364
|
+
unless source.frozen?
|
|
365
|
+
relationship.inverse.set(source, target)
|
|
305
366
|
end
|
|
306
367
|
end
|
|
307
368
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
369
|
+
# TODO: document
|
|
370
|
+
# @api private
|
|
371
|
+
def assert_source_saved(message)
|
|
372
|
+
unless source.saved?
|
|
373
|
+
raise UnsavedParentError, message
|
|
374
|
+
end
|
|
311
375
|
end
|
|
312
|
-
end # class
|
|
376
|
+
end # class Collection
|
|
313
377
|
end # module OneToMany
|
|
314
378
|
end # module Associations
|
|
315
379
|
end # module DataMapper
|