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.
Files changed (192) hide show
  1. data/.autotest +17 -14
  2. data/.gitignore +3 -1
  3. data/FAQ +6 -5
  4. data/History.txt +5 -39
  5. data/Manifest.txt +67 -76
  6. data/QUICKLINKS +1 -1
  7. data/README.txt +21 -15
  8. data/Rakefile +16 -15
  9. data/SPECS +2 -29
  10. data/TODO +1 -1
  11. data/dm-core.gemspec +11 -15
  12. data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
  13. data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
  14. data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
  15. data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
  16. data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
  19. data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
  20. data/lib/dm-core/adapters.rb +135 -16
  21. data/lib/dm-core/associations/many_to_many.rb +372 -90
  22. data/lib/dm-core/associations/many_to_one.rb +220 -73
  23. data/lib/dm-core/associations/one_to_many.rb +319 -255
  24. data/lib/dm-core/associations/one_to_one.rb +66 -53
  25. data/lib/dm-core/associations/relationship.rb +560 -158
  26. data/lib/dm-core/collection.rb +1104 -381
  27. data/lib/dm-core/core_ext/kernel.rb +12 -0
  28. data/lib/dm-core/core_ext/symbol.rb +10 -0
  29. data/lib/dm-core/identity_map.rb +4 -34
  30. data/lib/dm-core/migrations.rb +1283 -0
  31. data/lib/dm-core/model/descendant_set.rb +81 -0
  32. data/lib/dm-core/model/hook.rb +45 -0
  33. data/lib/dm-core/model/is.rb +32 -0
  34. data/lib/dm-core/model/property.rb +248 -0
  35. data/lib/dm-core/model/relationship.rb +335 -0
  36. data/lib/dm-core/model/scope.rb +90 -0
  37. data/lib/dm-core/model.rb +570 -369
  38. data/lib/dm-core/property.rb +753 -280
  39. data/lib/dm-core/property_set.rb +141 -98
  40. data/lib/dm-core/query/conditions/comparison.rb +814 -0
  41. data/lib/dm-core/query/conditions/operation.rb +247 -0
  42. data/lib/dm-core/query/direction.rb +43 -0
  43. data/lib/dm-core/query/operator.rb +42 -0
  44. data/lib/dm-core/query/path.rb +102 -0
  45. data/lib/dm-core/query/sort.rb +45 -0
  46. data/lib/dm-core/query.rb +974 -492
  47. data/lib/dm-core/repository.rb +147 -107
  48. data/lib/dm-core/resource.rb +644 -429
  49. data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
  50. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
  51. data/lib/dm-core/support/chainable.rb +20 -0
  52. data/lib/dm-core/support/deprecate.rb +12 -0
  53. data/lib/dm-core/support/equalizer.rb +23 -0
  54. data/lib/dm-core/support/logger.rb +13 -0
  55. data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
  56. data/lib/dm-core/transaction.rb +333 -92
  57. data/lib/dm-core/type.rb +98 -60
  58. data/lib/dm-core/types/boolean.rb +1 -1
  59. data/lib/dm-core/types/discriminator.rb +34 -20
  60. data/lib/dm-core/types/object.rb +7 -4
  61. data/lib/dm-core/types/paranoid_boolean.rb +11 -9
  62. data/lib/dm-core/types/paranoid_datetime.rb +11 -9
  63. data/lib/dm-core/types/serial.rb +3 -3
  64. data/lib/dm-core/types/text.rb +3 -4
  65. data/lib/dm-core/version.rb +1 -1
  66. data/lib/dm-core.rb +106 -110
  67. data/script/performance.rb +102 -109
  68. data/script/profile.rb +169 -38
  69. data/spec/lib/adapter_helpers.rb +105 -0
  70. data/spec/lib/collection_helpers.rb +18 -0
  71. data/spec/lib/counter_adapter.rb +34 -0
  72. data/spec/lib/pending_helpers.rb +27 -0
  73. data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
  74. data/spec/public/associations/many_to_many_spec.rb +193 -0
  75. data/spec/public/associations/many_to_one_spec.rb +73 -0
  76. data/spec/public/associations/one_to_many_spec.rb +77 -0
  77. data/spec/public/associations/one_to_one_spec.rb +156 -0
  78. data/spec/public/collection_spec.rb +65 -0
  79. data/spec/public/model/relationship_spec.rb +924 -0
  80. data/spec/public/model_spec.rb +159 -0
  81. data/spec/public/property_spec.rb +829 -0
  82. data/spec/public/resource_spec.rb +71 -0
  83. data/spec/public/sel_spec.rb +44 -0
  84. data/spec/public/setup_spec.rb +145 -0
  85. data/spec/public/shared/association_collection_shared_spec.rb +317 -0
  86. data/spec/public/shared/collection_shared_spec.rb +1723 -0
  87. data/spec/public/shared/finder_shared_spec.rb +1619 -0
  88. data/spec/public/shared/resource_shared_spec.rb +924 -0
  89. data/spec/public/shared/sel_shared_spec.rb +112 -0
  90. data/spec/public/transaction_spec.rb +129 -0
  91. data/spec/public/types/discriminator_spec.rb +130 -0
  92. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  93. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  94. data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
  95. data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
  96. data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
  97. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
  98. data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
  99. data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
  100. data/spec/semipublic/associations/relationship_spec.rb +194 -0
  101. data/spec/semipublic/associations_spec.rb +177 -0
  102. data/spec/semipublic/collection_spec.rb +142 -0
  103. data/spec/semipublic/property_spec.rb +61 -0
  104. data/spec/semipublic/query/conditions_spec.rb +528 -0
  105. data/spec/semipublic/query/path_spec.rb +443 -0
  106. data/spec/semipublic/query_spec.rb +2626 -0
  107. data/spec/semipublic/resource_spec.rb +47 -0
  108. data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
  109. data/spec/spec.opts +3 -1
  110. data/spec/spec_helper.rb +80 -57
  111. data/tasks/ci.rb +19 -31
  112. data/tasks/dm.rb +43 -48
  113. data/tasks/doc.rb +8 -11
  114. data/tasks/gemspec.rb +5 -5
  115. data/tasks/hoe.rb +15 -16
  116. data/tasks/install.rb +8 -10
  117. metadata +72 -93
  118. data/lib/dm-core/associations/relationship_chain.rb +0 -81
  119. data/lib/dm-core/associations.rb +0 -207
  120. data/lib/dm-core/auto_migrations.rb +0 -105
  121. data/lib/dm-core/dependency_queue.rb +0 -32
  122. data/lib/dm-core/hook.rb +0 -11
  123. data/lib/dm-core/is.rb +0 -16
  124. data/lib/dm-core/logger.rb +0 -232
  125. data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
  126. data/lib/dm-core/migrator.rb +0 -29
  127. data/lib/dm-core/scope.rb +0 -58
  128. data/lib/dm-core/support/array.rb +0 -13
  129. data/lib/dm-core/support/assertions.rb +0 -8
  130. data/lib/dm-core/support/errors.rb +0 -23
  131. data/lib/dm-core/support/kernel.rb +0 -11
  132. data/lib/dm-core/support/symbol.rb +0 -41
  133. data/lib/dm-core/support.rb +0 -7
  134. data/lib/dm-core/type_map.rb +0 -80
  135. data/lib/dm-core/types.rb +0 -19
  136. data/script/all +0 -4
  137. data/spec/integration/association_spec.rb +0 -1382
  138. data/spec/integration/association_through_spec.rb +0 -203
  139. data/spec/integration/associations/many_to_many_spec.rb +0 -449
  140. data/spec/integration/associations/many_to_one_spec.rb +0 -163
  141. data/spec/integration/associations/one_to_many_spec.rb +0 -188
  142. data/spec/integration/auto_migrations_spec.rb +0 -413
  143. data/spec/integration/collection_spec.rb +0 -1073
  144. data/spec/integration/data_objects_adapter_spec.rb +0 -32
  145. data/spec/integration/dependency_queue_spec.rb +0 -46
  146. data/spec/integration/model_spec.rb +0 -197
  147. data/spec/integration/mysql_adapter_spec.rb +0 -85
  148. data/spec/integration/postgres_adapter_spec.rb +0 -731
  149. data/spec/integration/property_spec.rb +0 -253
  150. data/spec/integration/query_spec.rb +0 -514
  151. data/spec/integration/repository_spec.rb +0 -61
  152. data/spec/integration/resource_spec.rb +0 -513
  153. data/spec/integration/sqlite3_adapter_spec.rb +0 -352
  154. data/spec/integration/sti_spec.rb +0 -273
  155. data/spec/integration/strategic_eager_loading_spec.rb +0 -156
  156. data/spec/integration/transaction_spec.rb +0 -75
  157. data/spec/integration/type_spec.rb +0 -275
  158. data/spec/lib/logging_helper.rb +0 -18
  159. data/spec/lib/mock_adapter.rb +0 -27
  160. data/spec/lib/model_loader.rb +0 -100
  161. data/spec/lib/publicize_methods.rb +0 -28
  162. data/spec/models/content.rb +0 -16
  163. data/spec/models/vehicles.rb +0 -34
  164. data/spec/models/zoo.rb +0 -48
  165. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
  166. data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
  167. data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
  168. data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
  169. data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
  170. data/spec/unit/associations/many_to_many_spec.rb +0 -32
  171. data/spec/unit/associations/many_to_one_spec.rb +0 -159
  172. data/spec/unit/associations/one_to_many_spec.rb +0 -393
  173. data/spec/unit/associations/one_to_one_spec.rb +0 -7
  174. data/spec/unit/associations/relationship_spec.rb +0 -71
  175. data/spec/unit/associations_spec.rb +0 -242
  176. data/spec/unit/auto_migrations_spec.rb +0 -111
  177. data/spec/unit/collection_spec.rb +0 -182
  178. data/spec/unit/data_mapper_spec.rb +0 -35
  179. data/spec/unit/identity_map_spec.rb +0 -126
  180. data/spec/unit/is_spec.rb +0 -80
  181. data/spec/unit/migrator_spec.rb +0 -33
  182. data/spec/unit/model_spec.rb +0 -321
  183. data/spec/unit/naming_conventions_spec.rb +0 -36
  184. data/spec/unit/property_set_spec.rb +0 -90
  185. data/spec/unit/property_spec.rb +0 -753
  186. data/spec/unit/query_spec.rb +0 -571
  187. data/spec/unit/repository_spec.rb +0 -93
  188. data/spec/unit/resource_spec.rb +0 -649
  189. data/spec/unit/scope_spec.rb +0 -142
  190. data/spec/unit/transaction_spec.rb +0 -493
  191. data/spec/unit/type_map_spec.rb +0 -114
  192. data/spec/unit/type_spec.rb +0 -119
