dm-core 0.10.1 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/.autotest +29 -0
  2. data/.document +5 -0
  3. data/.gitignore +27 -0
  4. data/LICENSE +20 -0
  5. data/{README.txt → README.rdoc} +14 -3
  6. data/Rakefile +23 -22
  7. data/VERSION +1 -0
  8. data/dm-core.gemspec +201 -10
  9. data/lib/dm-core.rb +32 -23
  10. data/lib/dm-core/adapters.rb +0 -1
  11. data/lib/dm-core/adapters/data_objects_adapter.rb +230 -151
  12. data/lib/dm-core/adapters/mysql_adapter.rb +7 -8
  13. data/lib/dm-core/adapters/oracle_adapter.rb +39 -59
  14. data/lib/dm-core/adapters/postgres_adapter.rb +0 -1
  15. data/lib/dm-core/adapters/sqlite3_adapter.rb +5 -0
  16. data/lib/dm-core/adapters/sqlserver_adapter.rb +114 -0
  17. data/lib/dm-core/adapters/yaml_adapter.rb +0 -5
  18. data/lib/dm-core/associations/many_to_many.rb +118 -56
  19. data/lib/dm-core/associations/many_to_one.rb +48 -21
  20. data/lib/dm-core/associations/one_to_many.rb +8 -30
  21. data/lib/dm-core/associations/one_to_one.rb +1 -5
  22. data/lib/dm-core/associations/relationship.rb +89 -97
  23. data/lib/dm-core/collection.rb +299 -184
  24. data/lib/dm-core/core_ext/enumerable.rb +28 -0
  25. data/lib/dm-core/core_ext/kernel.rb +0 -2
  26. data/lib/dm-core/migrations.rb +314 -170
  27. data/lib/dm-core/model.rb +97 -66
  28. data/lib/dm-core/model/descendant_set.rb +1 -1
  29. data/lib/dm-core/model/hook.rb +0 -3
  30. data/lib/dm-core/model/property.rb +7 -10
  31. data/lib/dm-core/model/relationship.rb +79 -26
  32. data/lib/dm-core/model/scope.rb +3 -4
  33. data/lib/dm-core/property.rb +152 -90
  34. data/lib/dm-core/property_set.rb +18 -37
  35. data/lib/dm-core/query.rb +452 -153
  36. data/lib/dm-core/query/conditions/comparison.rb +266 -173
  37. data/lib/dm-core/query/conditions/operation.rb +499 -57
  38. data/lib/dm-core/query/direction.rb +0 -3
  39. data/lib/dm-core/query/operator.rb +0 -4
  40. data/lib/dm-core/query/path.rb +10 -12
  41. data/lib/dm-core/query/sort.rb +4 -10
  42. data/lib/dm-core/repository.rb +10 -6
  43. data/lib/dm-core/resource.rb +343 -148
  44. data/lib/dm-core/spec/adapter_shared_spec.rb +17 -1
  45. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +277 -17
  46. data/lib/dm-core/support/chainable.rb +0 -2
  47. data/lib/dm-core/support/equalizer.rb +27 -3
  48. data/lib/dm-core/transaction.rb +75 -75
  49. data/lib/dm-core/type.rb +19 -5
  50. data/lib/dm-core/types/discriminator.rb +4 -4
  51. data/lib/dm-core/types/object.rb +2 -7
  52. data/lib/dm-core/types/paranoid_boolean.rb +8 -2
  53. data/lib/dm-core/types/paranoid_datetime.rb +8 -2
  54. data/lib/dm-core/version.rb +1 -1
  55. data/script/performance.rb +7 -7
  56. data/script/profile.rb +6 -6
  57. data/spec/lib/collection_helpers.rb +2 -2
  58. data/spec/lib/pending_helpers.rb +22 -3
  59. data/spec/lib/rspec_immediate_feedback_formatter.rb +1 -0
  60. data/spec/public/associations/many_to_many_spec.rb +6 -4
  61. data/spec/public/associations/many_to_one_spec.rb +10 -1
  62. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +39 -0
  63. data/spec/public/associations/one_to_many_spec.rb +4 -3
  64. data/spec/public/associations/one_to_one_spec.rb +19 -1
  65. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +45 -0
  66. data/spec/public/collection_spec.rb +4 -3
  67. data/spec/public/migrations_spec.rb +144 -0
  68. data/spec/public/model/relationship_spec.rb +115 -55
  69. data/spec/public/model_spec.rb +13 -13
  70. data/spec/public/property/object_spec.rb +106 -0
  71. data/spec/public/property_spec.rb +18 -14
  72. data/spec/public/resource_spec.rb +10 -1
  73. data/spec/public/sel_spec.rb +16 -49
  74. data/spec/public/setup_spec.rb +1 -1
  75. data/spec/public/shared/association_collection_shared_spec.rb +6 -14
  76. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  77. data/spec/public/shared/collection_shared_spec.rb +214 -217
  78. data/spec/public/shared/finder_shared_spec.rb +259 -365
  79. data/spec/public/shared/resource_shared_spec.rb +524 -248
  80. data/spec/public/transaction_spec.rb +27 -3
  81. data/spec/public/types/discriminator_spec.rb +1 -1
  82. data/spec/rcov.opts +6 -0
  83. data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +17 -0
  84. data/spec/semipublic/associations/many_to_one_spec.rb +3 -20
  85. data/spec/semipublic/associations_spec.rb +2 -2
  86. data/spec/semipublic/collection_spec.rb +0 -32
  87. data/spec/semipublic/model_spec.rb +96 -0
  88. data/spec/semipublic/property_spec.rb +3 -3
  89. data/spec/semipublic/query/conditions/comparison_spec.rb +1719 -0
  90. data/spec/semipublic/query/conditions/operation_spec.rb +1292 -0
  91. data/spec/semipublic/query_spec.rb +1285 -144
  92. data/spec/semipublic/resource_spec.rb +0 -24
  93. data/spec/semipublic/shared/resource_shared_spec.rb +103 -38
  94. data/spec/spec.opts +1 -1
  95. data/spec/spec_helper.rb +15 -6
  96. data/tasks/ci.rake +1 -0
  97. data/tasks/metrics.rake +37 -0
  98. data/tasks/spec.rake +41 -0
  99. data/tasks/yard.rake +9 -0
  100. data/tasks/yardstick.rake +19 -0
  101. metadata +99 -29
  102. data/CONTRIBUTING +0 -51
  103. data/FAQ +0 -93
  104. data/History.txt +0 -27
  105. data/MIT-LICENSE +0 -22
  106. data/Manifest.txt +0 -121
  107. data/QUICKLINKS +0 -11
  108. data/SPECS +0 -35
  109. data/TODO +0 -1
  110. data/spec/semipublic/query/conditions_spec.rb +0 -528
  111. data/tasks/ci.rb +0 -24
  112. data/tasks/dm.rb +0 -58
  113. data/tasks/doc.rb +0 -17
  114. data/tasks/gemspec.rb +0 -23
  115. data/tasks/hoe.rb +0 -45
  116. data/tasks/install.rb +0 -18
