dm-core 0.9.11 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (194) hide show
  1. data/.autotest +17 -14
  2. data/.gitignore +3 -1
  3. data/FAQ +6 -5
  4. data/History.txt +5 -50
  5. data/Manifest.txt +66 -76
  6. data/QUICKLINKS +1 -1
  7. data/README.txt +21 -15
  8. data/Rakefile +6 -7
  9. data/SPECS +2 -29
  10. data/TODO +1 -1
  11. data/deps.rip +2 -0
  12. data/dm-core.gemspec +11 -15
  13. data/lib/dm-core.rb +105 -110
  14. data/lib/dm-core/adapters.rb +135 -16
  15. data/lib/dm-core/adapters/abstract_adapter.rb +251 -181
  16. data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
  17. data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
  18. data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
  19. data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
  20. data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
  21. data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
  22. data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
  23. data/lib/dm-core/associations/many_to_many.rb +372 -90
  24. data/lib/dm-core/associations/many_to_one.rb +220 -73
  25. data/lib/dm-core/associations/one_to_many.rb +319 -255
  26. data/lib/dm-core/associations/one_to_one.rb +66 -53
  27. data/lib/dm-core/associations/relationship.rb +561 -156
  28. data/lib/dm-core/collection.rb +1101 -379
  29. data/lib/dm-core/core_ext/kernel.rb +12 -0
  30. data/lib/dm-core/core_ext/symbol.rb +10 -0
  31. data/lib/dm-core/identity_map.rb +4 -34
  32. data/lib/dm-core/migrations.rb +1283 -0
  33. data/lib/dm-core/model.rb +570 -369
  34. data/lib/dm-core/model/descendant_set.rb +81 -0
  35. data/lib/dm-core/model/hook.rb +45 -0
  36. data/lib/dm-core/model/is.rb +32 -0
  37. data/lib/dm-core/model/property.rb +247 -0
  38. data/lib/dm-core/model/relationship.rb +335 -0
  39. data/lib/dm-core/model/scope.rb +90 -0
  40. data/lib/dm-core/property.rb +808 -273
  41. data/lib/dm-core/property_set.rb +141 -98
  42. data/lib/dm-core/query.rb +1037 -483
  43. data/lib/dm-core/query/conditions/comparison.rb +872 -0
  44. data/lib/dm-core/query/conditions/operation.rb +221 -0
  45. data/lib/dm-core/query/direction.rb +43 -0
  46. data/lib/dm-core/query/operator.rb +84 -0
  47. data/lib/dm-core/query/path.rb +138 -0
  48. data/lib/dm-core/query/sort.rb +45 -0
  49. data/lib/dm-core/repository.rb +210 -94
  50. data/lib/dm-core/resource.rb +641 -421
  51. data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
  52. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
  53. data/lib/dm-core/support/chainable.rb +22 -0
  54. data/lib/dm-core/support/deprecate.rb +12 -0
  55. data/lib/dm-core/support/logger.rb +13 -0
  56. data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
  57. data/lib/dm-core/transaction.rb +333 -92
  58. data/lib/dm-core/type.rb +98 -60
  59. data/lib/dm-core/types/boolean.rb +1 -1
  60. data/lib/dm-core/types/discriminator.rb +34 -20
  61. data/lib/dm-core/types/object.rb +7 -4
  62. data/lib/dm-core/types/paranoid_boolean.rb +11 -9
  63. data/lib/dm-core/types/paranoid_datetime.rb +11 -9
  64. data/lib/dm-core/types/serial.rb +3 -3
  65. data/lib/dm-core/types/text.rb +3 -4
  66. data/lib/dm-core/version.rb +1 -1
  67. data/script/performance.rb +102 -109
  68. data/script/profile.rb +169 -38
  69. data/spec/lib/adapter_helpers.rb +105 -0
  70. data/spec/lib/collection_helpers.rb +18 -0
  71. data/spec/lib/counter_adapter.rb +34 -0
  72. data/spec/lib/pending_helpers.rb +27 -0
  73. data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
  74. data/spec/public/associations/many_to_many_spec.rb +193 -0
  75. data/spec/public/associations/many_to_one_spec.rb +73 -0
  76. data/spec/public/associations/one_to_many_spec.rb +77 -0
  77. data/spec/public/associations/one_to_one_spec.rb +156 -0
  78. data/spec/public/collection_spec.rb +65 -0
  79. data/spec/public/migrations_spec.rb +359 -0
  80. data/spec/public/model/relationship_spec.rb +924 -0
  81. data/spec/public/model_spec.rb +159 -0
  82. data/spec/public/property_spec.rb +829 -0
  83. data/spec/public/resource_spec.rb +71 -0
  84. data/spec/public/sel_spec.rb +44 -0
  85. data/spec/public/setup_spec.rb +145 -0
  86. data/spec/public/shared/association_collection_shared_spec.rb +317 -0
  87. data/spec/public/shared/collection_shared_spec.rb +1670 -0
  88. data/spec/public/shared/finder_shared_spec.rb +1619 -0
  89. data/spec/public/shared/resource_shared_spec.rb +924 -0
  90. data/spec/public/shared/sel_shared_spec.rb +112 -0
  91. data/spec/public/transaction_spec.rb +129 -0
  92. data/spec/public/types/discriminator_spec.rb +130 -0
  93. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  94. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  95. data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
  96. data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
  97. data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
  98. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
  99. data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
  100. data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
  101. data/spec/semipublic/associations/relationship_spec.rb +194 -0
  102. data/spec/semipublic/associations_spec.rb +177 -0
  103. data/spec/semipublic/collection_spec.rb +142 -0
  104. data/spec/semipublic/property_spec.rb +61 -0
  105. data/spec/semipublic/query/conditions_spec.rb +528 -0
  106. data/spec/semipublic/query/path_spec.rb +443 -0
  107. data/spec/semipublic/query_spec.rb +2626 -0
  108. data/spec/semipublic/resource_spec.rb +47 -0
  109. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  110. data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
  111. data/spec/spec.opts +3 -1
  112. data/spec/spec_helper.rb +80 -57
  113. data/tasks/ci.rb +19 -31
  114. data/tasks/dm.rb +43 -48
  115. data/tasks/doc.rb +8 -11
  116. data/tasks/gemspec.rb +5 -5
  117. data/tasks/hoe.rb +15 -16
  118. data/tasks/install.rb +8 -10
  119. metadata +74 -111
  120. data/lib/dm-core/associations.rb +0 -207
  121. data/lib/dm-core/associations/relationship_chain.rb +0 -81
  122. data/lib/dm-core/auto_migrations.rb +0 -105
  123. data/lib/dm-core/dependency_queue.rb +0 -32
  124. data/lib/dm-core/hook.rb +0 -11
  125. data/lib/dm-core/is.rb +0 -16
  126. data/lib/dm-core/logger.rb +0 -232
  127. data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
  128. data/lib/dm-core/migrator.rb +0 -29
  129. data/lib/dm-core/scope.rb +0 -58
  130. data/lib/dm-core/support.rb +0 -7
  131. data/lib/dm-core/support/array.rb +0 -13
  132. data/lib/dm-core/support/assertions.rb +0 -8
  133. data/lib/dm-core/support/errors.rb +0 -23
  134. data/lib/dm-core/support/kernel.rb +0 -11
  135. data/lib/dm-core/support/symbol.rb +0 -41
  136. data/lib/dm-core/type_map.rb +0 -80
  137. data/lib/dm-core/types.rb +0 -19
  138. data/script/all +0 -4
  139. data/spec/integration/association_spec.rb +0 -1382
  140. data/spec/integration/association_through_spec.rb +0 -203
  141. data/spec/integration/associations/many_to_many_spec.rb +0 -449
  142. data/spec/integration/associations/many_to_one_spec.rb +0 -163
  143. data/spec/integration/associations/one_to_many_spec.rb +0 -188
  144. data/spec/integration/auto_migrations_spec.rb +0 -413
  145. data/spec/integration/collection_spec.rb +0 -1073
  146. data/spec/integration/data_objects_adapter_spec.rb +0 -32
  147. data/spec/integration/dependency_queue_spec.rb +0 -46
  148. data/spec/integration/model_spec.rb +0 -197
  149. data/spec/integration/mysql_adapter_spec.rb +0 -85
  150. data/spec/integration/postgres_adapter_spec.rb +0 -731
  151. data/spec/integration/property_spec.rb +0 -253
  152. data/spec/integration/query_spec.rb +0 -514
  153. data/spec/integration/repository_spec.rb +0 -61
  154. data/spec/integration/resource_spec.rb +0 -513
  155. data/spec/integration/sqlite3_adapter_spec.rb +0 -352
  156. data/spec/integration/sti_spec.rb +0 -273
  157. data/spec/integration/strategic_eager_loading_spec.rb +0 -156
  158. data/spec/integration/transaction_spec.rb +0 -75
  159. data/spec/integration/type_spec.rb +0 -275
  160. data/spec/lib/logging_helper.rb +0 -18
  161. data/spec/lib/mock_adapter.rb +0 -27
  162. data/spec/lib/model_loader.rb +0 -100
  163. data/spec/lib/publicize_methods.rb +0 -28
  164. data/spec/models/content.rb +0 -16
  165. data/spec/models/vehicles.rb +0 -34
  166. data/spec/models/zoo.rb +0 -48
  167. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
  168. data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
  169. data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
  170. data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
  171. data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
  172. data/spec/unit/associations/many_to_many_spec.rb +0 -32
  173. data/spec/unit/associations/many_to_one_spec.rb +0 -159
  174. data/spec/unit/associations/one_to_many_spec.rb +0 -393
  175. data/spec/unit/associations/one_to_one_spec.rb +0 -7
  176. data/spec/unit/associations/relationship_spec.rb +0 -71
  177. data/spec/unit/associations_spec.rb +0 -242
  178. data/spec/unit/auto_migrations_spec.rb +0 -111
  179. data/spec/unit/collection_spec.rb +0 -182
  180. data/spec/unit/data_mapper_spec.rb +0 -35
  181. data/spec/unit/identity_map_spec.rb +0 -126
  182. data/spec/unit/is_spec.rb +0 -80
  183. data/spec/unit/migrator_spec.rb +0 -33
  184. data/spec/unit/model_spec.rb +0 -321
  185. data/spec/unit/naming_conventions_spec.rb +0 -36
  186. data/spec/unit/property_set_spec.rb +0 -90
  187. data/spec/unit/property_spec.rb +0 -753
  188. data/spec/unit/query_spec.rb +0 -571
  189. data/spec/unit/repository_spec.rb +0 -93
  190. data/spec/unit/resource_spec.rb +0 -649
  191. data/spec/unit/scope_spec.rb +0 -142
  192. data/spec/unit/transaction_spec.rb +0 -493
  193. data/spec/unit/type_map_spec.rb +0 -114
  194. data/spec/unit/type_spec.rb +0 -119