@@ -1,315 +1,379 @@
1
1
  module DataMapper
2
2
  module Associations
3
- module OneToMany
4
- extend Assertions
5
-
6
- # Setup one to many relationship between two models
7
- # -
8
- # @api private
9
- def self.setup(name, model, options = {})
10
- assert_kind_of 'name', name, Symbol
11
- assert_kind_of 'model', model, Model
12
- assert_kind_of 'options', options, Hash
13
-
14
- repository_name = model.repository.name
15
-
16
- model.class_eval <<-EOS, __FILE__, __LINE__
17
- def #{name}(query = {})
18
- #{name}_association.all(query)
19
- end
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
- def #{name}=(children)
22
- #{name}_association.replace(children)
23
- end
9
+ # TODO: document
10
+ # @api semipublic
11
+ alias target_model child_model
24
12
 
25
- private
26
-
27
- def #{name}_association
28
- @#{name}_association ||= begin
29
- unless relationship = model.relationships(#{repository_name.inspect})[#{name.inspect}]
30
- raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
31
- end
32
- association = Proxy.new(relationship, self)
33
- parent_associations << association
34
- association
35
- end
36
- end
37
- EOS
13
+ # TODO: document
14
+ # @api semipublic
15
+ alias source_repository_name parent_repository_name
38
16
 
39
- model.relationships(repository_name)[name] = if options.has_key?(:through)
40
- opts = options.dup
17
+ # TODO: document
18
+ # @api semipublic
19
+ alias source_model parent_model
41
20
 
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
21
+ # TODO: document
22
+ # @api semipublic
23
+ alias source_key parent_key
47
24
 
48
- opts[:child_model] ||= opts.delete(:class_name) || Extlib::Inflection.classify(name)
49
- opts[:parent_model] = model
50
- opts[:repository_name] = repository_name
51
- opts[:near_relationship_name] = opts.delete(:through)
52
- opts[:remote_relationship_name] ||= opts.delete(:remote_name) || name
53
- opts[:parent_key] = opts[:parent_key]
54
- opts[:child_key] = opts[:child_key]
55
-
56
- RelationshipChain.new( opts )
57
- else
58
- Relationship.new(
59
- name,
60
- repository_name,
61
- options.fetch(:class_name, Extlib::Inflection.classify(name)),
62
- model,
63
- options
64
- )
25
+ # TODO: document
26
+ # @api semipublic
27
+ def child_key
28
+ inverse.child_key
65
29
  end
66
- end
67
30
 
68
- # TODO: look at making this inherit from Collection. The API is
69
- # almost identical, and it would make more sense for the
70
- # relationship.get_children method to return a Proxy than a
71
- # Collection that is wrapped in a Proxy.
72
- class Proxy
73
- include Assertions
31
+ # TODO: document
32
+ # @api semipublic
33
+ alias target_key child_key
74
34
 
75
- instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ class object_id kind_of? respond_to? assert_kind_of should should_not instance_variable_set instance_variable_get ].include?(m.to_s) }
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
- # FIXME: remove when RelationshipChain#get_children can return a Collection
78
- def all(query = {})
79
- query.empty? ? self : @relationship.get_children(@parent, query)
80
- end
49
+ collection = collection_class.new(query)
50
+ collection.relationship = self
51
+ collection.source = source
81
52
 