@@ -4,32 +4,33 @@ module DataMapper
4
4
  # Relationship class with implementation specific
5
5
  # to n side of 1 to n association
6
6
  class Relationship < Associations::Relationship
7
- OPTIONS = superclass::OPTIONS.dup << :nullable
7
+ OPTIONS = superclass::OPTIONS.dup << :required
8
8
 
9
- # TODO: document
10
9
  # @api semipublic
11
10
  alias source_repository_name child_repository_name
12
11
 
13
- # TODO: document
14
12
  # @api semipublic
15
13
  alias source_model child_model
16
14
 
17
- # TODO: document
18
15
  # @api semipublic
19
16
  alias target_repository_name parent_repository_name
20
17
 
21
- # TODO: document
22
18
  # @api semipublic
23
19
  alias target_model parent_model
24
20
 
25
- # TODO: document
26
21
  # @api semipublic
27
22
  alias target_key parent_key
28
23
 
29
- # TODO: document
30
24
  # @api semipublic
25
+ def required?
26
+ @required
27
+ end
28
+
29
+ # @api private
31
30
  def nullable?
32
- @nullable
31
+ klass = self.class
32
+ warn "#{klass}#nullable? is deprecated, use #{klass}#required? instead (#{caller[0]})"
33
+ !required?
33
34
  end
34
35
 
35
36
  # Returns a set of keys that identify child model
@@ -39,8 +40,9 @@ module DataMapper
39
40
  def child_key