@@ -0,0 +1,872 @@
1
+ module DataMapper
2
+ class Query
3
+ # The Conditions module contains classes used as part of a Query when
4
+ # filtering collections of resources.
5
+ #
6
+ # The Conditions module contains two types of class used for filtering
7
+ # queries: Comparison and Operation. Although these are used on all
8
+ # repositorie types -- not just SQL-based repos -- these classes are best
9
+ # thought of as being the DataMapper counterpart to an SQL WHERE clause.
10
+ #
11
+ # Comparisons compare properties and relationships with values, while
12
+ # operations tie Comparisons together to form more complex expressions.
13
+ #
14
+ # For example, the following SQL query fragment:
15
+ #
16
+ # ... WHERE my_field = my_value AND another_field = another_value ...
17
+ #
18
+ # ... would be represented as two EqualToComparison instances tied
19
+ # together with an AndOperation.
20
+ #
21
+ # Conditions -- together with the Query class -- allow DataMapper to
22
+ # represent SQL-like expressions in an ORM-agnostic manner, and are used
23
+ # for both in-memory filtering of loaded Collection instances, and by
24
+ # adapters to retrieve records directly from your repositories.
25
+ #
26
+ # The classes contained in the Conditions module are for internal use by
27
+ # DataMapper and DataMapper plugins, and are not intended to be used
28
+ # directly in your applications.
29
+ module Conditions
30
+
31
+ # An abstract class which provides easy access to comparison operators
32
+ #
33
+ # @example Creating a new comparison
34
+ # Comparison.new(:eql, MyClass.my_property, "value")
35
+ #
36
+ class Comparison
37
+
38
+ # Creates a new Comparison instance
39
+ #
40
+ # The returned instance will be suitable for matching the given
41
+ # subject (property or relationship) against the value.
42
+ #
43
+ # @param [Symbol] slug
44
+ # The type of comparison operator required. One of: :eql, :in, :gt,
45
+ # :gte, :lt, :lte, :regexp, :like.
46
+ # @param [Property, Associations::Relationship]
47
+ # The subject of the comparison - the value of the subject will be
48
+ # matched against the given value parameter.
49
+ # @param [Object] value
50
+ # The value for the comparison.
51
+ #
52
+ # @return [DataMapper::Query::Conditions::AbstractComparison]
53
+ #
54
+ # @example
55
+ # Comparison.new(:eql, MyClass.properties[:id], 1)
56
+ #
57
+ # @api semipublic
58
+ def self.new(slug, subject, value)
59
+ if klass = comparison_class(slug)
60
+ klass.new(subject, value)
61
+ else
62
+ raise ArgumentError,
63
+ "No Comparison class for `#{slug.inspect}' has been defined"
64
+ end
65
+ end
66
+
67
+ # Returns the comparison class identified by the given slug
68
+ #
69
+ # @param [Symbol] slug
70
+ # See slug parameter for Comparison.new
71
+ #
72
+ # @return [AbstractComparison, nil]
73
+ #
74
+ # @api private
75
+ def self.comparison_class(slug)
76
+ comparison_classes[slug] ||=
77
+ AbstractComparison.descendants.detect do |comparison_class|
78
+ comparison_class.slug == slug
79
+ end
80
+ end
81
+
82
+ # Returns an array of all slugs registered with Comparison
83
+ #
84
+ # @return [Array<Symbol>]
85
+ #
86
+ # @api private
87
+ def self.slugs
88
+ @slugs ||=
89
+ AbstractComparison.descendants.map do |comparison_class|
90
+ comparison_class.slug
91
+ end.freeze
92
+ end
93
+
94
+ class << self
95
+ private
96
+
97
+ # Holds comparison subclasses keyed on their slug
98
+ #
99
+ # @return [Hash]
100
+ #
101
+ # @api private
102
+ def comparison_classes
103
+ @comparison_classes ||= {}
104
+ end
105
+ end
106
+ end # class Comparison
107
+
108
+ # A base class for the various comparison classes.
109
+ class AbstractComparison
110
+ extend Deprecate
111
+
112
+ deprecate :property, :subject
113
+
114
+ # The property or relationship which is being matched against
115
+ #
116
+ # @return [Property, Associations::Relationship]
117
+ #
118
+ # @api semipublic
119
+ attr_reader :subject
120
+
121
+ # Value to be compared with the subject
122
+ #
123
+ # This value is compared against that contained in the subject when
124
+ # filtering collections, or the value in the repository when
125
+ # performing queries.
126
+ #
127
+ # In the case of custom types, this is the value as it is stored in
128
+ # the repository.
129
+ #
130
+ # @return [Object]
131
+ #
132
+ # @api semipublic
133
+ attr_reader :value
134
+
135
+ # The loaded/typecast value
136
+ #
137
+ # In the case of primitive types, this will be the same as +value+,
138
+ # however when using custom types this stores the loaded value.
139
+ #
140
+ # If writing an adapter, you should use +value+, while plugin authors
141
+ # should refer to +loaded_value+.
142
+ #
143
+ #--
144
+ # As an example, you might use symbols with the Enum type in dm-types
145
+ #
146
+ # property :myprop, Enum[:open, :closed]
147
+ #
148
+ # These are stored in repositories as 1 and 2, respectively. +value+
149
+ # returns the 1 or 2, while +loaded_value+ returns the symbol.
150
+ #++
151
+ #
152
+ # @return [Object]
153
+ #
154
+ # @api semipublic
155
+ attr_reader :loaded_value
156
+
157
+ # Keeps track of AbstractComparison subclasses (used in Comparison)
158
+ #
159
+ # @return [Set<AbstractComparison>]
160
+ # @api private
161
+ def self.descendants
162
+ @descendants ||= Set.new
163
+ end
164
+
165
+ # Registers AbstractComparison subclasses (used in Comparison)
166
+ #
167
+ # @api private
168
+ def self.inherited(comparison_class)
169
+ descendants << comparison_class
170
+ end
171
+
172
+ # Setter/getter: allows subclasses to easily set their slug
173
+ #
174
+ # @param [Symbol] slug
175
+ # The slug to be set for this class. Passing nil returns the current
176
+ # value instead.
177
+ #
178
+ # @return [Symbol]
179
+ # The current slug set for the Comparison.
180
+ #
181
+ # @example Creating a MyComparison compairson with slug :exact.
182
+ # class MyComparison < AbstractComparison
183
+ # slug :exact
184
+ # end
185
+ #
186
+ # @api semipublic
187
+ def self.slug(slug = nil)
188
+ slug ? @slug = slug : @slug
189
+ end
190
+
191
+ # Tests that the Comparison is valid
192
+ #
193
+ # Subclasses can overload this to customise the means by which they
194
+ # determine the validity of the comparison. #valid? is called prior to
195
+ # performing a query on the repository: each Comparison within a Query
196
+ # must be valid otherwise the query will not be performed.
197
+ #
198
+ # @see DataMapper::Property#valid?
199
+ # @see DataMapper::Associations::Relationship#valid?
200
+ #
201
+ # @return [Boolean]
202
+ #
203
+ # @api semipublic
204
+ def valid?
205
+ # This needs to be deferred until the last moment because the value
206
+ # could be a reference to a Resource, that when the comparison was
207
+ # created was invalid, but has since been saved and has it's key
208
+ # set.
209
+ subject.valid?(loaded_value)
210
+ end
211
+
212
+ # Returns whether the subject is a Relationship
213
+ #
214
+ # @return [Boolean]
215
+ #
216
+ # @api semipublic
217
+ def relationship?
218
+ false
219
+ end
220
+
221
+ # Returns whether the subject is a Property
222
+ #
223
+ # @return [Boolean]
224
+ #
225
+ # @api semipublic
226
+ def property?
227
+ subject.kind_of?(Property)
228
+ end
229
+
230
+ # Computes a hash-code for this Comparison
231
+ #
232
+ # Two Comparisons of the same class, and with the same subject and
233
+ # value will have the same hash-code.
234
+ #
235
+ # @return [Fixnum] The computed hash-code.
236
+ #
237
+ # @api semipublic
238
+ def hash
239
+ [ self.class, @subject, @value ].hash
240
+ end
241
+
242
+ # Returns true if this object equals +other+
243
+ #
244
+ # The objects are considered equal if they are the same object, or
245
+ # have the same slug, subject and value.
246
+ #
247
+ # @param [Object] other
248
+ # Another object to be compared against this one.
249
+ #
250
+ # @return [Boolean]
251
+ #
252
+ # @api semipublic
253
+ def ==(other)
254
+ return true if equal?(other)
255
+
256
+ return false unless other.class.respond_to?(:slug)
257
+ return false unless other.respond_to?(:subject)
258
+ return false unless other.respond_to?(:value)
259
+
260
+ cmp?(other, :==)
261
+ end
262
+
263
+ # Returns true if this object equals +other+
264
+ #
265
+ # The objects are considered equal if they are the same object, or are
266
+ # the same class, and have the same slug, subject and value.
267
+ #
268
+ # @param [Object] other
269
+ # Another object to be compared against this one.
270
+ #
271
+ # @return [Boolean]
272
+ #
273
+ # @api semipublic
274
+ def eql?(other)
275
+ return true if equal?(other)
276
+ return false unless instance_of?(other.class)
277
+
278
+ cmp?(other, :eql?)
279
+ end
280
+
281
+ # Returns a human-readable representation of this object
282
+ #
283
+ # @return [String]
284
+ #
285
+ # @api semipublic
286
+ def inspect
287
+ "#<#{self.class} @subject=#{@subject.inspect} " \
288
+ "@value=#{@value.inspect} @loaded_value=#{@loaded_value.inspect}>"
289
+ end
290
+
291
+ # Returns a string version of this Comparison object
292
+ #
293
+ # @example
294
+ # Comparison.new(:==, MyClass.my_property, "value")
295
+ # # => "my_property == value"
296
+ #
297
+ # @return [String]
298
+ #
299
+ # @api semipublic
300
+ def to_s
301
+ "#{@subject} #{comparator_string} #{@value}"
302
+ end
303
+
304
+ private # ============================================================
305
+
306
+ # Holds the actual value of the given property or relationship
307
+ #
308
+ # @return [Object]
309
+ #
310
+ # @api semipublic
311
+ attr_reader :expected
312
+
313
+ # Creates a new AbstractComparison instance with +subject+ and +value+
314
+ #
315
+ # @param [Property, Associations::Relationship] subject
316
+ # The subject of the comparison - the value of the subject will be
317
+ # matched against the given value parameter.
318
+ # @param [Object] value
319
+ # The value for the comparison.
320
+ #
321
+ # @api semipublic
322
+ def initialize(subject, value)
323
+ @subject = subject
324
+ @loaded_value = typecast_value(value)
325
+ @value = dumped_value(@loaded_value)
326
+ @expected = expected_value
327
+ end
328
+
329
+ # Used by Ruby when creating a copy of the comparison
330
+ #
331
+ # @api private
332
+ def initialize_copy(*)
333
+ @value = @value.dup
334
+ @loaded_value = @loaded_value.dup
335
+ end
336
+
337
+ # Compares this comparison with +other+ using the given +operator+
338
+ #
339
+ # Checks that the slug, subject and value all return true when
340
+ # compared with their counterparts in +other+ with +operator+.
341
+ #
342
+ # @param [AbstractComparison] other
343
+ # Another object to be compared against this one.
344
+ #
345
+ # @see AbstractComparison#==
346
+ # @see AbstractComparison#eql?
347
+ #
348
+ # @return [Boolean]
349
+ #
350
+ # @api private
351
+ def cmp?(other, operator)
352
+ self.class.slug.send(operator, other.class.slug) &&
353
+ subject.send(operator, other.subject) &&
354
+ value.send(operator, other.value)
355
+ end
356
+
357
+ # Typecasts the given +val+ using subject#typecast
358
+ #
359
+ # If the subject has no typecast method the value is returned without
360
+ # any changes.
361
+ #
362
+ # @param [Object] val
363
+ # The object to attempt to typecast.
364
+ #
365
+ # @return [Object]
366
+ # The typecasted object.
367
+ #
368
+ # @see Property#typecast
369
+ #
370
+ # @api private
371
+ def typecast_value(val)
372
+ if subject.respond_to?(:typecast)
373
+ subject.typecast(val)
374
+ else
375
+ val
376
+ end
377
+ end
378
+
379
+ # Dumps the given +val+ using subject#value
380
+ #
381
+ # This converts property values to the primitive as stored in the
382
+ # repository.
383
+ #
384
+ # @param [Object] val
385
+ # The object to attempt to typecast.
386
+ #
387
+ # @return [Object]
388
+ # The raw (dumped) object.
389
+ #
390
+ # @see Property#value
391
+ #
392
+ # @api private
393
+ def dumped_value(val)
394
+ if subject.respond_to?(:value)
395
+ subject.value(val)
396
+ else
397
+ val
398
+ end
399
+ end
400
+
401
+ # Returns a value for the comparison +subject+
402
+ #
403
+ # Extracts value for the +subject+ property or relationship from the
404
+ # given +record+, where +record+ is a Resource instance or a Hash.
405
+ #
406
+ # @param [DataMapper::Resource, Hash] record
407
+ # The resource or hash from which to retrieve the value.
408
+ # @param [Property, Associations::Relationship]
409
+ # The subject of the comparison. For example, if this is a property,
410
+ # the value for the resources +subject+ property is retrieved.
411
+ # @param [Symbol] key_type
412
+ # In the event that +subject+ is a relationship, key_type indicated
413
+ # which key should be used to retrieve the value from the resource.
414
+ #
415
+ # @return [Object]
416
+ #
417
+ # @api semipublic
418
+ def record_value(record, subject = @subject, key_type = :source_key)
419
+ case record
420
+ when Hash
421
+ record_value_from_hash(record, subject, key_type)
422
+ when Resource
423
+ record_value_from_resource(record, subject, key_type)
424
+ else
425
+ record
426
+ end
427
+ end
428
+
429
+ # Returns a value from a record hash
430
+ #
431
+ # Retrieves value for the +subject+ property or relationship from the
432
+ # given +hash+.
433
+ #
434
+ # @return [Object]
435
+ #
436
+ # @see AbstractComparison#record_value
437
+ #
438
+ # @api private
439
+ def record_value_from_hash(hash, subject, key_type)
440
+ hash.fetch subject, case subject
441
+ when Property
442
+ hash[subject.field]
443
+ when Associations::Relationship
444
+ subject.send(key_type).map { |property|
445
+ record_value_from_hash(hash, property, key_type)
446
+ }
447
+ end
448
+ end
449
+
450
+ # Returns a value from a resource
451
+ #
452
+ # Extracts value for the +subject+ property or relationship from the
453
+ # given +resource+.
454
+ #
455
+ # @return [Object]
456
+ #
457
+ # @see AbstractComparison#record_value
458
+ #
459
+ # @api private
460
+ def record_value_from_resource(resource, subject, key_type)
461
+ case subject
462
+ when Property
463
+ subject.get!(resource)
464
+ when Associations::Relationship
465
+ subject.send(key_type).get!(resource)
466
+ end
467
+ end
468
+
469
+ # Retrieves the value of the +subject+
470
+ #
471
+ # @return [Object]
472
+ #
473
+ # @api semipublic
474
+ def expected_value(val = @loaded_value)
475
+ expected_value = record_value(val, @subject, :target_key)
476
+
477
+ if @subject.respond_to?(:source_key)
478
+ @subject.source_key.typecast(expected_value)
479
+ else
480
+ expected_value
481
+ end
482
+ end
483
+
484
+ # Returns the name of this comparison
485
+ #
486
+ # @return [String]
487
+ # The name of the comparison class minus the trailing "Comparison".
488
+ #
489
+ # @example
490
+ # Comparison.new(:eql, ...).comparator_string
491
+ # # => Equal
492
+ #
493
+ # @api private
494
+ def comparator_string
495
+ self.class.name.chomp('Comparison')
496
+ end
497
+ end # class AbstractComparison
498
+
499
+ # Included into comparisons which are capable of supporting
500
+ # Relationships.
501
+ module RelationshipHandler
502
+ # Returns whether this comparison subject is a Relationship
503
+ #
504
+ # @return [Boolean]
505
+ #
506
+ # @api semipublic
507
+ def relationship?
508
+ subject.kind_of?(Associations::Relationship)
509
+ end
510
+
511
+ # Returns the conditions required to match the subject relationship
512
+ #
513
+ # @return [Hash]
514
+ #
515
+ # @api semipublic
516
+ def foreign_key_mapping
517
+ relationship = subject.inverse
518
+
519
+ Query.target_conditions(value, relationship.source_key, relationship.target_key)
520
+ end
521
+ end # module RelationshipHandler
522
+
523
+ # Tests whether the value in the record is equal to the expected_value
524
+ # set for the Comparison.
525
+ class EqualToComparison < AbstractComparison
526
+ include RelationshipHandler
527
+
528
+ slug :eql
529
+
530
+ # Asserts that the record value matches the comparison
531
+ #
532
+ # @param [Resource, Hash] record
533
+ # The record containing the value to be matched
534
+ #
535
+ # @return [Boolean]
536
+ # @api semipublic
537
+ def matches?(record)
538
+ record_value(record) == expected
539
+ end
540
+
541
+ private
542
+
543
+ # @return [String]
544
+ #
545
+ # @see AbstractComparison#to_s
546
+ #
547
+ # @api private
548
+ def comparator_string
549
+ '='
550
+ end
551
+ end # class EqualToComparison
552
+
553
+ # Tests whether the value in the record is contained in the
554
+ # expected_value set for the Comparison, where expected_value is an
555
+ # Array, Range, or Set.
556
+ class InclusionComparison < AbstractComparison
557
+ include RelationshipHandler
558
+
559
+ slug :in
560
+
561
+ # Asserts that the record value matches the comparison
562
+ #
563
+ # @param [Resource, Hash] record
564
+ # The record containing the value to be matched
565
+ #
566
+ # @return [Boolean]
567
+ #
568
+ # @api semipublic
569
+ def matches?(record)
570
+ record_value = record_value(record)
571
+ !record_value.nil? && expected.include?(record_value)
572
+ end
573
+
574
+ # Checks that the Comparison is valid
575
+ #
576
+ # @see DataMapper::Query::Conditions::AbstractComparison#valid?
577
+ #
578
+ # @return [Boolean]
579
+ #
580
+ # @api semipublic
581
+ def valid?
582
+ case value
583
+ when Array, Set
584
+ loaded_value.any? && loaded_value.all? { |val| subject.valid?(val) }
585
+ when Range
586
+ loaded_value.any? && subject.valid?(loaded_value.first) && subject.valid?(loaded_value.last)
587
+ else
588
+ false
589
+ end
590
+ end
591
+
592
+ private
593
+
594
+ # Overloads AbtractComparison#expected_value
595
+ #
596
+ # @return [Array<Object>]
597
+ # @see AbtractComparison#expected_value
598
+ #
599
+ # @api private
600
+ def expected_value
601
+ if loaded_value.is_a?(Range)
602
+ Range.new(super(loaded_value.first), super(loaded_value.last), loaded_value.exclude_end?)
603
+ else
604
+ loaded_value.map { |val| super(val) }
605
+ end
606
+ end
607
+
608
+ # Typecasts each value in the inclusion set
609
+ #
610
+ # @return [Array<Object>]
611
+ #
612
+ # @see AbtractComparison#typecast_value
613
+ #
614
+ # @api private
615
+ def typecast_value(val)
616
+ if subject.respond_to?(:typecast) && val.is_a?(Range)
617
+ if subject.primitive?(val.first)
618
+ # If the range type matches, nothing to do
619
+ val
620
+ else
621
+ # Create a new range with the new type
622
+ Range.new(subject.typecast(val.first), subject.typecast(val.last), val.exclude_end?)
623
+ end
624
+ elsif subject.respond_to?(:typecast) && val.respond_to?(:map)
625
+ val.map { |el| subject.typecast(el) }
626
+ else
627
+ val
628
+ end
629
+ end
630
+
631
+ # Dumps the given +val+ using subject#value
632
+ #
633
+ # @return [Array<Object>]
634
+ #
635
+ # @see AbtractComparison#dumped_value
636
+ #
637
+ # @api private
638
+ def dumped_value(val)
639
+ if subject.respond_to?(:value) && val.is_a?(Range) && !subject.custom?
640
+ val
641
+ elsif subject.respond_to?(:value) && val.respond_to?(:map)
642
+ val.map { |el| subject.value(el) }
643
+ else
644
+ val
645
+ end
646
+ end
647
+
648
+ # @return [String]
649
+ #
650
+ # @see AbstractComparison#to_s
651
+ #
652
+ # @api private
653
+ def comparator_string
654
+ 'IN'
655
+ end
656
+ end # class InclusionComparison
657
+
658
+ # Tests whether the value in the record matches the expected_value
659
+ # regexp set for the Comparison.
660
+ class RegexpComparison < AbstractComparison
661
+ slug :regexp
662
+
663
+ # Asserts that the record value matches the comparison
664
+ #
665
+ # @param [Resource, Hash] record
666
+ # The record containing the value to be matched
667
+ #
668
+ # @return [Boolean]
669
+ #
670
+ # @api semipublic
671
+ def matches?(record)
672
+ record_value = record_value(record)
673
+ !record_value.nil? && record_value =~ expected
674
+ end
675
+
676
+ # Checks that the Comparison is valid
677
+ #
678
+ # @see AbstractComparison#valid?
679
+ #
680
+ # @api semipublic
681
+ def valid?
682
+ value.kind_of?(Regexp)
683
+ end
684
+
685
+ private
686
+
687
+ # Returns the value untouched
688
+ #
689
+ # @return [Object]
690
+ #
691
+ # @api private
692
+ def typecast_value(val)
693
+ val
694
+ end
695
+
696
+ # @return [String]
697
+ #
698
+ # @see AbstractComparison#to_s
699
+ #
700
+ # @api private
701
+ def comparator_string
702
+ '=~'
703
+ end
704
+ end # class RegexpComparison
705
+
706
+ # Tests whether the value in the record is like the expected_value set
707
+ # for the Comparison. Equivalent to a LIKE clause in an SQL database.
708
+ #
709
+ # TODO: move this to dm-more with DataObjectsAdapter plugins
710
+ class LikeComparison < AbstractComparison
711
+ slug :like
712
+
713
+ # Asserts that the record value matches the comparison
714
+ #
715
+ # @param [Resource, Hash] record
716
+ # The record containing the value to be matched
717
+ #
718
+ # @return [Boolean]
719
+ #
720
+ # @api semipublic
721
+ def matches?(record)
722
+ record_value = record_value(record)
723
+ !record_value.nil? && record_value =~ expected
724
+ end
725
+
726
+ private
727
+
728
+ # Overloads the +expected_value+ method in AbstractComparison
729
+ #
730
+ # Return a regular expression suitable for matching against the
731
+ # records value.
732
+ #
733
+ # @return [Regexp]
734
+ #
735
+ # @see AbtractComparison#expected_value
736
+ #
737
+ # @api semipublic
738
+ def expected_value
739
+ Regexp.new(@value.to_s.gsub('%', '.*').gsub('_', '.'))
740
+ end
741
+
742
+ # @return [String]
743
+ #
744
+ # @see AbstractComparison#to_s
745
+ #
746
+ # @api private
747
+ def comparator_string
748
+ 'LIKE'
749
+ end
750
+ end # class LikeComparison
751
+
752
+ # Tests whether the value in the record is greater than the
753
+ # expected_value set for the Comparison.
754
+ class GreaterThanComparison < AbstractComparison
755
+ slug :gt
756
+
757
+ # Asserts that the record value matches the comparison
758
+ #
759
+ # @param [Resource, Hash] record
760
+ # The record containing the value to be matched
761
+ #
762
+ # @return [Boolean]
763
+ #
764
+ # @api semipublic
765
+ def matches?(record)
766
+ record_value = record_value(record)
767
+ !record_value.nil? && record_value > expected
768
+ end
769
+
770
+ private
771
+
772
+ # @return [String]
773
+ #
774
+ # @see AbstractComparison#to_s
775
+ #
776
+ # @api private
777
+ def comparator_string
778
+ '>'
779
+ end
780
+ end # class GreaterThanComparison
781
+
782
+ # Tests whether the value in the record is less than the expected_value
783
+ # set for the Comparison.
784
+ class LessThanComparison < AbstractComparison
785
+ slug :lt
786
+
787
+ # Asserts that the record value matches the comparison
788
+ #
789
+ # @param [Resource, Hash] record
790
+ # The record containing the value to be matched
791
+ #
792
+ # @return [Boolean]
793
+ #
794
+ # @api semipublic
795
+ def matches?(record)
796
+ record_value = record_value(record)
797
+ !record_value.nil? && record_value < expected
798
+ end
799
+
800
+ private
801
+
802
+ # @return [String]
803
+ #
804
+ # @see AbstractComparison#to_s
805
+ #
806
+ # @api private
807
+ def comparator_string
808
+ '<'
809
+ end
810
+ end # class LessThanComparison
811
+
812
+ # Tests whether the value in the record is greater than, or equal to,
813
+ # the expected_value set for the Comparison.
814
+ class GreaterThanOrEqualToComparison < AbstractComparison
815
+ slug :gte
816
+
817
+ # Asserts that the record value matches the comparison
818
+ #
819
+ # @param [Resource, Hash] record
820
+ # The record containing the value to be matched
821
+ #
822
+ # @return [Boolean]
823
+ #
824
+ # @api semipublic
825
+ def matches?(record)
826
+ record_value = record_value(record)
827
+ !record_value.nil? && record_value >= expected
828
+ end
829
+
830
+ private
831
+
832
+ # @see AbstractComparison#to_s
833
+ #
834
+ # @api private
835
+ def comparator_string
836
+ '>='
837
+ end
838
+ end # class GreaterThanOrEqualToComparison
839
+
840
+ # Tests whether the value in the record is less than, or equal to, the
841
+ # expected_value set for the Comparison.
842
+ class LessThanOrEqualToComparison < AbstractComparison
843
+ slug :lte
844
+
845
+ # Asserts that the record value matches the comparison
846
+ #
847
+ # @param [Resource, Hash] record
848
+ # The record containing the value to be matched
849
+ #
850
+ # @return [Boolean]
851
+ #
852
+ # @api semipublic
853
+ def matches?(record)
854
+ record_value = record_value(record)
855
+ !record_value.nil? && record_value <= expected
856
+ end
857
+
858
+ private
859
+
860
+ # @return [String]
861
+ #
862
+ # @see AbstractComparison#to_s
863
+ #
864
+ # @api private
865
+ def comparator_string
866
+ '<='
867
+ end
868
+ end # class LessThanOrEqualToComparison
869
+
870
+ end # module Conditions
871
+ end # class Query
872
+ end # module DataMapper