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.
- data/.autotest +29 -0
- data/.document +5 -0
- data/.gitignore +27 -0
- data/LICENSE +20 -0
- data/{README.txt → README.rdoc} +14 -3
- data/Rakefile +23 -22
- data/VERSION +1 -0
- data/dm-core.gemspec +201 -10
- data/lib/dm-core.rb +32 -23
- data/lib/dm-core/adapters.rb +0 -1
- data/lib/dm-core/adapters/data_objects_adapter.rb +230 -151
- data/lib/dm-core/adapters/mysql_adapter.rb +7 -8
- data/lib/dm-core/adapters/oracle_adapter.rb +39 -59
- data/lib/dm-core/adapters/postgres_adapter.rb +0 -1
- data/lib/dm-core/adapters/sqlite3_adapter.rb +5 -0
- data/lib/dm-core/adapters/sqlserver_adapter.rb +114 -0
- data/lib/dm-core/adapters/yaml_adapter.rb +0 -5
- data/lib/dm-core/associations/many_to_many.rb +118 -56
- data/lib/dm-core/associations/many_to_one.rb +48 -21
- data/lib/dm-core/associations/one_to_many.rb +8 -30
- data/lib/dm-core/associations/one_to_one.rb +1 -5
- data/lib/dm-core/associations/relationship.rb +89 -97
- data/lib/dm-core/collection.rb +299 -184
- data/lib/dm-core/core_ext/enumerable.rb +28 -0
- data/lib/dm-core/core_ext/kernel.rb +0 -2
- data/lib/dm-core/migrations.rb +314 -170
- data/lib/dm-core/model.rb +97 -66
- data/lib/dm-core/model/descendant_set.rb +1 -1
- data/lib/dm-core/model/hook.rb +0 -3
- data/lib/dm-core/model/property.rb +7 -10
- data/lib/dm-core/model/relationship.rb +79 -26
- data/lib/dm-core/model/scope.rb +3 -4
- data/lib/dm-core/property.rb +152 -90
- data/lib/dm-core/property_set.rb +18 -37
- data/lib/dm-core/query.rb +452 -153
- data/lib/dm-core/query/conditions/comparison.rb +266 -173
- data/lib/dm-core/query/conditions/operation.rb +499 -57
- data/lib/dm-core/query/direction.rb +0 -3
- data/lib/dm-core/query/operator.rb +0 -4
- data/lib/dm-core/query/path.rb +10 -12
- data/lib/dm-core/query/sort.rb +4 -10
- data/lib/dm-core/repository.rb +10 -6
- data/lib/dm-core/resource.rb +343 -148
- data/lib/dm-core/spec/adapter_shared_spec.rb +17 -1
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +277 -17
- data/lib/dm-core/support/chainable.rb +0 -2
- data/lib/dm-core/support/equalizer.rb +27 -3
- data/lib/dm-core/transaction.rb +75 -75
- data/lib/dm-core/type.rb +19 -5
- data/lib/dm-core/types/discriminator.rb +4 -4
- data/lib/dm-core/types/object.rb +2 -7
- data/lib/dm-core/types/paranoid_boolean.rb +8 -2
- data/lib/dm-core/types/paranoid_datetime.rb +8 -2
- data/lib/dm-core/version.rb +1 -1
- data/script/performance.rb +7 -7
- data/script/profile.rb +6 -6
- data/spec/lib/collection_helpers.rb +2 -2
- data/spec/lib/pending_helpers.rb +22 -3
- data/spec/lib/rspec_immediate_feedback_formatter.rb +1 -0
- data/spec/public/associations/many_to_many_spec.rb +6 -4
- data/spec/public/associations/many_to_one_spec.rb +10 -1
- data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +39 -0
- data/spec/public/associations/one_to_many_spec.rb +4 -3
- data/spec/public/associations/one_to_one_spec.rb +19 -1
- data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +45 -0
- data/spec/public/collection_spec.rb +4 -3
- data/spec/public/migrations_spec.rb +144 -0
- data/spec/public/model/relationship_spec.rb +115 -55
- data/spec/public/model_spec.rb +13 -13
- data/spec/public/property/object_spec.rb +106 -0
- data/spec/public/property_spec.rb +18 -14
- data/spec/public/resource_spec.rb +10 -1
- data/spec/public/sel_spec.rb +16 -49
- data/spec/public/setup_spec.rb +1 -1
- data/spec/public/shared/association_collection_shared_spec.rb +6 -14
- data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
- data/spec/public/shared/collection_shared_spec.rb +214 -217
- data/spec/public/shared/finder_shared_spec.rb +259 -365
- data/spec/public/shared/resource_shared_spec.rb +524 -248
- data/spec/public/transaction_spec.rb +27 -3
- data/spec/public/types/discriminator_spec.rb +1 -1
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +17 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +3 -20
- data/spec/semipublic/associations_spec.rb +2 -2
- data/spec/semipublic/collection_spec.rb +0 -32
- data/spec/semipublic/model_spec.rb +96 -0
- data/spec/semipublic/property_spec.rb +3 -3
- data/spec/semipublic/query/conditions/comparison_spec.rb +1719 -0
- data/spec/semipublic/query/conditions/operation_spec.rb +1292 -0
- data/spec/semipublic/query_spec.rb +1285 -144
- data/spec/semipublic/resource_spec.rb +0 -24
- data/spec/semipublic/shared/resource_shared_spec.rb +103 -38
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +15 -6
- data/tasks/ci.rake +1 -0
- data/tasks/metrics.rake +37 -0
- data/tasks/spec.rake +41 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +99 -29
- data/CONTRIBUTING +0 -51
- data/FAQ +0 -93
- data/History.txt +0 -27
- data/MIT-LICENSE +0 -22
- data/Manifest.txt +0 -121
- data/QUICKLINKS +0 -11
- data/SPECS +0 -35
- data/TODO +0 -1
- data/spec/semipublic/query/conditions_spec.rb +0 -528
- data/tasks/ci.rb +0 -24
- data/tasks/dm.rb +0 -58
- data/tasks/doc.rb +0 -17
- data/tasks/gemspec.rb +0 -23
- data/tasks/hoe.rb +0 -45
- data/tasks/install.rb +0 -18
data/lib/dm-core/collection.rb
CHANGED
@@ -65,8 +65,9 @@ module DataMapper
|
|
65
65
|
# @return [self]
|
66
66
|
#
|
67
67
|
# @api public
|
68
|
-
def reload(
|
69
|
-
query =
|
68
|
+
def reload(other_query = nil)
|
69
|
+
query = self.query
|
70
|
+
query = other_query.nil? ? query.dup : query.merge(other_query)
|
70
71
|
|
71
72
|
# make sure the Identity Map contains all the existing resources
|
72
73
|
identity_map = repository.identity_map(model)
|
@@ -75,20 +76,60 @@ module DataMapper
|
|
75
76
|
identity_map[resource.key] = resource
|
76
77
|
end
|
77
78
|
|
78
|
-
properties = model.properties(repository.name)
|
79
|
-
fields = properties.key | query.fields
|
80
|
-
|
81
|
-
if discriminator = properties.discriminator
|
82
|
-
fields |= [ discriminator ]
|
83
|
-
end
|
84
|
-
|
85
79
|
# sort fields based on declared order, for more consistent reload queries
|
86
|
-
|
80
|
+
properties = self.properties
|
81
|
+
fields = properties & (query.fields | model_key | [ properties.discriminator ].compact)
|
87
82
|
|
88
83
|
# replace the list of resources
|
89
84
|
replace(all(query.update(:fields => fields, :reload => true)))
|
90
85
|
end
|
91
86
|
|
87
|
+
# Return the union with another collection
|
88
|
+
#
|
89
|
+
# @param [Collection] other
|
90
|
+
# the other collection
|
91
|
+
#
|
92
|
+
# @return [Collection]
|
93
|
+
# the union of the collection and other
|
94
|
+
#
|
95
|
+
# @api public
|
96
|
+
def union(other)
|
97
|
+
set_operation(:|, other)
|
98
|
+
end
|
99
|
+
|
100
|
+
alias | union
|
101
|
+
alias + union
|
102
|
+
|
103
|
+
# Return the intersection with another collection
|
104
|
+
#
|
105
|
+
# @param [Collection] other
|
106
|
+
# the other collection
|
107
|
+
#
|
108
|
+
# @return [Collection]
|
109
|
+
# the intersection of the collection and other
|
110
|
+
#
|
111
|
+
# @api public
|
112
|
+
def intersection(other)
|
113
|
+
set_operation(:&, other)
|
114
|
+
end
|
115
|
+
|
116
|
+
alias & intersection
|
117
|
+
|
118
|
+
# Return the difference with another collection
|
119
|
+
#
|
120
|
+
# @param [Collection] other
|
121
|
+
# the other collection
|
122
|
+
#
|
123
|
+
# @return [Collection]
|
124
|
+
# the difference of the collection and other
|
125
|
+
#
|
126
|
+
# @api public
|
127
|
+
def difference(other)
|
128
|
+
set_operation(:-, other)
|
129
|
+
end
|
130
|
+
|
131
|
+
alias - difference
|
132
|
+
|
92
133
|
# Lookup a Resource in the Collection by key
|
93
134
|
#
|
94
135
|
# This looksup a Resource by key, typecasting the key to the
|
@@ -108,9 +149,12 @@ module DataMapper
|
|
108
149
|
#
|
109
150
|
# @api public
|
110
151
|
def get(*key)
|
111
|
-
|
152
|
+
assert_valid_key_size(key)
|
153
|
+
|
154
|
+
key = model_key.typecast(key)
|
155
|
+
query = self.query
|
112
156
|
|
113
|
-
|
157
|
+
@identity_map[key] || if !loaded? && (query.limit || query.offset > 0)
|
114
158
|
# current query is exclusive, find resource within the set
|
115
159
|
|
116
160
|
# TODO: use a subquery to retrieve the Collection and then match
|
@@ -128,10 +172,6 @@ module DataMapper
|
|
128
172
|
# current query is all inclusive, lookup using normal approach
|
129
173
|
first(model.key_conditions(repository, key))
|
130
174
|
end
|
131
|
-
|
132
|
-
return if resource.nil?
|
133
|
-
|
134
|
-
orphan_resource(resource)
|
135
175
|
end
|
136
176
|
|
137
177
|
# Lookup a Resource in the Collection by key, raising an exception if not found
|
@@ -175,12 +215,13 @@ module DataMapper
|
|
175
215
|
#
|
176
216
|
# @api public
|
177
217
|
def all(query = nil)
|
218
|
+
# TODO: update this not to accept a nil value, and instead either
|
219
|
+
# accept a Hash/Query and nothing else
|
178
220
|
if query.nil? || (query.kind_of?(Hash) && query.empty?)
|
179
221
|
dup
|
180
222
|
else
|
181
223
|
# TODO: if there is no order parameter, and the Collection is not loaded
|
182
224
|
# check to see if the query can be satisfied by the head/tail
|
183
|
-
|
184
225
|
new_collection(scoped_query(query))
|
185
226
|
end
|
186
227
|
end
|
@@ -203,13 +244,16 @@ module DataMapper
|
|
203
244
|
#
|
204
245
|
# @api public
|
205
246
|
def first(*args)
|
206
|
-
|
247
|
+
first_arg = args.first
|
248
|
+
last_arg = args.last
|
207
249
|
|
208
|
-
|
209
|
-
with_query
|
250
|
+
limit_specified = first_arg.kind_of?(Integer)
|
251
|
+
with_query = (last_arg.kind_of?(Hash) && !last_arg.empty?) || last_arg.kind_of?(Query)
|
210
252
|
|
211
|
-
|
212
|
-
query =
|
253
|
+
limit = limit_specified ? first_arg : 1
|
254
|
+
query = with_query ? last_arg : {}
|
255
|
+
|
256
|
+
query = self.query.slice(0, limit).update(query)
|
213
257
|
|
214
258
|
# TODO: when a query provided, and there are enough elements in head to
|
215
259
|
# satisfy the query.limit, filter the head with the query, and make
|
@@ -217,16 +261,23 @@ module DataMapper
|
|
217
261
|
# of calling all()
|
218
262
|
# - this can probably only be done if there is no :order parameter
|
219
263
|
|
220
|
-
|
221
|
-
|
264
|
+
loaded = loaded?
|
265
|
+
head = self.head
|
266
|
+
|
267
|
+
collection = if !with_query && (loaded || lazy_possible?(head, limit))
|
268
|
+
new_collection(query, super(limit))
|
222
269
|
else
|
223
270
|
all(query)
|
224
271
|
end
|
225
272
|
|
226
|
-
if
|
227
|
-
|
228
|
-
|
229
|
-
|
273
|
+
return collection if limit_specified
|
274
|
+
|
275
|
+
resource = collection.to_a.first
|
276
|
+
|
277
|
+
if with_query || loaded
|
278
|
+
resource
|
279
|
+
elsif resource
|
280
|
+
head[0] = resource
|
230
281
|
end
|
231
282
|
end
|
232
283
|
|
@@ -248,13 +299,16 @@ module DataMapper
|
|
248
299
|
#
|
249
300
|
# @api public
|
250
301
|
def last(*args)
|
251
|
-
|
302
|
+
first_arg = args.first
|
303
|
+
last_arg = args.last
|
304
|
+
|
305
|
+
limit_specified = first_arg.kind_of?(Integer)
|
306
|
+
with_query = (last_arg.kind_of?(Hash) && !last_arg.empty?) || last_arg.kind_of?(Query)
|
252
307
|
|
253
|
-
limit
|
254
|
-
|
308
|
+
limit = limit_specified ? first_arg : 1
|
309
|
+
query = with_query ? last_arg : {}
|
255
310
|
|
256
|
-
query =
|
257
|
-
query = self.query.slice(0, limit || 1).update(query).reverse!
|
311
|
+
query = self.query.slice(0, limit).update(query).reverse!
|
258
312
|
|
259
313
|
# tell the Query to prepend each result from the adapter
|
260
314
|
query.update(:add_reversed => !query.add_reversed?)
|
@@ -264,16 +318,23 @@ module DataMapper
|
|
264
318
|
# sure it matches the limit exactly. if so, use that result instead
|
265
319
|
# of calling all()
|
266
320
|
|
267
|
-
|
268
|
-
|
321
|
+
loaded = loaded?
|
322
|
+
tail = self.tail
|
323
|
+
|
324
|
+
collection = if !with_query && (loaded || lazy_possible?(tail, limit))
|
325
|
+
new_collection(query, super(limit))
|
269
326
|
else
|
270
327
|
all(query)
|
271
328
|
end
|
272
329
|
|
273
|
-
if
|
274
|
-
|
275
|
-
|
276
|
-
|
330
|
+
return collection if limit_specified
|
331
|
+
|
332
|
+
resource = collection.to_a.last
|
333
|
+
|
334
|
+
if with_query || loaded
|
335
|
+
resource
|
336
|
+
elsif resource
|
337
|
+
tail[tail.empty? ? 0 : -1] = resource
|
277
338
|
end
|
278
339
|
end
|
279
340
|
|
@@ -290,8 +351,7 @@ module DataMapper
|
|
290
351
|
# @api public
|
291
352
|
def at(offset)
|
292
353
|
if loaded? || partially_loaded?(offset)
|
293
|
-
|
294
|
-
orphan_resource(resource)
|
354
|
+
super
|
295
355
|
elsif offset >= 0
|
296
356
|
first(:offset => offset)
|
297
357
|
else
|
@@ -401,9 +461,6 @@ module DataMapper
|
|
401
461
|
# mark resources as removed
|
402
462
|
resources_removed(orphans - loaded_entries)
|
403
463
|
|
404
|
-
# ensure remaining orphans are still related
|
405
|
-
(orphans & loaded_entries).each { |resource| relate_resource(resource) }
|
406
|
-
|
407
464
|
resources
|
408
465
|
end
|
409
466
|
|
@@ -438,6 +495,24 @@ module DataMapper
|
|
438
495
|
self
|
439
496
|
end
|
440
497
|
|
498
|
+
# Iterate over each Resource
|
499
|
+
#
|
500
|
+
# @yield [Resource] Each resource in the collection
|
501
|
+
#
|
502
|
+
# @return [self]
|
503
|
+
#
|
504
|
+
# @api public
|
505
|
+
def each
|
506
|
+
super do |resource|
|
507
|
+
begin
|
508
|
+
original, resource.collection = resource.collection, self
|
509
|
+
yield resource
|
510
|
+
ensure
|
511
|
+
resource.collection = original
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
441
516
|
# Invoke the block for each resource and replace it the return value
|
442
517
|
#
|
443
518
|
# @yield [Resource] Each resource in the collection
|
@@ -460,12 +535,7 @@ module DataMapper
|
|
460
535
|
#
|
461
536
|
# @api public
|
462
537
|
def <<(resource)
|
463
|
-
|
464
|
-
resource = new(resource)
|
465
|
-
end
|
466
|
-
|
467
|
-
resource_added(resource)
|
468
|
-
super
|
538
|
+
super(resource_added(resource))
|
469
539
|
end
|
470
540
|
|
471
541
|
# Appends the resources to self
|
@@ -477,8 +547,7 @@ module DataMapper
|
|
477
547
|
#
|
478
548
|
# @api public
|
479
549
|
def concat(resources)
|
480
|
-
resources_added(resources)
|
481
|
-
super
|
550
|
+
super(resources_added(resources))
|
482
551
|
end
|
483
552
|
|
484
553
|
# Append one or more Resources to the Collection
|
@@ -493,8 +562,7 @@ module DataMapper
|
|
493
562
|
#
|
494
563
|
# @api public
|
495
564
|
def push(*resources)
|
496
|
-
resources_added(resources)
|
497
|
-
super
|
565
|
+
super(*resources_added(resources))
|
498
566
|
end
|
499
567
|
|
500
568
|
# Prepend one or more Resources to the Collection
|
@@ -509,8 +577,7 @@ module DataMapper
|
|
509
577
|
#
|
510
578
|
# @api public
|
511
579
|
def unshift(*resources)
|
512
|
-
resources_added(resources)
|
513
|
-
super
|
580
|
+
super(*resources_added(resources))
|
514
581
|
end
|
515
582
|
|
516
583
|
# Inserts the Resources before the Resource at the offset (which may be negative).
|
@@ -524,8 +591,7 @@ module DataMapper
|
|
524
591
|
#
|
525
592
|
# @api public
|
526
593
|
def insert(offset, *resources)
|
527
|
-
resources_added(resources)
|
528
|
-
super
|
594
|
+
super(offset, *resources_added(resources))
|
529
595
|
end
|
530
596
|
|
531
597
|
# Removes and returns the last Resource in the Collection
|
@@ -619,6 +685,12 @@ module DataMapper
|
|
619
685
|
super { |resource| yield(resource) && resource_removed(resource) }
|
620
686
|
end
|
621
687
|
|
688
|
+
# Access LazyArray#replace directly
|
689
|
+
#
|
690
|
+
# @api private
|
691
|
+
alias superclass_replace replace
|
692
|
+
private :superclass_replace
|
693
|
+
|
622
694
|
# Replace the Resources within the Collection
|
623
695
|
#
|
624
696
|
# @param [Enumerable] other
|
@@ -628,26 +700,23 @@ module DataMapper
|
|
628
700
|
#
|
629
701
|
# @api public
|
630
702
|
def replace(other)
|
631
|
-
other = other
|
632
|
-
|
633
|
-
|
634
|
-
else
|
635
|
-
resource
|
636
|
-
end
|
637
|
-
end
|
638
|
-
|
639
|
-
if loaded?
|
640
|
-
resources_removed(self - other)
|
641
|
-
end
|
642
|
-
|
643
|
-
super(resources_added(other))
|
703
|
+
other = resources_added(other)
|
704
|
+
resources_removed(entries - other)
|
705
|
+
super(other)
|
644
706
|
end
|
645
707
|
|
646
|
-
#
|
708
|
+
# (Private) Set the Collection
|
709
|
+
#
|
710
|
+
# @param [Array] resources
|
711
|
+
# resources to add to the collection
|
712
|
+
#
|
713
|
+
# @return [self]
|
647
714
|
#
|
648
715
|
# @api private
|
649
|
-
|
650
|
-
|
716
|
+
def set(resources)
|
717
|
+
superclass_replace(resources_added(resources))
|
718
|
+
self
|
719
|
+
end
|
651
720
|
|
652
721
|
# Removes all Resources from the Collection
|
653
722
|
#
|
@@ -766,11 +835,13 @@ module DataMapper
|
|
766
835
|
def update!(attributes = {})
|
767
836
|
assert_update_clean_only(:update!)
|
768
837
|
|
838
|
+
model = self.model
|
839
|
+
|
769
840
|
dirty_attributes = model.new(attributes).dirty_attributes
|
770
841
|
|
771
842
|
if dirty_attributes.empty?
|
772
843
|
true
|
773
|
-
elsif dirty_attributes.any? { |property, value| !property.
|
844
|
+
elsif dirty_attributes.any? { |property, value| !property.valid?(value) }
|
774
845
|
false
|
775
846
|
else
|
776
847
|
unless _update(dirty_attributes)
|
@@ -836,21 +907,18 @@ module DataMapper
|
|
836
907
|
#
|
837
908
|
# @api public
|
838
909
|
def destroy!
|
839
|
-
|
840
|
-
|
841
|
-
conditions = Query.target_conditions(self, key, key)
|
910
|
+
repository = self.repository
|
911
|
+
deleted = repository.delete(self)
|
842
912
|
|
843
|
-
|
913
|
+
if loaded?
|
914
|
+
unless deleted == size
|
844
915
|
return false
|
845
916
|
end
|
846
|
-
else
|
847
|
-
repository.delete(self)
|
848
|
-
mark_loaded
|
849
|
-
end
|
850
917
|
|
851
|
-
if loaded?
|
852
918
|
each { |resource| resource.reset }
|
853
919
|
clear
|
920
|
+
else
|
921
|
+
mark_loaded
|
854
922
|
end
|
855
923
|
|
856
924
|
true
|
@@ -884,11 +952,11 @@ module DataMapper
|
|
884
952
|
# Checks if any resources have unsaved changes
|
885
953
|
#
|
886
954
|
# @return [Boolean]
|
887
|
-
# true if
|
955
|
+
# true if the resources have unsaved changed
|
888
956
|
#
|
889
957
|
# @api public
|
890
958
|
def dirty?
|
891
|
-
loaded_entries.any? { |resource| resource.dirty? }
|
959
|
+
loaded_entries.any? { |resource| resource.dirty? } || @removed.any?
|
892
960
|
end
|
893
961
|
|
894
962
|
# Gets a Human-readable representation of this collection,
|
@@ -902,14 +970,41 @@ module DataMapper
|
|
902
970
|
"[#{map { |resource| resource.inspect }.join(', ')}]"
|
903
971
|
end
|
904
972
|
|
973
|
+
# @api semipublic
|
974
|
+
def hash
|
975
|
+
query.hash
|
976
|
+
end
|
977
|
+
|
978
|
+
protected
|
979
|
+
|
980
|
+
# Returns the model key
|
981
|
+
#
|
982
|
+
# @return [PropertySet]
|
983
|
+
# the model key
|
984
|
+
#
|
985
|
+
# @api private
|
986
|
+
def model_key
|
987
|
+
model.key(repository_name)
|
988
|
+
end
|
989
|
+
|
990
|
+
# Loaded Resources in the collection
|
991
|
+
#
|
992
|
+
# @return [Array<Resource>]
|
993
|
+
# Resources in the collection
|
994
|
+
#
|
995
|
+
# @api private
|
996
|
+
def loaded_entries
|
997
|
+
(loaded? ? self : head + tail).reject { |resource| resource.destroyed? }
|
998
|
+
end
|
999
|
+
|
905
1000
|
# Returns the PropertySet representing the fields in the Collection scope
|
906
1001
|
#
|
907
1002
|
# @return [PropertySet]
|
908
1003
|
# The set of properties this Collection's query will retrieve
|
909
1004
|
#
|
910
|
-
# @api
|
1005
|
+
# @api private
|
911
1006
|
def properties
|
912
|
-
|
1007
|
+
model.properties(repository_name)
|
913
1008
|
end
|
914
1009
|
|
915
1010
|
# Returns the Relationships for the Collection's Model
|
@@ -918,9 +1013,9 @@ module DataMapper
|
|
918
1013
|
# The model's relationships, mapping the name to the
|
919
1014
|
# Associations::Relationship object
|
920
1015
|
#
|
921
|
-
# @api
|
1016
|
+
# @api private
|
922
1017
|
def relationships
|
923
|
-
model.relationships(
|
1018
|
+
model.relationships(repository_name)
|
924
1019
|
end
|
925
1020
|
|
926
1021
|
private
|
@@ -947,9 +1042,7 @@ module DataMapper
|
|
947
1042
|
# TODO: change LazyArray to not use a load proc at all
|
948
1043
|
remove_instance_variable(:@load_with_proc)
|
949
1044
|
|
950
|
-
if resources
|
951
|
-
replace(resources)
|
952
|
-
end
|
1045
|
+
set(resources) if resources
|
953
1046
|
end
|
954
1047
|
|
955
1048
|
# Copies the original Collection state
|
@@ -967,6 +1060,19 @@ module DataMapper
|
|
967
1060
|
@removed = @removed.dup
|
968
1061
|
end
|
969
1062
|
|
1063
|
+
# Initialize a resource from a Hash
|
1064
|
+
#
|
1065
|
+
# @param [Resource, Hash] resource
|
1066
|
+
# resource to process
|
1067
|
+
#
|
1068
|
+
# @return [Resource]
|
1069
|
+
# an initialized resource
|
1070
|
+
#
|
1071
|
+
# @api private
|
1072
|
+
def initialize_resource(resource)
|
1073
|
+
resource.kind_of?(Hash) ? new(resource) : resource
|
1074
|
+
end
|
1075
|
+
|
970
1076
|
# Test if the collection is loaded between the offset and limit
|
971
1077
|
#
|
972
1078
|
# @param [Integer] offset
|
@@ -998,6 +1104,10 @@ module DataMapper
|
|
998
1104
|
|
999
1105
|
mark_loaded
|
1000
1106
|
|
1107
|
+
head = self.head
|
1108
|
+
tail = self.tail
|
1109
|
+
query = self.query
|
1110
|
+
|
1001
1111
|
resources = repository.read(query)
|
1002
1112
|
|
1003
1113
|
# remove already known results
|
@@ -1018,14 +1128,14 @@ module DataMapper
|
|
1018
1128
|
self
|
1019
1129
|
end
|
1020
1130
|
|
1021
|
-
#
|
1131
|
+
# Returns the Query Repository name
|
1022
1132
|
#
|
1023
|
-
# @return [
|
1024
|
-
#
|
1133
|
+
# @return [Symbol]
|
1134
|
+
# the repository name
|
1025
1135
|
#
|
1026
1136
|
# @api private
|
1027
|
-
def
|
1028
|
-
|
1137
|
+
def repository_name
|
1138
|
+
repository.name
|
1029
1139
|
end
|
1030
1140
|
|
1031
1141
|
# Initializes a new Collection
|
@@ -1046,6 +1156,40 @@ module DataMapper
|
|
1046
1156
|
self.class.new(query, resources, &block)
|
1047
1157
|
end
|
1048
1158
|
|
1159
|
+
# Apply a set operation on self and another collection
|
1160
|
+
#
|
1161
|
+
# @param [Symbol] operation
|
1162
|
+
# the set operation to apply
|
1163
|
+
# @param [Collection] other
|
1164
|
+
# the other collection to apply the set operation on
|
1165
|
+
#
|
1166
|
+
# @return [Collection]
|
1167
|
+
# the collection that was created for the set operation
|
1168
|
+
#
|
1169
|
+
# @api private
|
1170
|
+
def set_operation(operation, other)
|
1171
|
+
resources = set_operation_resources(operation, other)
|
1172
|
+
other_query = Query.target_query(repository, model, other)
|
1173
|
+
new_collection(query.send(operation, other_query), resources)
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
# Prepopulate the set operation if the collection is loaded
|
1177
|
+
#
|
1178
|
+
# @param [Symbol] operation
|
1179
|
+
# the set operation to apply
|
1180
|
+
# @param [Collection] other
|
1181
|
+
# the other collection to apply the set operation on
|
1182
|
+
#
|
1183
|
+
# @return [nil]
|
1184
|
+
# nil if the Collection is not loaded
|
1185
|
+
# @return [Array]
|
1186
|
+
# the resources to prepopulate the set operation results with
|
1187
|
+
#
|
1188
|
+
# @api private
|
1189
|
+
def set_operation_resources(operation, other)
|
1190
|
+
entries.send(operation, other.entries) if loaded?
|
1191
|
+
end
|
1192
|
+
|
1049
1193
|
# Creates a resource in the collection
|
1050
1194
|
#
|
1051
1195
|
# @param [Boolean] safe
|
@@ -1070,19 +1214,7 @@ module DataMapper
|
|
1070
1214
|
#
|
1071
1215
|
# @api private
|
1072
1216
|
def _update(dirty_attributes)
|
1073
|
-
|
1074
|
-
attributes = dirty_attributes.map { |property, value| [ property.name, value ] }.to_hash
|
1075
|
-
|
1076
|
-
key = model.key(repository.name)
|
1077
|
-
conditions = Query.target_conditions(self, key, key)
|
1078
|
-
|
1079
|
-
unless model.all(:repository => repository, :conditions => conditions).update!(attributes)
|
1080
|
-
return false
|
1081
|
-
end
|
1082
|
-
else
|
1083
|
-
repository.update(dirty_attributes, self)
|
1084
|
-
end
|
1085
|
-
|
1217
|
+
repository.update(dirty_attributes, self)
|
1086
1218
|
true
|
1087
1219
|
end
|
1088
1220
|
|
@@ -1096,9 +1228,10 @@ module DataMapper
|
|
1096
1228
|
#
|
1097
1229
|
# @api private
|
1098
1230
|
def _save(safe)
|
1231
|
+
loaded_entries = self.loaded_entries
|
1099
1232
|
loaded_entries.each { |resource| set_default_attributes(resource) }
|
1100
1233
|
@removed.clear
|
1101
|
-
loaded_entries.all? { |resource| resource.
|
1234
|
+
loaded_entries.all? { |resource| resource.__send__(safe ? :save : :save!) }
|
1102
1235
|
end
|
1103
1236
|
|
1104
1237
|
# Returns default values to initialize new Resources in the Collection
|
@@ -1113,23 +1246,21 @@ module DataMapper
|
|
1113
1246
|
|
1114
1247
|
conditions = query.conditions
|
1115
1248
|
|
1116
|
-
if conditions.
|
1117
|
-
|
1118
|
-
|
1119
|
-
properties = model.properties(repository_name)
|
1120
|
-
key = model.key(repository_name)
|
1249
|
+
if conditions.slug == :and
|
1250
|
+
model_properties = properties.dup
|
1251
|
+
model_key = self.model_key
|
1121
1252
|
|
1122
|
-
|
1123
|
-
|
1124
|
-
if query.condition_properties.to_set.superset?(key.to_set)
|
1125
|
-
properties -= key
|
1253
|
+
if model_properties.to_set.superset?(model_key.to_set)
|
1254
|
+
model_properties -= model_key
|
1126
1255
|
end
|
1127
1256
|
|
1128
1257
|
conditions.each do |condition|
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1258
|
+
next unless condition.slug == :eql
|
1259
|
+
|
1260
|
+
subject = condition.subject
|
1261
|
+
next unless model_properties.include?(subject) || (condition.relationship? && subject.source_model == model)
|
1262
|
+
|
1263
|
+
default_attributes[subject] = condition.value
|
1133
1264
|
end
|
1134
1265
|
end
|
1135
1266
|
|
@@ -1145,55 +1276,11 @@ module DataMapper
|
|
1145
1276
|
#
|
1146
1277
|
# @api private
|
1147
1278
|
def set_default_attributes(resource)
|
1148
|
-
unless resource.
|
1279
|
+
unless resource.readonly?
|
1149
1280
|
resource.attributes = default_attributes
|
1150
1281
|
end
|
1151
1282
|
end
|
1152
1283
|
|
1153
|
-
# Relates a Resource to the Collection
|
1154
|
-
#
|
1155
|
-
# This is used by SEL related code to reload a Resource and the
|
1156
|
-
# Collection it belongs to.
|
1157
|
-
#
|
1158
|
-
# @param [Resource] resource
|
1159
|
-
# The Resource to relate
|
1160
|
-
#
|
1161
|
-
# @return [Resource]
|
1162
|
-
# If Resource was successfully related
|
1163
|
-
# @return [nil]
|
1164
|
-
# If a nil resource was provided
|
1165
|
-
#
|
1166
|
-
# @api private
|
1167
|
-
def relate_resource(resource)
|
1168
|
-
unless resource.frozen?
|
1169
|
-
resource.collection = self
|
1170
|
-
end
|
1171
|
-
|
1172
|
-
resource
|
1173
|
-
end
|
1174
|
-
|
1175
|
-
# Orphans a Resource from the Collection
|
1176
|
-
#
|
1177
|
-
# Removes the association between the Resource and Collection so that
|
1178
|
-
# SEL related code will not load the Collection.
|
1179
|
-
#
|
1180
|
-
# @param [Resource] resource
|
1181
|
-
# The Resource to orphan
|
1182
|
-
#
|
1183
|
-
# @return [Resource]
|
1184
|
-
# The Resource that was orphaned
|
1185
|
-
# @return [nil]
|
1186
|
-
# If a nil resource was provided
|
1187
|
-
#
|
1188
|
-
# @api private
|
1189
|
-
def orphan_resource(resource)
|
1190
|
-
if resource.collection.equal?(self) && !resource.frozen?
|
1191
|
-
resource.collection = nil
|
1192
|
-
end
|
1193
|
-
|
1194
|
-
resource
|
1195
|
-
end
|
1196
|
-
|
1197
1284
|
# Track the added resource
|
1198
1285
|
#
|
1199
1286
|
# @param [Resource] resource
|
@@ -1204,6 +1291,8 @@ module DataMapper
|
|
1204
1291
|
#
|
1205
1292
|
# @api private
|
1206
1293
|
def resource_added(resource)
|
1294
|
+
resource = initialize_resource(resource)
|
1295
|
+
|
1207
1296
|
if resource.saved?
|
1208
1297
|
@identity_map[resource.key] = resource
|
1209
1298
|
@removed.delete(resource)
|
@@ -1211,7 +1300,7 @@ module DataMapper
|
|
1211
1300
|
set_default_attributes(resource)
|
1212
1301
|
end
|
1213
1302
|
|
1214
|
-
|
1303
|
+
resource
|
1215
1304
|
end
|
1216
1305
|
|
1217
1306
|
# Track the added resources
|
@@ -1225,7 +1314,7 @@ module DataMapper
|
|
1225
1314
|
# @api private
|
1226
1315
|
def resources_added(resources)
|
1227
1316
|
if resources.kind_of?(Enumerable)
|
1228
|
-
resources.
|
1317
|
+
resources.map { |resource| resource_added(resource) }
|
1229
1318
|
else
|
1230
1319
|
resource_added(resources)
|
1231
1320
|
end
|
@@ -1246,7 +1335,7 @@ module DataMapper
|
|
1246
1335
|
@removed << resource
|
1247
1336
|
end
|
1248
1337
|
|
1249
|
-
|
1338
|
+
resource
|
1250
1339
|
end
|
1251
1340
|
|
1252
1341
|
# Track the removed resources
|
@@ -1277,17 +1366,20 @@ module DataMapper
|
|
1277
1366
|
# nil if no resources match the Query
|
1278
1367
|
#
|
1279
1368
|
# @api private
|
1280
|
-
def filter(
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
query.
|
1288
|
-
|
1369
|
+
def filter(other_query)
|
1370
|
+
query = self.query
|
1371
|
+
fields = query.fields.to_set
|
1372
|
+
unique = other_query.unique?
|
1373
|
+
|
1374
|
+
# TODO: push this into a Query#subset? method
|
1375
|
+
if other_query.links.empty? &&
|
1376
|
+
(unique || (!unique && !query.unique?)) &&
|
1377
|
+
!other_query.reload? &&
|
1378
|
+
!other_query.raw? &&
|
1379
|
+
other_query.fields.to_set.subset?(fields) &&
|
1380
|
+
other_query.condition_properties.subset?(fields)
|
1289
1381
|
then
|
1290
|
-
|
1382
|
+
other_query.filter_records(to_a.dup)
|
1291
1383
|
end
|
1292
1384
|
end
|
1293
1385
|
|
@@ -1340,6 +1432,8 @@ module DataMapper
|
|
1340
1432
|
#
|
1341
1433
|
# @api public
|
1342
1434
|
def method_missing(method, *args, &block)
|
1435
|
+
relationships = self.relationships
|
1436
|
+
|
1343
1437
|
if model.model_method_defined?(method)
|
1344
1438
|
delegate_to_model(method, *args, &block)
|
1345
1439
|
elsif relationship = relationships[method] || relationships[method.to_s.singular.to_sym]
|
@@ -1361,6 +1455,7 @@ module DataMapper
|
|
1361
1455
|
#
|
1362
1456
|
# @api private
|
1363
1457
|
def delegate_to_model(method, *args, &block)
|
1458
|
+
model = self.model
|
1364
1459
|
model.__send__(:with_scope, query) do
|
1365
1460
|
model.send(method, *args, &block)
|
1366
1461
|
end
|
@@ -1386,7 +1481,27 @@ module DataMapper
|
|
1386
1481
|
# @api private
|
1387
1482
|
def assert_update_clean_only(method)
|
1388
1483
|
if dirty?
|
1389
|
-
raise UpdateConflictError, "##{method} cannot be called on a dirty collection"
|
1484
|
+
raise UpdateConflictError, "#{self.class}##{method} cannot be called on a dirty collection"
|
1485
|
+
end
|
1486
|
+
end
|
1487
|
+
|
1488
|
+
# Raises an exception if #get receives the wrong number of arguments
|
1489
|
+
#
|
1490
|
+
# @param [Array] key
|
1491
|
+
# the key value
|
1492
|
+
#
|
1493
|
+
# @return [undefined]
|
1494
|
+
#
|
1495
|
+
# @raise [UpdateConflictError]
|
1496
|
+
# raise if the resource is dirty
|
1497
|
+
#
|
1498
|
+
# @api private
|
1499
|
+
def assert_valid_key_size(key)
|
1500
|
+
expected_key_size = model_key.size
|
1501
|
+
actual_key_size = key.size
|
1502
|
+
|
1503
|
+
if actual_key_size != expected_key_size
|
1504
|
+
raise ArgumentError, "The number of arguments for the key is invalid, expected #{expected_key_size} but was #{actual_key_size}"
|
1390
1505
|
end
|
1391
1506
|
end
|
1392
1507
|
end # class Collection
|