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