datamapper-dm-core 0.9.11 → 0.10.0

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