dm-core 0.10.0 → 0.10.1

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 (39) hide show
  1. data/History.txt +25 -5
  2. data/Manifest.txt +1 -0
  3. data/README.txt +67 -23
  4. data/Rakefile +0 -2
  5. data/deps.rip +1 -1
  6. data/dm-core.gemspec +6 -6
  7. data/lib/dm-core/adapters/abstract_adapter.rb +3 -76
  8. data/lib/dm-core/adapters/data_objects_adapter.rb +8 -39
  9. data/lib/dm-core/associations/many_to_many.rb +28 -16
  10. data/lib/dm-core/associations/many_to_one.rb +1 -45
  11. data/lib/dm-core/associations/one_to_many.rb +1 -38
  12. data/lib/dm-core/associations/relationship.rb +43 -20
  13. data/lib/dm-core/collection.rb +33 -32
  14. data/lib/dm-core/model/property.rb +8 -8
  15. data/lib/dm-core/model/relationship.rb +10 -12
  16. data/lib/dm-core/property.rb +20 -85
  17. data/lib/dm-core/property_set.rb +8 -8
  18. data/lib/dm-core/query/conditions/comparison.rb +13 -71
  19. data/lib/dm-core/query/conditions/operation.rb +73 -47
  20. data/lib/dm-core/query/operator.rb +3 -45
  21. data/lib/dm-core/query/path.rb +5 -41
  22. data/lib/dm-core/query.rb +37 -108
  23. data/lib/dm-core/repository.rb +3 -79
  24. data/lib/dm-core/resource.rb +54 -49
  25. data/lib/dm-core/support/chainable.rb +0 -2
  26. data/lib/dm-core/support/equalizer.rb +23 -0
  27. data/lib/dm-core/types/object.rb +4 -4
  28. data/lib/dm-core/version.rb +1 -1
  29. data/lib/dm-core.rb +3 -11
  30. data/spec/public/model/relationship_spec.rb +4 -4
  31. data/spec/public/property_spec.rb +5 -449
  32. data/spec/public/sel_spec.rb +52 -2
  33. data/spec/public/shared/collection_shared_spec.rb +79 -26
  34. data/spec/public/shared/finder_shared_spec.rb +6 -6
  35. data/spec/public/shared/resource_shared_spec.rb +2 -2
  36. data/spec/semipublic/property_spec.rb +524 -9
  37. data/spec/semipublic/query_spec.rb +6 -6
  38. data/tasks/hoe.rb +2 -2
  39. metadata +24 -4
@@ -39,6 +39,9 @@ module DataMapper
39
39
 
40
40
  class AbstractOperation
41
41
  include Enumerable
42
+ extend Equalizer
43
+
44
+ equalize :slug, :sorted_operands
42
45
 
43
46
  # TODO: document
44
47
  # @api semipublic
@@ -62,6 +65,16 @@ module DataMapper
62
65
  slug ? @slug = slug : @slug
63
66
  end
64
67
 
68
+ # Return the comparison class slug
69
+ #
70
+ # @return [Symbol]
71
+ # the comparison class slug
72
+ #
73
+ # @api private
74
+ def slug
75
+ self.class.slug
76
+ end
77
+
65
78
  # TODO: document
66
79
  # @api semipublic
67
80
  def each
@@ -71,7 +84,7 @@ module DataMapper
71
84
  # TODO: document
72
85
  # @api semipublic
73
86
  def valid?
74
- operands.all? do |operand|
87
+ operands.any? && operands.all? do |operand|
75
88
  if operand.respond_to?(:valid?)
76
89
  operand.valid?
77
90
  else
@@ -87,52 +100,22 @@ module DataMapper
87
100
  self
88
101
  end
89
102
 
90
- # TODO: document
91
- # @api semipublic
92
- def hash
93
- @operands.sort_by { |operand| operand.hash }.hash
94
- end
95
-
96
- # TODO: document
97
- # @api semipublic
98
- def ==(other)
99
- if equal?(other)
100
- return true
101
- end
102
-
103
- other_class = other.class
104
-
105
- unless other_class.respond_to?(:slug) && other_class.slug == self.class.slug
106
- return false
107
- end
108
-
109
- unless other.respond_to?(:operands)
110
- return false
111
- end
112
-
113
- cmp?(other, :==)
114
- end
115
-
116
- # TODO: document
117
- # @api semipublic
118
- def eql?(other)
119
- if equal?(other)
120
- return true
121
- end
122
-
123
- unless instance_of?(other.class)
124
- return false
125
- end
126
-
127
- cmp?(other, :eql?)
128
- end
129
-
130
103
  # TODO: document