40
41
  return @child_key if defined?(@child_key)
41
42
 
43
+ model = child_model
42
44
  repository_name = child_repository_name || parent_repository_name
43
- properties = child_model.properties(repository_name)
45
+ properties = model.properties(repository_name)
44
46
 
45
47
  child_key = parent_key.zip(@child_properties || []).map do |parent_property, property_name|
46
48
  property_name ||= "#{name}_#{parent_property.name}".to_sym
@@ -49,7 +51,7 @@ module DataMapper
49
51
  # create the property within the correct repository
50
52
  DataMapper.repository(repository_name) do
51
53
  type = parent_property.send(parent_property.type == DataMapper::Types::Boolean ? :type : :primitive)
52
- child_model.property(property_name, type, child_key_options(parent_property))
54
+ model.property(property_name, type, child_key_options(parent_property))
53
55
  end
54
56
  end
55
57
  end
@@ -57,10 +59,24 @@ module DataMapper
57
59
  @child_key = properties.class.new(child_key).freeze
58
60
  end
59
61
 
60
- # TODO: document
61
62
  # @api semipublic
62
63
  alias source_key child_key
63
64
 
65
+ # Returns a hash of conditions that scopes query that fetches
66
+ # target object
67
+ #
68
+ # @return [Hash]
69
+ # Hash of conditions that scopes query
70
+ #
71
+ # @api private
72
+ def source_scope(source)
73
+ if source.kind_of?(Resource)
74
+ Query.target_conditions(source, source_key, target_key)
75
+ else
76
+ super
77
+ end
78
+ end
79
+
64
80
  # Returns a Resource for this relationship with a given source
65
81
  #
66
82
  # @param [Resource] source
@@ -112,6 +128,8 @@ module DataMapper
112
128
  #
113
129
  # @api semipublic
114
130
  def set(source, target)
131
+ target_model = self.target_model
132
+
115
133
  assert_kind_of 'source', source, source_model
116
134
  assert_kind_of 'target', target, target_model, Hash, NilClass
117
135
 
@@ -129,9 +147,16 @@ module DataMapper
129
147
  #
130
148
  # @api semipublic
131
149
  def initialize(name, source_model, target_model, options = {})
132
- @nullable = options.fetch(:nullable, false)
150
+ if options.key?(:nullable)
151
+ nullable_options = options.only(:nullable)
152
+ required_options = { :required => !options.delete(:nullable) }
153
+ warn "#{nullable_options.inspect} is deprecated, use #{required_options.inspect} instead (#{caller[2]})"
154
+ options.update(required_options)
155
+ end
156
+
157
+ @required = options.fetch(:required, true)
133
158
  target_model ||= Extlib::Inflection.camelize(name)
134
- options = { :min => @nullable ? 0 : 1, :max => 1 }.update(options)
159
+ options = { :min => @required ? 1 : 0, :max => 1 }.update(options)
135
160
  super
136
161
  end
137
162
 
@@ -145,11 +170,12 @@ module DataMapper
145
170
  #
146
171
  # @api private
147
172
  def lazy_load(source)
148
- return unless source_key.get(source).all?
173
+ return unless valid_source?(source)
149
174
 
150
175
  # SEL: load all related resources in the source collection
151
- if source.saved? && source.collection.size > 1
152
- eager_load(source.collection)
176
+ collection = source.collection
177
+ if source.saved? && collection.size > 1
178
+ eager_load(collection)
153
179
  end
154
180
 
155
181
  unless loaded?(source)
@@ -187,19 +213,20 @@ module DataMapper
187
213
  super || Extlib::Inflection.underscore(Extlib::Inflection.demodulize(source_model.name)).pluralize.to_sym
188
214
  end
189
215
 
190
- # TODO: document
191
216
  # @api private
192
217
  def child_key_options(parent_property)
193
- options = parent_property.options.only(:length, :precision, :scale).update(:index => name, :nullable => nullable?)
218
+ options = parent_property.options.only(:length, :precision, :scale).update(:index => name, :required => required?)
219
+
220
+ min = parent_property.min
221
+ max = parent_property.max
194
222
 
195
- if parent_property.primitive == Integer && parent_property.min && parent_property.max
196
- options.update(:min => parent_property.min, :max => parent_property.max)
223
+ if parent_property.primitive == Integer && min && max
224
+ options.update(:min => min, :max => max)
197
225
  end
