dm-core 0.10.0 → 0.10.1

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