131
104
  # @api semipublic
132
105
  def inspect
133
106
  "#<#{self.class} @operands=#{@operands.inspect}>"
134
107
  end
135
108
 
109
+ # Return a list of operands in predictable order
110
+ #
111
+ # @return [Array<AbstractOperation>]
112
+ # list of operands sorted in deterministic order
113
+ #
114
+ # @api private
115
+ def sorted_operands
116
+ @operands.sort_by { |operand| operand.hash }
117
+ end
118
+
136
119
  private
137
120
 
138
121
  # TODO: document
@@ -146,12 +129,6 @@ module DataMapper
146
129
  def initialize_copy(*)
147
130
  @operands = @operands.map { |operand| operand.dup }
148
131
  end
149
-
150
- # TODO: document
151
- # @api private
152
- def cmp?(other, operator)
153
- @operands.sort_by { |operand| operand.hash }.send(operator, other.operands.sort_by { |operand| operand.hash })
154
- end
155
132
  end # class AbstractOperation
156
133
 
157
134
  module FlattenOperation
@@ -216,6 +193,55 @@ module DataMapper
216
193
  super
217
194
  end
218
195
  end # class NotOperation
196
+
197
+ class NullOperation < AbstractOperation
198
+ undef_method :<<
199
+
200
+ slug :null
201
+
202
+ # Match the record
203
+ #
204
+ # @param [Resource] record
205
+ # the resource to match
206
+ #
207
+ # @return [true]
208
+ # every record matches
209
+ #
210
+ # @api semipublic
211
+ def matches?(record)
212
+ true
213
+ end
214
+
215
+ # Test validity of the operation
216
+ #
217
+ # @return [true]
218
+ # always valid
219
+ #
220
+ # @api semipublic
221
+ def valid?
222
+ true
223
+ end
224
+
225
+ # Treat the operation the same as nil
226
+ #
227
+ # @return [true]
228
+ # should be treated as nil
229
+ #
230
+ # @api semipublic
231
+ def nil?
232
+ true
233
+ end
234
+
235
+ # Inspecting the operation should return the same as nil
236
+ #
237
+ # @return [String]
238
+ # return the string 'nil'
239
+ #
240
+ # @api semipublic
241
+ def inspect
242
+ 'nil'
243
+ end
244
+ end
219
245
  end # module Conditions
220
246
  end # class Query
221
247
  end # module DataMapper
@@ -9,6 +9,9 @@ module DataMapper
9
9
  class Query
10
10
  class Operator
11
11
  include Extlib::Assertions
12
+ extend Equalizer
13
+
14
+ equalize :target, :operator
12
15
 
13
16
  # TODO: document
14
17
  # @api private
@@ -18,44 +21,6 @@ module DataMapper
18
21
  # @api private
19
22
  attr_reader :operator
20
23
 
21
- # TODO: document
22
- # @api private
23
- def ==(other)
24
- if equal?(other)
25
- return true
26
- end
27
-
28
- unless other.respond_to?(:target)
29
- return false
30
- end
31
-
32
- unless other.respond_to?(:operator)
33
- return false
34
- end
35
-
36
- cmp?(other, :==)
37
- end
38
-
39
- # TODO: document
40
- # @api private
41
- def eql?(other)
42
- if equal?(other)
43
- return true
44
- end
45
-
46
- unless instance_of?(other.class)
47
- return false
48
- end
49
-
50
- cmp?(other, :eql?)
51
- end
52
-
53
- # TODO: document
54
- # @api private
55
- def hash
56
- @target.hash
57
- end
58
-
59
24
  # TODO: document
60
25
  # @api private
61
26
  def inspect
@@ -72,13 +37,6 @@ module DataMapper
72
37
  @target = target
73
38
  @operator = operator
74
39
  end
75
-
76
- # TODO: document
77
- # @api private
78
- def cmp?(other, operator)
79
- target.send(operator, other.target) &&
80
- self.operator.send(operator, other.operator)
81
- end
82
40
  end # class Operator
83
41
  end # class Query
84
42
  end # module DataMapper