82
- # FIXME: remove when RelationshipChain#get_children can return a Collection
83
- def first(*args)
84
- if args.last.respond_to?(:merge)
85
- query = args.pop
86
- @relationship.get_children(@parent, query, :first, *args)
87
- else
88
- children.first(*args)
89
- end
90
- end
53
+ # make the collection empty if the source is not saved
54
+ collection.replace([]) unless source.saved?
91
55
 
92
- def <<(resource)
93
- assert_mutable
94
- return self if !resource.new_record? && self.include?(resource)
95
- children << resource
96
- relate_resource(resource)
97
- self
56
+ collection
98
57
  end
99
58
 
100
- def push(*resources)
101
- assert_mutable
102
- resources.reject! { |resource| !resource.new_record? && self.include?(resource) }
103
- children.push(*resources)
104
- resources.each { |resource| relate_resource(resource) }
105
- self
106
- end
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
- def unshift(*resources)
109
- assert_mutable
110
- resources.reject! { |resource| !resource.new_record? && self.include?(resource) }
111
- children.unshift(*resources)
112
- resources.each { |resource| relate_resource(resource) }
113
- self
114
- end
66
+ lazy_load(source) unless loaded?(source)
115
67
 
116
- def replace(other)
117
- assert_mutable
118
- each { |resource| orphan_resource(resource) }
119
- other = other.map { |resource| resource.kind_of?(Hash) ? new_child(resource) : resource }
120
- children.replace(other)
121
- other.each { |resource| relate_resource(resource) }
122
- self
68
+ collection = get!(source)
69
+ other_query.nil? ? collection : collection.all(other_query)
123
70
  end
