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