198
226
 
199
227
  options
200
228
  end
201
229
 
202
- # TODO: document
203
230
  # @api private
204
231
  def child_properties
205
232
  child_key.map { |property| property.name }
@@ -2,33 +2,26 @@ module DataMapper
2
2
  module Associations
3
3
  module OneToMany #:nodoc:
4
4
  class Relationship < Associations::Relationship
5
- # TODO: document
6
5
  # @api semipublic
7
6
  alias target_repository_name child_repository_name
8
7
 
9
- # TODO: document
10
8
  # @api semipublic
11
9
  alias target_model child_model
12
10
 
13
- # TODO: document
14
11
  # @api semipublic
15
12
  alias source_repository_name parent_repository_name
16
13
 
17
- # TODO: document
18
14
  # @api semipublic
19
15
  alias source_model parent_model
20
16
 
21
- # TODO: document
22
17
  # @api semipublic
23
18
  alias source_key parent_key
24
19
 
25
- # TODO: document
26
20
  # @api semipublic
27
21
  def child_key
28
22
  inverse.child_key
29
23
  end
30
24
 
31
- # TODO: document
32
25
  # @api semipublic
33
26
  alias target_key child_key
34
27
 
@@ -84,7 +77,6 @@ module DataMapper
84
77
 
85
78
  private
86
79
 
87
- # TODO: document
88
80
  # @api semipublic
89
81
  def initialize(name, target_model, source_model, options = {})
90
82
  target_model ||= Extlib::Inflection.camelize(name.to_s.singular)
@@ -103,8 +95,9 @@ module DataMapper
103
95
  # @api private
104
96
  def lazy_load(source)
105
97
  # SEL: load all related resources in the source collection
106
- if source.saved? && source.collection.size > 1
107
- eager_load(source.collection)
98
+ collection = source.collection
99
+ if source.saved? && collection.size > 1
100
+ eager_load(collection)
108
101
  end
109
102
 
110
103
  unless loaded?(source)
@@ -125,11 +118,7 @@ module DataMapper
125
118
  #
126
119
  # @api private
127
120
  def eager_load_targets(source, targets, query)
128
- # TODO: figure out an alternative approach to using a
129
- # private method call collection_replace
130
- association = collection_for(source, query)
131
- association.send(:collection_replace, targets)
132
- set!(source, association)
121
+ set!(source, collection_for(source, query).set(targets))
133
122
  end
134
123
 
135
124
  # Returns collection class used by this type of
@@ -154,7 +143,6 @@ module DataMapper
154
143
  super || Extlib::Inflection.underscore(Extlib::Inflection.demodulize(source_model.name)).to_sym
155
144
  end
156
145
 
157
- # TODO: document
158
146
  # @api private
159
147
  def child_properties
160
148
  super || parent_key.map do |parent_property|
@@ -164,15 +152,12 @@ module DataMapper
164
152
  end # class Relationship
165
153
 
166
154
  class Collection < DataMapper::Collection
167
- # TODO: document
168
155
  # @api private
169
156
  attr_accessor :relationship
170
157
 
171
- # TODO: document
172
158
  # @api private
173
159
  attr_accessor :source
174
160
 
175
- # TODO: document
176
161
  # @api public
177
162
  def reload(*)
178
163
  assert_source_saved 'The source must be saved before reloading the collection'
@@ -265,23 +250,20 @@ module DataMapper
265
250
 
266
251
  private
267
252
 
268
- # TODO: document
269
253
  # @api private
270
254
  def _create(*)
271
255
  assert_source_saved 'The source must be saved before creating a resource'
272
256
  super
273
257
  end
274
258
 
275
- # TODO: document
276
259
  # @api private
277
260
  def _save(safe)
278
261
  assert_source_saved 'The source must be saved before saving the collection'
279
262
 
280
263
  # update removed resources to not reference the source
281
- @removed.all? { |resource| resource.destroyed? || resource.send(safe ? :save : :save!) } && super
264
+ @removed.all? { |resource| resource.destroyed? || resource.__send__(safe ? :save : :save!) } && super
282
265
  end
283
266
 
284
- # TODO: document
285
267
  # @api private
286
268
  def lazy_load