124
71
 
125
- def pop
126
- assert_mutable
127
- orphan_resource(children.pop)
128
- end
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
- def shift
131
- assert_mutable
132
- orphan_resource(children.shift)
133
- end
80
+ lazy_load(source) unless loaded?(source)
134
81
 
135
- def delete(resource)
136
- assert_mutable
137
- orphan_resource(children.delete(resource))
82
+ get!(source).replace(targets)
138
83
  end
139
84
 
140
- def delete_at(index)
141
- assert_mutable
142
- orphan_resource(children.delete_at(index))
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
- def clear
146
- assert_mutable
147
- each { |resource| orphan_resource(resource) }
148
- children.clear
149
- self
150
- end
151
-
152
- def build(attributes = {})
153
- assert_mutable
154
- attributes = default_attributes.merge(attributes)
155
- resource = children.respond_to?(:build) ? children.build(attributes) : new_child(attributes)
156
- resource
157
- end
158
-
159
- def new(attributes = {})
160
- assert_mutable
161
- raise UnsavedParentError, 'You cannot intialize until the parent is saved' if @parent.new_record?
162
- attributes = default_attributes.merge(attributes)
163
- resource = children.respond_to?(:new) ? children.new(attributes) : @relationship.child_model.new(attributes)
164
- self << resource
165
- resource
166
- end
167
-
168
- def create(attributes = {})
169
- assert_mutable
170
- raise UnsavedParentError, 'You cannot create until the parent is saved' if @parent.new_record?
171
- attributes = default_attributes.merge(attributes)
172
- resource = children.respond_to?(:create) ? children.create(attributes) : @relationship.child_model.create(attributes)
173
- self << resource
174
- resource
175
- end
176
-
177
- def update(attributes = {})
178
- assert_mutable
179
- raise UnsavedParentError, 'You cannot mass-update until the parent is saved' if @parent.new_record?
180
- children.update(attributes)
181
- end
182
-
183
- def update!(attributes = {})
184
- assert_mutable
185
- raise UnsavedParentError, 'You cannot mass-update without validations until the parent is saved' if @parent.new_record?
186
- children.update!(attributes)
187
- end
188
-
189
- def destroy
190
- assert_mutable
191
- raise UnsavedParentError, 'You cannot mass-delete until the parent is saved' if @parent.new_record?
192
- children.destroy
193
- end
194
-
195
- def destroy!
196
- assert_mutable
197
- raise UnsavedParentError, 'You cannot mass-delete without validations until the parent is saved' if @parent.new_record?
198
- children.destroy!
199
- end
200
-
201
- def reload
202
- @children = nil
203
- self
204
- end
205
-
206
- def save
207
- return true if children.frozen?
208
-
209
- # save every resource in the collection
210
- each { |resource| save_resource(resource) }
92
+ private
211
93
 
