dm-core 0.9.11 → 0.10.0

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 (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