287
269
  if source.saved?
@@ -289,7 +271,6 @@ module DataMapper
289
271
  end
290
272
  end
291
273
 
292
- # TODO: document
293
274
  # @api private
294
275
  def new_collection(query, resources = nil, &block)
295
276
  collection = self.class.new(query, &block)
@@ -301,35 +282,32 @@ module DataMapper
301
282
 
302
283
  # set the resources after the relationship and source are set
303
284
  if resources
304
- collection.send(:collection_replace, resources)
285
+ collection.set(resources)
305
286
  end
306
287
 
307
288
  collection
308
289
  end
309
290
 
310
- # TODO: document
311
291
  # @api private
312
292
  def resource_added(resource)
293
+ resource = initialize_resource(resource)
313
294
  inverse_set(resource, source)
314
295
  super
315
296
  end
316
297
 
317
- # TODO: document
318
298
  # @api private
319
299
  def resource_removed(resource)
320
300
  inverse_set(resource, nil)
321
301
  super
322
302
  end
323
303
 
324
- # TODO: document
325
304
  # @api private
326
305
  def inverse_set(source, target)
327
- unless source.frozen?
306
+ unless source.readonly?
328
307
  relationship.inverse.set(source, target)
329
308
  end
330
309
  end
331
310
 
332
- # TODO: document
333
311
  # @api private
334
312
  def assert_source_saved(message)
335
313
  unless source.saved?
@@ -15,7 +15,7 @@ module DataMapper
15
15
  def get(source, other_query = nil)
16
16
  assert_kind_of 'source', source, source_model
17
17
 
18
- return unless loaded?(source) || source_key.get(source).all?
18
+ return unless loaded?(source) || valid_source?(source)
19
19
 
20
20
  relationship.get(source, other_query).first
21
21
  end
@@ -31,19 +31,16 @@ module DataMapper
31
31
  relationship.set(source, [ target ].compact).first
32
32
  end
33
33
 
34
- # TODO: document
35
34
  # @api public
36
35
  def kind_of?(klass)
37
36
  super || relationship.kind_of?(klass)
38
37
  end
39
38
 
40
- # TODO: document
41
39
  # @api public
42
40
  def instance_of?(klass)
43
41
  super || relationship.instance_of?(klass)
44
42
  end
45
43
 
46
- # TODO: document
47
44
  # @api public
48
45
  def respond_to?(method, include_private = false)
49
46
  super || relationship.respond_to?(method, include_private)
@@ -63,7 +60,6 @@ module DataMapper
63
60
  @relationship = klass.new(name, target_model, source_model, options)
64
61
  end
65
62
 
66
- # TODO: document
67
63
  # @api private
68
64
  def method_missing(method, *args, &block)
69
65
  relationship.send(method, *args, &block)
@@ -1,3 +1,5 @@
1
+ # TODO: move argument and option validation into the class
2
+
1
3
  module DataMapper
2
4
  module Associations
3
5
  # Base class for relationships. Each type of relationship
@@ -119,6 +121,16 @@ module DataMapper
119
121
  # @api private
120
122
  attr_reader :query
121
123
 
124
+ # Returns the String the Relationship would use in a Hash
125
+ #
126
+ # @return [String]
127
+ # String name for the Relationship
128
+ #
129
+ # @api private
130
+ def field
131
+ name.to_s
132
+ end
133
+
122
134
  # Returns a hash of conditions that scopes query that fetches
123
135
  # target object
124
136
  #
@@ -140,7 +152,7 @@ module DataMapper
140
152
  DataMapper.repository(repository_name).scope do
141
153
  query = target_model.query.dup
142
154
  query.update(self.query)
143
- query.update(source_scope(source))
155
+ query.update(:conditions => source_scope(source))
144
156
  query.update(other_query) if other_query
145
157
  query.update(:fields => query.fields | target_key)
146
158
  end
@@ -153,12 +165,13 @@ module DataMapper
153
165
  #
154
166
  # @api private
155
167
  def child_model
156
- @child_model ||= (@parent_model || Object).find_const(child_model_name)
168
+ return @child_model if defined?(@child_model)
169
+ child_model_name = self.child_model_name
170
+ @child_model = (@parent_model || Object).find_const(child_model_name)
157
171
  rescue NameError