@@ -10,6 +10,9 @@ module DataMapper
10
10
  instance_methods.each { |method| undef_method method unless %w[ __id__ __send__ send class dup object_id kind_of? instance_of? respond_to? equal? should should_not instance_variable_set instance_variable_get extend hash inspect ].include?(method.to_s) }
11
11
 
12
12
  include Extlib::Assertions
13
+ extend Equalizer
14
+
15
+ equalize :relationships, :property
13
16
 
14
17
  # TODO: document
15
18
  # @api semipublic
@@ -39,13 +42,13 @@ module DataMapper
39
42
  # TODO: document
40
43
  # @api public
41
44
  def kind_of?(klass)
42
- super || (defined?(@property) && @property.kind_of?(klass))
45
+ super || (defined?(@property) ? @property.kind_of?(klass) : false)
43
46
  end
44
47
 
45
48
  # TODO: document
46
49
  # @api public
47
50
  def instance_of?(klass)
48
- super || (defined?(@property) && @property.instance_of?(klass))
51
+ super || (defined?(@property) ? @property.instance_of?(klass) : false)
49
52
  end
50
53
 
51
54
  # TODO: document
@@ -57,38 +60,6 @@ module DataMapper
57
60
  @model.properties(@repository_name).named?(method)
58
61
  end
59
62
 
60
- # TODO: document
61
- # @api semipublic
62
- def ==(other)
63
- if equal?(other)
64
- return true
65
- end
66
-
67
- unless other.respond_to?(:relationships)
68
- return false
69
- end
70
-
71
- unless other.respond_to?(:property)
72
- return false
73
- end
74
-
75
- cmp?(other, :==)
76
- end
77
-
78
- # TODO: document
79
- # @api semipublic
80
- def eql?(other)
81
- if equal?(other)
82
- return true
83
- end
84
-
85
- unless instance_of?(other.class)
86
- return false
87
- end
88
-
89
- cmp?(other, :eql?)
90
- end
91
-
92
63
  private
93
64
 
94
65
  # TODO: document
@@ -109,13 +80,6 @@ module DataMapper
109
80
  end
110
81
  end
111
82
 
112
- # TODO: document
113
- # @api private
114
- def cmp?(other, operator)
115
- relationships.send(operator, other.relationships) &&
116
- property.send(operator, other.property)
117
- end
118
-
119
83
  # TODO: document
120
84
  # @api semipublic
121
85
  def method_missing(method, *args)
data/lib/dm-core/query.rb CHANGED
@@ -27,9 +27,12 @@ module DataMapper
27
27
  #
28
28
  class Query
29
29
  include Extlib::Assertions
30
+ extend Equalizer
30
31
 
31
32
  OPTIONS = [ :fields, :links, :conditions, :offset, :limit, :order, :unique, :add_reversed, :reload ].to_set.freeze
32
33
 
34
+ equalize :repository, :model, :sorted_fields, :links, :conditions, :order, :offset, :limit, :reload?, :unique?, :add_reversed?
35
+
33
36
  # Extract conditions to match a Resource or Collection
34
37
  #
35
38
  # @param [Array, Collection, Resource] source
@@ -419,6 +422,7 @@ module DataMapper
419
422
  #
420
423
  # @api semipublic
421
424
  def match_records(records)
425
+ return records if conditions.nil?
422
426
  records.select do |record|
423
427
  conditions.matches?(record)
424
428
  end
@@ -464,48 +468,6 @@ module DataMapper
464
468
  end
465
469
  end
466
470
 
467
- # Compares another Query for equivalency
468
- #
469
- # @param [Query] other
470
- # the other Query to compare with
471
- #
472
- # @return [Boolean]
473
- # true if they are equivalent, false if not
474
- #
475
- # @api semipublic
476
- def ==(other)
477
- if equal?(other)
478
- return true
479
- end
480
-
481
- unless [ :repository, :model, :fields, :links, :conditions, :order, :offset, :limit, :reload?, :unique?, :add_reversed? ].all? { |method| other.respond_to?(method) }
482
- return false
483
- end
484
-
485
- cmp?(other, :==)
486
- end
487
-
488
- # Compares another Query for equality
489
- #
490
- # @param [Query] other
491
- # the other Query to compare with
492
- #
493
- # @return [Boolean]
494
- # true if they are equal, false if not
495
- #
496
- # @api semipublic
497
- def eql?(other)
498
- if equal?(other)
499
- return true
500
- end
501
-
502
- unless instance_of?(other.class)
503
- return false
504
- end
505
-
506
- cmp?(other, :eql?)
507
- end
508
-
509
471
  # Slices collection by adding limit and offset to the