212
- # save orphan resources
213
- @orphans.each do |resource|
214
- begin
215
- save_resource(resource, nil)
216
- rescue
217
- children << resource unless children.frozen? || children.include?(resource)
218
- raise
219
- end
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
- # FIXME: remove when RelationshipChain#get_children can return a Collection
223
- # place the children into a Collection if not already
224
- if children.kind_of?(Array) && !children.frozen?
225
- @children = @relationship.get_children(@parent).replace(children)
147
+ unless loaded?(source)
148
+ set!(source, collection_for(source))
226
149
  end
227
-
228
- true
229
150
  end
230
151
 
231
- def kind_of?(klass)
232
- super || children.kind_of?(klass)
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
- def respond_to?(method, include_private = false)
236
- super || children.respond_to?(method, include_private)
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
- def initialize(relationship, parent)
242
- assert_kind_of 'relationship', relationship, Relationship
243
- assert_kind_of 'parent', parent, Resource
244
-
245
- @relationship = relationship
246
- @parent = parent
247
- @orphans = []
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
- def children
251
- @children ||= @relationship.get_children(@parent)
252
- end
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
- def assert_mutable
255
- raise ImmutableAssociationError, 'You can not modify this association' if children.frozen?
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
- def default_attributes
259
- default_attributes = {}
260
-
261
- @relationship.query.each do |attribute, value|
262
- next if Query::OPTIONS.include?(attribute) || attribute.kind_of?(Query::Operator)
263
- default_attributes[attribute] = value
321
+ # TODO: document
322
+ # @api private
323
+ def lazy_load
324
+ if source.saved?
325
+ super
264
326
  end
327
+ end
265
328
 
266
- @relationship.child_key.zip(@relationship.parent_key.get(@parent)) do |property,value|
267
- default_attributes[property.name] = value
268
- end
329
+ # TODO: document
330
+ # @api private
331
+ def new_collection(query, resources = nil, &block)
332
+ collection = self.class.new(query, &block)
269
333
 
270
- default_attributes
271
- end
334
+ collection.relationship = relationship
335
+ collection.source = source
272
336
 
273
- def add_default_association_values(resource)
274
- default_attributes.each do |attribute, value|
275
- next if !resource.respond_to?("#{attribute}=") || resource.attribute_loaded?(attribute)
276
- resource.send("#{attribute}=", value)
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
- def new_child(attributes)
281
- @relationship.child_model.new(default_attributes.merge(attributes))
344
+ collection
282
345
  end
283
346
 
284
- def relate_resource(resource)
285
- assert_mutable
286
- add_default_association_values(resource)
287
- @orphans.delete(resource)
288
- resource
347
+ # TODO: document
348
+ # @api private
349
+ def resource_added(resource)
350
+ inverse_set(resource, source)
351
+ super
289
352
  end
290
353
 
291
- def orphan_resource(resource)
292
- assert_mutable
293
- @orphans << resource
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
- def save_resource(resource, parent = @parent)
298
- @relationship.with_repository(resource) do |r|
299
- if parent.nil? && resource.model.respond_to?(:many_to_many)
300
- resource.destroy
301
- else
302
- @relationship.attach_parent(resource, parent)
303
- resource.save
304
- end
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
- def method_missing(method, *args, &block)
309
- results = children.send(method, *args, &block)
310
- results.equal?(children) ? self : results
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 Proxy
376
+ end # class Collection
313
377
  end # module OneToMany
314
378
  end # module Associations
315
379
  end # module DataMapper