158
172
  raise NameError, "Cannot find the child_model #{child_model_name} for #{parent_model_name} in #{name}"
159
173
  end
160
174
 
161
- # TODO: document
162
175
  # @api private
163
176
  def child_model?
164
177
  child_model
@@ -167,7 +180,6 @@ module DataMapper
167
180
  false
168
181
  end
169
182
 
170
- # TODO: document
171
183
  # @api private
172
184
  def child_model_name
173
185
  @child_model ? child_model.name : @child_model_name
@@ -206,12 +218,13 @@ module DataMapper
206
218
  #
207
219
  # @api private
208
220
  def parent_model
209
- @parent_model ||= (@child_model || Object).find_const(parent_model_name)
221
+ return @parent_model if defined?(@parent_model)
222
+ parent_model_name = self.parent_model_name
223
+ @parent_model = (@child_model || Object).find_const(parent_model_name)
210
224
  rescue NameError
211
225
  raise NameError, "Cannot find the parent_model #{parent_model_name} for #{child_model_name} in #{name}"
212
226
  end
213
227
 
214
- # TODO: document
215
228
  # @api private
216
229
  def parent_model?
217
230
  parent_model
@@ -220,7 +233,6 @@ module DataMapper
220
233
  false
221
234
  end
222
235
 
223
- # TODO: document
224
236
  # @api private
225
237
  def parent_model_name
226
238
  @parent_model ? parent_model.name : @parent_model_name
@@ -282,8 +294,8 @@ module DataMapper
282
294
 
283
295
  # Eager load the collection using the source as a base
284
296
  #
285
- # @param [Resource, Collection] source
286
- # the source to query with
297
+ # @param [Collection] source
298
+ # the source collection to query with
287
299
  # @param [Query, Hash] query
288
300
  # optional query to restrict the collection
289
301
  #
@@ -292,24 +304,14 @@ module DataMapper
292
304
  #
293
305
  # @api private
294
306
  def eager_load(source, query = nil)
295
- target_maps = Hash.new { |h,k| h[k] = [] }
296
-
297
- collection_query = query_for(source, query)
298
-
299
- # TODO: create an object that wraps this logic, and when the first
300
- # kicker is fired, then it'll load up the collection, and then
301
- # populate all the other methods
302
-
303
- collection = source.model.all(collection_query).each do |target|
304
- target_maps[target_key.get(target)] << target
305
- end
307
+ targets = source.model.all(query_for(source, query))
306
308
 
307
- Array(source).each do |source|
308
- key = target_key.typecast(source_key.get(source))
309
- eager_load_targets(source, target_maps[key], query)
309
+ # FIXME: cannot associate targets to m:m collection yet
310
+ if source.loaded? && !source.kind_of?(ManyToMany::Collection)
311
+ associate_targets(source, targets)
310
312
  end
311
313
 
312
- collection
314
+ targets
313
315
  end
314
316
 
315
317
  # Checks if "other end" of association is loaded on given
@@ -322,7 +324,7 @@ module DataMapper
322
324
  resource.instance_variable_defined?(instance_variable_name)
323
325
  end
324
326
 
325
- # Test the source to see if it is a valid target
327
+ # Test the resource to see if it is a valid target
326
328
  #
327
329
  # @param [Object] source
328
330
  # the resource or collection to be tested
@@ -331,14 +333,13 @@ module DataMapper
331
333
  # true if the resource is valid
332
334
  #
333
335
  # @api semipulic
334
- def valid?(source)
335
- return true if source.nil?
336
-
337
- case source
338
- when Array, Collection then valid_collection?(source)
339
- when Resource then valid_resource?(source)
336
+ def valid?(value, negated = false)
337
+ case value
338
+ when Enumerable then valid_target_collection?(value, negated)
339
+ when Resource then valid_target?(value)
340
+ when nil then true
340
341
  else
341
- raise ArgumentError, "+source+ should be an Array or Resource, but was a #{source.class.name}"
342
+ raise ArgumentError, "+value+ should be an Enumerable, Resource or nil, but was a #{value.class.name}"
342
343
  end
343
344
  end
344
345
 
@@ -367,10 +368,11 @@ module DataMapper
367
368
  # @api public
368
369
  def ==(other)
369
370
  return true if equal?(other)
370
- return false if kind_of_inverse?(other)
371
371
  other.respond_to?(:cmp_repository?, true) &&