510
472
  # query, so a single query is executed
511
473
  #
@@ -591,6 +553,16 @@ module DataMapper
591
553
  properties
592
554
  end
593
555
 
556
+ # Return a list of fields in predictable order
557
+ #
558
+ # @return [Array<Property>]
559
+ # list of fields sorted in deterministic order
560
+ #
561
+ # @api private
562
+ def sorted_fields
563
+ fields.sort_by { |property| property.hash }
564
+ end
565
+
594
566
  private
595
567
 
596
568
  # Initializes a Query instance
@@ -627,7 +599,7 @@ module DataMapper
627
599
 
628
600
  @fields = @options.fetch :fields, @properties.defaults
629
601
  @links = @options.fetch :links, []
630
- @conditions = Conditions::Operation.new(:and) # AND all the conditions together
602
+ @conditions = Conditions::Operation.new(:null)
631
603
  @offset = @options.fetch :offset, 0
632
604
  @limit = @options.fetch :limit, nil
633
605
  @order = @options.fetch :order, @model.default_order(repository_name)
@@ -644,14 +616,14 @@ module DataMapper
644
616
  # parse @options[:conditions] differently
645
617
  case conditions = @options[:conditions]
646
618
  when Conditions::AbstractOperation, Conditions::AbstractComparison
647
- @conditions << conditions
619
+ add_condition(conditions)
648
620
 
649
621
  when Hash
650
622
  conditions.each { |kv| append_condition(*kv) }
651
623
 
652
624
  when Array
653
625
  statement, *bind_values = *conditions
654
- @conditions << [ statement, bind_values ]
626
+ add_condition([ statement, bind_values ])
655
627
  @raw = true
656
628
  end
657
629
 
@@ -953,7 +925,7 @@ module DataMapper
953
925
 
954
926
  @links.clear
955
927
 
956
- while link = links.shift
928
+ while link = links.pop
957
929
  relationship = case link
958
930
  when Symbol, String then @relationships[link]
959
931
  when Associations::Relationship then link
@@ -989,6 +961,7 @@ module DataMapper
989
961
  @links << relationship
990
962
  end
991
963
  end
964
+ @links.reverse!
992
965
  end
993
966
 
994
967
  # Append conditions to this Query
@@ -1038,7 +1011,7 @@ module DataMapper
1038
1011
  condition = Conditions::Operation.new(:not, condition)
1039
1012
  end
1040
1013
 
1041
- @conditions << condition
1014
+ add_condition(condition)
1042
1015
  end
1043
1016
 
1044
1017
  # TODO: document
@@ -1052,7 +1025,10 @@ module DataMapper
1052
1025
  def append_string_condition(string, bind_value, model, operator)
1053
1026
  if string.include?('.')
1054
1027
  query_path = model
1055
- string.split('.').each { |method| query_path = query_path.send(method) }
1028
+
1029
+ target_components = string.split('.')
1030
+ operator = target_components.pop.to_sym if DataMapper::Query::Conditions::Comparison.slugs.map{ |s| s.to_s }.include? target_components.last
1031
+ target_components.each { |method| query_path = query_path.send(method) }
1056
1032
 
1057
1033
  append_condition(query_path, bind_value, model, operator)
1058
1034
  else
@@ -1077,6 +1053,19 @@ module DataMapper
1077
1053
  append_condition(path.property, bind_value, path.model, operator)
1078
1054
  end
1079
1055
 
1056
+ # Add a condition to the Query
1057
+ #
1058
+ # @param [AbstractOperation, AbstractComparison]
1059
+ # the condition to add to the Query
1060
+ #
1061
+ # @return [undefined]
1062
+ #
1063
+ # @api private
1064
+ def add_condition(condition)
1065
+ @conditions = Conditions::Operation.new(:and) if @conditions.nil?
1066
+ @conditions << condition
1067
+ end
1068
+
1080
1069
  # TODO: make this typecast all bind values that do not match the
1081
1070
  # property primitive
1082
1071
 
@@ -1142,66 +1131,6 @@ module DataMapper
1142
1131
  return new_offset, limit
1143
1132
  end
1144
1133
 