372
372
  other.respond_to?(:cmp_model?, true) &&
373
373
  other.respond_to?(:cmp_key?, true) &&
374
+ other.respond_to?(:min) &&
375
+ other.respond_to?(:max) &&
374
376
  other.respond_to?(:query) &&
375
377
  cmp?(other, :==)
376
378
  end
@@ -381,8 +383,10 @@ module DataMapper
381
383
  def inverse
382
384
  return @inverse if defined?(@inverse)
383
385
 
384
- if kind_of_inverse?(options[:inverse])
385
- return @inverse = options[:inverse]
386
+ @inverse = options[:inverse]
387
+
388
+ if kind_of_inverse?(@inverse)
389
+ return @inverse
386
390
  end
387
391
 
388
392
  relationships = target_model.relationships(relative_target_repository_name).values
@@ -395,13 +399,11 @@ module DataMapper
395
399
  @inverse
396
400
  end
397
401
 
398
- # TODO: document
399
402
  # @api private
400
403
  def relative_target_repository_name
401
404
  target_repository_name || source_repository_name
402
405
  end
403
406
 
404
- # TODO: document
405
407
  # @api private
406
408
  def relative_target_repository_name_for(source)
407
409
  target_repository_name || if source.respond_to?(:repository)
@@ -411,13 +413,16 @@ module DataMapper
411
413
  end
412
414
  end
413
415
 
416
+ # @api private
417
+ def hash
418
+ source_model.hash ^ name.hash
419
+ end
420
+
414
421
  private
415
422
 
416
- # TODO: document
417
423
  # @api private
418
424
  attr_reader :child_properties
419
425
 
420
- # TODO: document
421
426
  # @api private
422
427
  attr_reader :parent_properties
423
428
 
@@ -457,9 +462,6 @@ module DataMapper
457
462
  # - this should provide the best performance
458
463
 
459
464
  @query = @options.except(*self.class::OPTIONS).freeze
460
-
461
- create_reader
462
- create_writer
463
465
  end
464
466
 
465
467
  # Set the correct ivars for the named object
@@ -495,40 +497,6 @@ module DataMapper
495
497
  object
496
498
  end
497
499
 
498
- # Dynamically defines reader method for source side of association
499
- # (for instance, method article for model Paragraph)
500
- #
501
- # @api semipublic
502
- def create_reader
503
- reader_name = name.to_s
504
-
505
- return if source_model.resource_method_defined?(reader_name)
506
-
507
- source_model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
508
- #{reader_visibility} # public
509
- def #{reader_name}(query = nil) # def author(query = nil)
510
- relationships[#{name.inspect}].get(self, query) # relationships[:author].get(self, query)
511
- end # end
512
- RUBY
513
- end
514
-
515
- # Dynamically defines writer method for source side of association
516
- # (for instance, method article= for model Paragraph)
517
- #
518
- # @api semipublic
519
- def create_writer
520
- writer_name = "#{name}="
521
-
522
- return if source_model.resource_method_defined?(writer_name)
523
-
524
- source_model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
525
- #{writer_visibility} # public
526
- def #{writer_name}(target) # def author=(target)
527
- relationships[#{name.inspect}].set(self, target) # relationships[:author].set(self, target)
528
- end # end
529
- RUBY
530
- end
531
-
532
500
  # Sets the association targets in the resource
533
501
  #
534
502
  # @param [Resource] source
@@ -545,24 +513,36 @@ module DataMapper
545
513
  raise NotImplementedError, "#{self.class}#eager_load_targets not implemented"
546
514
  end
547
515
 
548
- # TODO: document
549
516
  # @api private
550
- def valid_collection?(collection)
551
- if collection.instance_of?(Array) || collection.loaded?
552
- collection.all? { |resource| valid_resource?(resource) }
517
+ def valid_target_collection?(collection, negated)
518
+ if collection.kind_of?(Collection)
519
+ # TODO: move the check for model_key into Collection#reloadable?
520
+ # since what we're really checking is a Collection's ability
521
+ # to reload itself, which is (currently) only possible if the
522
+ # key was loaded.
523
+ model = target_model
524
+ model_key = model.key(repository.name)
525
+
526
+ collection.model <= model &&
527
+ (collection.query.fields & model_key) == model_key &&
528
+ (collection.loaded? ? (collection.any? || negated) : true)
553
529
  else
554
- collection.model <= target_model && (collection.query.fields & target_key) == target_key
530
+ collection.all? { |resource| valid_target?(resource) }
555
531
  end
556
532
  end
557
533
 
558
- # TODO: document
559
534
  # @api private
560
- def valid_resource?(resource)
561
- resource.kind_of?(target_model) &&
562
- target_key.zip(target_key.get!(resource)).all? { |property, value| property.valid?(value) }
535
+ def valid_target?(target)
536
+ target.kind_of?(target_model) &&
537
+ source_key.valid?(target_key.get(target))
538
+ end
539
+
540
+ # @api private
541
+ def valid_source?(source)
542
+ source.kind_of?(source_model) &&
543
+ target_key.valid?(source_key.get(source))
563
544
  end
564
545
 
565
- # TODO: document
566
546
  # @api private
567
547
  def inverse?(other)
568
548
  return true if @inverse.equal?(other)
@@ -580,35 +560,31 @@ module DataMapper
580
560
  # default scope for the target model
581
561
  end
582
562
 
583
- # TODO: document
584
563
  # @api private
585
564
  def inverse_name
586
- if options[:inverse].kind_of?(Relationship)
587
- options[:inverse].name
565
+ inverse = options[:inverse]
566
+ if inverse.kind_of?(Relationship)
567
+ inverse.name
588
568
  else
589
- options[:inverse]
569
+ inverse
590
570
  end
591
571
  end
592
572
 
593
- # TODO: document
594
573
  # @api private
595
574
  def invert
596
575
  inverse_class.new(inverse_name, child_model, parent_model, inverted_options)
597
576
  end
598
577
 
599
- # TODO: document
600
578
  # @api private
601
579
  def inverted_options
602
580
  options.only(*OPTIONS - [ :min, :max ]).update(:inverse => self)
603
581
  end
604
582
 
605
- # TODO: document
606
583
  # @api private
607
584
  def kind_of_inverse?(other)
608
585
  other.kind_of?(inverse_class)
609
586
  end
610
587
 
611
- # TODO: document
612
588
  # @api private
613
589
  def cmp?(other, operator)
614
590
  name.send(operator, other.name) &&
@@ -618,10 +594,11 @@ module DataMapper
618
594
  cmp_model?(other, operator, :parent) &&
619
595
  cmp_key?(other, operator, :child) &&
620
596
  cmp_key?(other, operator, :parent) &&
597
+ min.send(operator, other.min) &&
598
+ max.send(operator, other.max) &&
621
599
  query.send(operator, other.query)
622
600
  end
623
601
 
624
- # TODO: document
625
602
  # @api private
626
603
  def cmp_repository?(other, operator, type)
627
604
  # if either repository is nil, then the relationship is relative,
@@ -632,7 +609,6 @@ module DataMapper
632
609
  repository_name.send(operator, other_repository_name)
633
610
  end
634
611
 
635
- # TODO: document
636
612
  # @api private
637
613
  def cmp_model?(other, operator, type)
638
614
  send("#{type}_model?") &&
@@ -640,7 +616,6 @@ module DataMapper
640
616
  send("#{type}_model").base_model.send(operator, other.send("#{type}_model").base_model)
641
617
  end
642
618
 
643
- # TODO: document
644
619
  # @api private
645
620
  def cmp_key?(other, operator, type)
646
621
  property_method = "#{type}_properties"
@@ -650,6 +625,23 @@ module DataMapper
650
625
 
651
626
  self_key.send(operator, other_key)
652
627
  end
628
+
629
+ def associate_targets(source, targets)
630
+ # TODO: create an object that wraps this logic, and when the first
631
+ # kicker is fired, then it'll load up the collection, and then
632
+ # populate all the other methods
633
+
634
+ target_maps = Hash.new { |hash, key| hash[key] = [] }
635
+
636
+ targets.each do |target|
637
+ target_maps[target_key.get(target)] << target
638
+ end
639
+
640
+ Array(source).each do |source|
641
+ key = source_key.get(source)
642
+ eager_load_targets(source, target_maps[key], query)
643
+ end
644
+ end
653
645
  end # class Relationship
654
646
  end # module Associations
655
647
  end # module DataMapper