1145
- # Return true if +other+'s is equivalent or equal to +self+'s
1146
- #
1147
- # @param [Query] other
1148
- # The Resource whose attributes are to be compared with +self+'s
1149
- # @param [Symbol] operator
1150
- # The comparison operator to use to compare the attributes
1151
- #
1152
- # @return [Boolean]
1153
- # The result of the comparison of +other+'s attributes with +self+'s
1154
- #
1155
- # @api private
1156
- def cmp?(other, operator)
1157
- # check the attributes that are most likely to differ first
1158
- unless repository.send(operator, other.repository)
1159
- return false
1160
- end
1161
-
1162
- unless model.send(operator, other.model)
1163
- return false
1164
- end
1165
-
1166
- unless conditions.send(operator, other.conditions)
1167
- return false
1168
- end
1169
-
1170
- unless offset.send(operator, other.offset)
1171
- return false
1172
- end
1173
-
1174
- unless limit.send(operator, other.limit)
1175
- return false
1176
- end
1177
-
1178
- unless order.send(operator, other.order)
1179
- return false
1180
- end
1181
-
1182
- unless fields.sort_by { |property| property.hash }.send(operator, other.fields.sort_by { |property| property.hash })
1183
- return false
1184
- end
1185
-
1186
- unless links.send(operator, other.links)
1187
- return false
1188
- end
1189
-
1190
- unless reload?.send(operator, other.reload?)
1191
- return false
1192
- end
1193
-
1194
- unless unique?.send(operator, other.unique?)
1195
- return false
1196
- end
1197
-
1198
- unless add_reversed?.send(operator, other.add_reversed?)
1199
- return false
1200
- end
1201
-
1202
- true
1203
- end
1204
-
1205
1134
  # TODO: DRY this up with conditions
1206
1135
  # @api private
1207
1136
  def record_value(record, property)
@@ -1,6 +1,9 @@
1
1
  module DataMapper
2
2
  class Repository
3
3
  include Extlib::Assertions
4
+ extend Equalizer
5
+
6
+ equalize :name, :adapter
4
7
 
5
8
  # Get the list of adapters registered for all Repositories,
6
9
  # keyed by repository name.
@@ -172,71 +175,6 @@ module DataMapper
172
175
  adapter.delete(collection)
173
176
  end
174
177
 
175
- # Compares another Repository for equality
176
- #
177
- # Repository is equal to +other+ if they are the same object (identity)
178
- # or if they are of the same class and have the same name
179
- #
180
- # @param [Repository] other
181
- # the other Repository to compare with
182
- #
183
- # @return [Boolean]
184
- # true if they are equal, false if not
185
- #
186
- # @api public
187
- def eql?(other)
188
- if equal?(other)
189
- return true
190
- end
191
-
192
- unless instance_of?(other.class)
193
- return false
194
- end
195
-
196
- cmp?(other, :eql?)
197
- end
198
-
199
- # Compares another Repository for equivalency
200
- #
201
- # Repository is equal to +other+ if they are the same object (identity)
202
- # or if they both have the same name
203
- #
204
- # @param [Repository] other
205
- # the other Repository to compare with
206
- #
207
- # @return [Boolean]
208
- # true if they are equal, false if not
209
- #
210
- # @api public
211
- def ==(other)
212
- if equal?(other)
213
- return true
214
- end
215
-
216
- unless other.respond_to?(:name)
217
- return false
218
- end
219
-
220
- unless other.respond_to?(:adapter)
221
- return false
222
- end
223
-
224
- cmp?(other, :==)
225
- end
226
-
227
- # Return the hash of the Repository
228
- #
229
- # This is necessary for properly determining the unique Repository
230
- # in a Set or Hash
231
- #
232
- # @return [Integer]
233
- # the Hash of the Repository name
234
- #
235
- # @api private
236
- def hash
237
- name.hash
238
- end
239
-
240
178
  # Return a human readable representation of the repository
241
179
  #
242
180
  # TODO: create example
@@ -265,19 +203,5 @@ module DataMapper
265
203
  @name = name
266
204
  @identity_maps = {}
267
205
  end
268
-
269
- # TODO: document
270
- # @api private
271
- def cmp?(other, operator)
272
- unless name.send(operator, other.name)
273
- return false
274
- end
275
-
276
- unless adapter.send(operator, other.adapter)
277
- return false
278
- end
279
-
280
- true
281
- end
282
206
  end # class Repository
283
207
  end # module DataMapper