ardm-core 1.2.1

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 (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +35 -0
  5. data/.travis.yml +23 -0
  6. data/.yardopts +1 -0
  7. data/Gemfile +63 -0
  8. data/LICENSE +20 -0
  9. data/README.rdoc +237 -0
  10. data/Rakefile +4 -0
  11. data/VERSION +1 -0
  12. data/ardm-core.gemspec +25 -0
  13. data/lib/ardm-core.rb +1 -0
  14. data/lib/dm-core.rb +285 -0
  15. data/lib/dm-core/adapters.rb +222 -0
  16. data/lib/dm-core/adapters/abstract_adapter.rb +236 -0
  17. data/lib/dm-core/adapters/in_memory_adapter.rb +113 -0
  18. data/lib/dm-core/associations/many_to_many.rb +496 -0
  19. data/lib/dm-core/associations/many_to_one.rb +296 -0
  20. data/lib/dm-core/associations/one_to_many.rb +345 -0
  21. data/lib/dm-core/associations/one_to_one.rb +86 -0
  22. data/lib/dm-core/associations/relationship.rb +663 -0
  23. data/lib/dm-core/backwards.rb +13 -0
  24. data/lib/dm-core/collection.rb +1514 -0
  25. data/lib/dm-core/core_ext/kernel.rb +23 -0
  26. data/lib/dm-core/core_ext/pathname.rb +6 -0
  27. data/lib/dm-core/core_ext/symbol.rb +10 -0
  28. data/lib/dm-core/identity_map.rb +7 -0
  29. data/lib/dm-core/model.rb +869 -0
  30. data/lib/dm-core/model/hook.rb +102 -0
  31. data/lib/dm-core/model/is.rb +32 -0
  32. data/lib/dm-core/model/property.rb +253 -0
  33. data/lib/dm-core/model/relationship.rb +377 -0
  34. data/lib/dm-core/model/scope.rb +89 -0
  35. data/lib/dm-core/property.rb +839 -0
  36. data/lib/dm-core/property/binary.rb +22 -0
  37. data/lib/dm-core/property/boolean.rb +31 -0
  38. data/lib/dm-core/property/class.rb +24 -0
  39. data/lib/dm-core/property/date.rb +45 -0
  40. data/lib/dm-core/property/date_time.rb +44 -0
  41. data/lib/dm-core/property/decimal.rb +50 -0
  42. data/lib/dm-core/property/discriminator.rb +46 -0
  43. data/lib/dm-core/property/float.rb +28 -0
  44. data/lib/dm-core/property/integer.rb +32 -0
  45. data/lib/dm-core/property/lookup.rb +29 -0
  46. data/lib/dm-core/property/numeric.rb +40 -0
  47. data/lib/dm-core/property/object.rb +28 -0
  48. data/lib/dm-core/property/serial.rb +13 -0
  49. data/lib/dm-core/property/string.rb +50 -0
  50. data/lib/dm-core/property/text.rb +12 -0
  51. data/lib/dm-core/property/time.rb +46 -0
  52. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  53. data/lib/dm-core/property/typecast/time.rb +33 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query.rb +1444 -0
  56. data/lib/dm-core/query/conditions/comparison.rb +910 -0
  57. data/lib/dm-core/query/conditions/operation.rb +720 -0
  58. data/lib/dm-core/query/direction.rb +36 -0
  59. data/lib/dm-core/query/operator.rb +35 -0
  60. data/lib/dm-core/query/path.rb +114 -0
  61. data/lib/dm-core/query/sort.rb +39 -0
  62. data/lib/dm-core/relationship_set.rb +72 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource.rb +1228 -0
  65. data/lib/dm-core/resource/persistence_state.rb +75 -0
  66. data/lib/dm-core/resource/persistence_state/clean.rb +40 -0
  67. data/lib/dm-core/resource/persistence_state/deleted.rb +30 -0
  68. data/lib/dm-core/resource/persistence_state/dirty.rb +96 -0
  69. data/lib/dm-core/resource/persistence_state/immutable.rb +34 -0
  70. data/lib/dm-core/resource/persistence_state/persisted.rb +29 -0
  71. data/lib/dm-core/resource/persistence_state/transient.rb +78 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +54 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
  74. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  75. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  76. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  77. data/lib/dm-core/spec/setup.rb +173 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +326 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1236 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +134 -0
  83. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  84. data/lib/dm-core/support/assertions.rb +8 -0
  85. data/lib/dm-core/support/chainable.rb +18 -0
  86. data/lib/dm-core/support/deprecate.rb +12 -0
  87. data/lib/dm-core/support/descendant_set.rb +89 -0
  88. data/lib/dm-core/support/equalizer.rb +48 -0
  89. data/lib/dm-core/support/ext/array.rb +22 -0
  90. data/lib/dm-core/support/ext/blank.rb +25 -0
  91. data/lib/dm-core/support/ext/hash.rb +67 -0
  92. data/lib/dm-core/support/ext/module.rb +47 -0
  93. data/lib/dm-core/support/ext/object.rb +57 -0
  94. data/lib/dm-core/support/ext/string.rb +24 -0
  95. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  96. data/lib/dm-core/support/hook.rb +402 -0
  97. data/lib/dm-core/support/inflections.rb +60 -0
  98. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  99. data/lib/dm-core/support/inflector/methods.rb +151 -0
  100. data/lib/dm-core/support/lazy_array.rb +451 -0
  101. data/lib/dm-core/support/local_object_space.rb +12 -0
  102. data/lib/dm-core/support/logger.rb +199 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +90 -0
  105. data/lib/dm-core/support/ordered_set.rb +380 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +250 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/script/performance.rb +275 -0
  110. data/script/profile.rb +218 -0
  111. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  112. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +68 -0
  113. data/spec/public/associations/many_to_many_spec.rb +197 -0
  114. data/spec/public/associations/many_to_one_spec.rb +83 -0
  115. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  116. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  117. data/spec/public/associations/one_to_many_spec.rb +81 -0
  118. data/spec/public/associations/one_to_one_spec.rb +176 -0
  119. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  120. data/spec/public/collection_spec.rb +69 -0
  121. data/spec/public/finalize_spec.rb +76 -0
  122. data/spec/public/model/hook_spec.rb +246 -0
  123. data/spec/public/model/property_spec.rb +88 -0
  124. data/spec/public/model/relationship_spec.rb +1040 -0
  125. data/spec/public/model_spec.rb +458 -0
  126. data/spec/public/property/binary_spec.rb +41 -0
  127. data/spec/public/property/boolean_spec.rb +22 -0
  128. data/spec/public/property/class_spec.rb +28 -0
  129. data/spec/public/property/date_spec.rb +22 -0
  130. data/spec/public/property/date_time_spec.rb +22 -0
  131. data/spec/public/property/decimal_spec.rb +23 -0
  132. data/spec/public/property/discriminator_spec.rb +135 -0
  133. data/spec/public/property/float_spec.rb +22 -0
  134. data/spec/public/property/integer_spec.rb +22 -0
  135. data/spec/public/property/object_spec.rb +107 -0
  136. data/spec/public/property/serial_spec.rb +22 -0
  137. data/spec/public/property/string_spec.rb +22 -0
  138. data/spec/public/property/text_spec.rb +63 -0
  139. data/spec/public/property/time_spec.rb +22 -0
  140. data/spec/public/property_spec.rb +341 -0
  141. data/spec/public/resource_spec.rb +284 -0
  142. data/spec/public/sel_spec.rb +53 -0
  143. data/spec/public/setup_spec.rb +145 -0
  144. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  145. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  146. data/spec/public/shared/collection_shared_spec.rb +1669 -0
  147. data/spec/public/shared/finder_shared_spec.rb +1629 -0
  148. data/spec/rcov.opts +6 -0
  149. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  150. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  151. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  152. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  153. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  154. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  155. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  156. data/spec/semipublic/associations_spec.rb +177 -0
  157. data/spec/semipublic/collection_spec.rb +110 -0
  158. data/spec/semipublic/model_spec.rb +96 -0
  159. data/spec/semipublic/property/binary_spec.rb +13 -0
  160. data/spec/semipublic/property/boolean_spec.rb +47 -0
  161. data/spec/semipublic/property/class_spec.rb +33 -0
  162. data/spec/semipublic/property/date_spec.rb +43 -0
  163. data/spec/semipublic/property/date_time_spec.rb +46 -0
  164. data/spec/semipublic/property/decimal_spec.rb +83 -0
  165. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  166. data/spec/semipublic/property/float_spec.rb +82 -0
  167. data/spec/semipublic/property/integer_spec.rb +82 -0
  168. data/spec/semipublic/property/lookup_spec.rb +29 -0
  169. data/spec/semipublic/property/serial_spec.rb +13 -0
  170. data/spec/semipublic/property/string_spec.rb +13 -0
  171. data/spec/semipublic/property/text_spec.rb +31 -0
  172. data/spec/semipublic/property/time_spec.rb +50 -0
  173. data/spec/semipublic/property_spec.rb +114 -0
  174. data/spec/semipublic/query/conditions/comparison_spec.rb +1501 -0
  175. data/spec/semipublic/query/conditions/operation_spec.rb +1294 -0
  176. data/spec/semipublic/query/path_spec.rb +471 -0
  177. data/spec/semipublic/query_spec.rb +3777 -0
  178. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  179. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  180. data/spec/semipublic/resource/state/dirty_spec.rb +156 -0
  181. data/spec/semipublic/resource/state/immutable_spec.rb +105 -0
  182. data/spec/semipublic/resource/state/transient_spec.rb +162 -0
  183. data/spec/semipublic/resource/state_spec.rb +230 -0
  184. data/spec/semipublic/resource_spec.rb +23 -0
  185. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  186. data/spec/semipublic/shared/resource_shared_spec.rb +199 -0
  187. data/spec/semipublic/shared/resource_state_shared_spec.rb +79 -0
  188. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  189. data/spec/spec.opts +5 -0
  190. data/spec/spec_helper.rb +37 -0
  191. data/spec/support/core_ext/hash.rb +10 -0
  192. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  193. data/spec/support/properties/huge_integer.rb +17 -0
  194. data/spec/unit/array_spec.rb +23 -0
  195. data/spec/unit/blank_spec.rb +73 -0
  196. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  197. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  198. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  199. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  200. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  201. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  202. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  203. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  204. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  205. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  206. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  207. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  208. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  216. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  217. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  218. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  219. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  220. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  221. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  222. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  223. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  224. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  226. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  227. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  228. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  229. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  230. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  231. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  232. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  233. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  237. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  239. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  240. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  241. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  242. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  243. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  244. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  245. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  246. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  247. data/spec/unit/hash_spec.rb +28 -0
  248. data/spec/unit/hook_spec.rb +1235 -0
  249. data/spec/unit/lazy_array_spec.rb +1949 -0
  250. data/spec/unit/mash_spec.rb +312 -0
  251. data/spec/unit/module_spec.rb +71 -0
  252. data/spec/unit/object_spec.rb +38 -0
  253. data/spec/unit/try_dup_spec.rb +46 -0
  254. data/tasks/ci.rake +1 -0
  255. data/tasks/db.rake +11 -0
  256. data/tasks/spec.rake +38 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +491 -0
@@ -0,0 +1,910 @@
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
+ # repository 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, "No Comparison class for #{slug.inspect} has been defined"
63
+ end
64
+ end
65
+
66
+ # Returns an array of all slugs registered with Comparison
67
+ #
68
+ # @return [Array<Symbol>]
69
+ #
70
+ # @api private
71
+ def self.slugs
72
+ AbstractComparison.descendants.map { |comparison_class| comparison_class.slug }
73
+ end
74
+
75
+ class << self
76
+ private
77
+
78
+ # Holds comparison subclasses keyed on their slug
79
+ #
80
+ # @return [Hash]
81
+ #
82
+ # @api private
83
+ def comparison_classes
84
+ @comparison_classes ||= {}
85
+ end
86
+
87
+ # Returns the comparison class identified by the given slug
88
+ #
89
+ # @param [Symbol] slug
90
+ # See slug parameter for Comparison.new
91
+ #
92
+ # @return [AbstractComparison, nil]
93
+ #
94
+ # @api private
95
+ def comparison_class(slug)
96
+ comparison_classes[slug] ||= AbstractComparison.descendants.detect { |comparison_class| comparison_class.slug == slug }
97
+ end
98
+ end
99
+ end # class Comparison
100
+
101
+ # A base class for the various comparison classes.
102
+ class AbstractComparison
103
+ extend Equalizer
104
+
105
+ equalize :subject, :value
106
+
107
+ # @api semipublic
108
+ attr_accessor :parent
109
+
110
+ # The property or relationship which is being matched against
111
+ #
112
+ # @return [Property, Associations::Relationship]
113
+ #
114
+ # @api semipublic
115
+ attr_reader :subject
116
+
117
+ # Value to be compared with the subject
118
+ #
119
+ # This value is compared against that contained in the subject when
120
+ # filtering collections, or the value in the repository when
121
+ # performing queries.
122
+ #
123
+ # In the case of primitive property, this is the value as it
124
+ # is stored in the repository.
125
+ #
126
+ # @return [Object]
127
+ #
128
+ # @api semipublic
129
+ def value
130
+ dumped_value
131
+ end
132
+
133
+ # The loaded/typecast value
134
+ #
135
+ # In the case of primitive types, this will be the same as +value+,
136
+ # however when using primitive property this stores the loaded value.
137
+ #
138
+ # If writing an adapter, you should use +value+, while plugin authors
139
+ # should refer to +loaded_value+.
140
+ #
141
+ #--
142
+ # As an example, you might use symbols with the Enum type in dm-types
143
+ #
144
+ # property :myprop, Enum[:open, :closed]
145
+ #
146
+ # These are stored in repositories as 1 and 2, respectively. +value+
147
+ # returns the 1 or 2, while +loaded_value+ returns the symbol.
148
+ #++
149
+ #
150
+ # @return [Object]
151
+ #
152
+ # @api semipublic
153
+ attr_reader :loaded_value
154
+
155
+ # Keeps track of AbstractComparison subclasses (used in Comparison)
156
+ #
157
+ # @return [Set<AbstractComparison>]
158
+ # @api private
159
+ def self.descendants
160
+ @descendants ||= DescendantSet.new
161
+ end
162
+
163
+ # Registers AbstractComparison subclasses (used in Comparison)
164
+ #
165
+ # @api private
166
+ def self.inherited(descendant)
167
+ descendants << descendant
168
+ end
169
+
170
+ # Setter/getter: allows subclasses to easily set their slug
171
+ #
172
+ # @param [Symbol] slug
173
+ # The slug to be set for this class. Passing nil returns the current
174
+ # value instead.
175
+ #
176
+ # @return [Symbol]
177
+ # The current slug set for the Comparison.
178
+ #
179
+ # @example Creating a MyComparison compairson with slug :exact.
180
+ # class MyComparison < AbstractComparison
181
+ # slug :exact
182
+ # end
183
+ #
184
+ # @api semipublic
185
+ def self.slug(slug = nil)
186
+ slug ? @slug = slug : @slug
187
+ end
188
+
189
+ # Return the comparison class slug
190
+ #
191
+ # @return [Symbol]
192
+ # the comparison class slug
193
+ #
194
+ # @api private
195
+ def slug
196
+ self.class.slug
197
+ end
198
+
199
+ # Test that the record value matches the comparison
200
+ #
201
+ # @param [Resource, Hash] record
202
+ # The record containing the value to be matched
203
+ #
204
+ # @return [Boolean]
205
+ #
206
+ # @api semipublic
207
+ def matches?(record)
208
+ match_property?(record)
209
+ end
210
+
211
+ # Tests that the Comparison is valid
212
+ #
213
+ # Subclasses can overload this to customise the means by which they
214
+ # determine the validity of the comparison. #valid? is called prior to
215
+ # performing a query on the repository: each Comparison within a Query
216
+ # must be valid otherwise the query will not be performed.
217
+ #
218
+ # @see DataMapper::Property#valid?
219
+ # @see DataMapper::Associations::Relationship#valid?
220
+ #
221
+ # @return [Boolean]
222
+ #
223
+ # @api semipublic
224
+ def valid?
225
+ valid_for_subject?(loaded_value)
226
+ end
227
+
228
+ # Returns whether the subject is a Relationship
229
+ #
230
+ # @return [Boolean]
231
+ #
232
+ # @api semipublic
233
+ def relationship?
234
+ false
235
+ end
236
+
237
+ # Returns whether the subject is a Property
238
+ #
239
+ # @return [Boolean]
240
+ #
241
+ # @api semipublic
242
+ def property?
243
+ subject.kind_of?(Property)
244
+ end
245
+
246
+ # Returns a human-readable representation of this object
247
+ #
248
+ # @return [String]
249
+ #
250
+ # @api semipublic
251
+ def inspect
252
+ "#<#{self.class} @subject=#{@subject.inspect} " \
253
+ "@dumped_value=#{@dumped_value.inspect} @loaded_value=#{@loaded_value.inspect}>"
254
+ end
255
+
256
+ # Returns a string version of this Comparison object
257
+ #
258
+ # @example
259
+ # Comparison.new(:==, MyClass.my_property, "value")
260
+ # # => "my_property == value"
261
+ #
262
+ # @return [String]
263
+ #
264
+ # @api semipublic
265
+ def to_s
266
+ "#{subject.name} #{comparator_string} #{dumped_value.inspect}"
267
+ end
268
+
269
+ # @api private
270
+ def negated?
271
+ parent = self.parent
272
+ parent ? parent.negated? : false
273
+ end
274
+
275
+ private
276
+
277
+ # @api private
278
+ attr_reader :dumped_value
279
+
280
+ # Creates a new AbstractComparison instance with +subject+ and +value+
281
+ #
282
+ # @param [Property, Associations::Relationship] subject
283
+ # The subject of the comparison - the value of the subject will be
284
+ # matched against the given value parameter.
285
+ # @param [Object] value
286
+ # The value for the comparison.
287
+ #
288
+ # @api semipublic
289
+ def initialize(subject, value)
290
+ @subject = subject
291
+ @loaded_value = typecast(value)
292
+ @dumped_value = dump
293
+ end
294
+
295
+ # @api private
296
+ def match_property?(record, operator = :===)
297
+ expected.send(operator, record_value(record))
298
+ end
299
+
300
+ # Typecasts the given +val+ using subject#typecast
301
+ #
302
+ # If the subject has no typecast method the value is returned without
303
+ # any changes.
304
+ #
305
+ # @param [Object] val
306
+ # The object to attempt to typecast.
307
+ #
308
+ # @return [Object]
309
+ # The typecasted object.
310
+ #
311
+ # @see Property#typecast
312
+ #
313
+ # @api private
314
+ def typecast(value)
315
+ typecast_property(value)
316
+ end
317
+
318
+ # @api private
319
+ def typecast_property(value)
320
+ subject.typecast(value)
321
+ end
322
+
323
+ # Dumps the given loaded_value using subject#value
324
+ #
325
+ # This converts property values to the primitive as stored in the
326
+ # repository.
327
+ #
328
+ # @return [Object]
329
+ # The raw (dumped) object.
330
+ #
331
+ # @see Property#value
332
+ #
333
+ # @api private
334
+ def dump
335
+ dump_property(loaded_value)
336
+ end
337
+
338
+ # @api private
339
+ def dump_property(value)
340
+ subject.dump(value)
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, key_type = :source_key)
361
+ subject = self.subject
362
+ case record
363
+ when Hash
364
+ record_value_from_hash(record, subject, key_type)
365
+ when Resource
366
+ record_value_from_resource(record, subject, key_type)
367
+ else
368
+ record
369
+ end
370
+ end
371
+
372
+ # Returns a value from a record hash
373
+ #
374
+ # Retrieves value for the +subject+ property or relationship from the
375
+ # given +hash+.
376
+ #
377
+ # @return [Object]
378
+ #
379
+ # @see AbstractComparison#record_value
380
+ #
381
+ # @api private
382
+ def record_value_from_hash(hash, subject, key_type)
383
+ hash.fetch subject, case subject
384
+ when Property
385
+ subject.load(hash[subject.field])
386
+ when Associations::Relationship
387
+ subject.send(key_type).map { |property|
388
+ record_value_from_hash(hash, property, key_type)
389
+ }
390
+ end
391
+ end
392
+
393
+ # Returns a value from a resource
394
+ #
395
+ # Extracts value for the +subject+ property or relationship from the
396
+ # given +resource+.
397
+ #
398
+ # @return [Object]
399
+ #
400
+ # @see AbstractComparison#record_value
401
+ #
402
+ # @api private
403
+ def record_value_from_resource(resource, subject, key_type)
404
+ case subject
405
+ when Property
406
+ subject.get!(resource)
407
+ when Associations::Relationship
408
+ subject.send(key_type).get!(resource)
409
+ end
410
+ end
411
+
412
+ # Retrieves the value of the +subject+
413
+ #
414
+ # @return [Object]
415
+ #
416
+ # @api semipublic
417
+ def expected(value = @loaded_value)
418
+ expected = record_value(value, :target_key)
419
+
420
+ if @subject.respond_to?(:source_key)
421
+ @subject.source_key.typecast(expected)
422
+ else
423
+ expected
424
+ end
425
+ end
426
+
427
+ # Test the value to see if it is valid
428
+ #
429
+ # @return [Boolean] true if the value is valid
430
+ #
431
+ # @api semipublic
432
+ def valid_for_subject?(loaded_value)
433
+ subject.valid?(loaded_value, negated?)
434
+ end
435
+ end # class AbstractComparison
436
+
437
+ # Included into comparisons which are capable of supporting
438
+ # Relationships.
439
+ module RelationshipHandler
440
+ # Returns whether this comparison subject is a Relationship
441
+ #
442
+ # @return [Boolean]
443
+ #
444
+ # @api semipublic
445
+ def relationship?
446
+ subject.kind_of?(Associations::Relationship)
447
+ end
448
+
449
+ # Tests that the record value matches the comparison
450
+ #
451
+ # @param [Resource, Hash] record
452
+ # The record containing the value to be matched
453
+ #
454
+ # @return [Boolean]
455
+ #
456
+ # @api semipublic
457
+ def matches?(record)
458
+ if relationship? && expected.respond_to?(:query)
459
+ match_relationship?(record)
460
+ else
461
+ super
462
+ end
463
+ end
464
+
465
+ # Returns the conditions required to match the subject relationship
466
+ #
467
+ # @return [Hash]
468
+ #
469
+ # @api semipublic
470
+ def foreign_key_mapping
471
+ relationship = subject.inverse
472
+ relationship = relationship.links.first if relationship.respond_to?(:links)
473
+
474
+ Query.target_conditions(value, relationship.source_key, relationship.target_key)
475
+ end
476
+
477
+ private
478
+
479
+ # @api private
480
+ def match_relationship?(record)
481
+ expected.query.conditions.matches?(record_value(record))
482
+ end
483
+
484
+ # Typecasts each value in the inclusion set
485
+ #
486
+ # @return [Array<Object>]
487
+ #
488
+ # @see AbtractComparison#typecast
489
+ #
490
+ # @api private
491
+ def typecast(value)
492
+ if relationship?
493
+ typecast_relationship(value)
494
+ else
495
+ super
496
+ end
497
+ end
498
+
499
+ # @api private
500
+ def dump
501
+ if relationship?
502
+ dump_relationship(loaded_value)
503
+ else
504
+ super
505
+ end
506
+ end
507
+
508
+ # @api private
509
+ def dump_relationship(value)
510
+ value
511
+ end
512
+ end # module RelationshipHandler
513
+
514
+ # Tests whether the value in the record is equal to the expected
515
+ # set for the Comparison.
516
+ class EqualToComparison < AbstractComparison
517
+ include RelationshipHandler
518
+
519
+ slug :eql
520
+
521
+ # Tests that the record value matches the comparison
522
+ #
523
+ # @param [Resource, Hash] record
524
+ # The record containing the value to be matched
525
+ #
526
+ # @return [Boolean]
527
+ #
528
+ # @api semipublic
529
+ def matches?(record)
530
+ if expected.nil?
531
+ record_value(record).nil?
532
+ else
533
+ super
534
+ end
535
+ end
536
+
537
+ private
538
+
539
+ # @api private
540
+ def typecast_relationship(value)
541
+ case value
542
+ when Hash then typecast_hash(value)
543
+ when Resource then typecast_resource(value)
544
+ end
545
+ end
546
+
547
+ # @api private
548
+ def typecast_hash(hash)
549
+ subject = self.subject
550
+ subject.target_model.new(subject.query.merge(hash))
551
+ end
552
+
553
+ # @api private
554
+ def typecast_resource(resource)
555
+ resource
556
+ end
557
+
558
+ # @return [String]
559
+ #
560
+ # @see AbstractComparison#to_s
561
+ #
562
+ # @api private
563
+ def comparator_string
564
+ '='
565
+ end
566
+ end # class EqualToComparison
567
+
568
+ # Tests whether the value in the record is contained in the
569
+ # expected set for the Comparison, where expected is an
570
+ # Array, Range, or Set.
571
+ class InclusionComparison < AbstractComparison
572
+ include RelationshipHandler
573
+
574
+ slug :in
575
+
576
+ # Checks that the Comparison is valid
577
+ #
578
+ # @see DataMapper::Query::Conditions::AbstractComparison#valid?
579
+ #
580
+ # @return [Boolean]
581
+ #
582
+ # @api semipublic
583
+ def valid?
584
+ loaded_value = self.loaded_value
585
+ case loaded_value
586
+ when Collection then valid_collection?(loaded_value)
587
+ when Range then valid_range?(loaded_value)
588
+ when Enumerable then valid_enumerable?(loaded_value)
589
+ else
590
+ false
591
+ end
592
+ end
593
+
594
+ private
595
+
596
+ # @api private
597
+ def match_property?(record)
598
+ super(record, :include?)
599
+ end
600
+
601
+ # Overloads AbtractComparison#expected
602
+ #
603
+ # @return [Array<Object>]
604
+ # @see AbtractComparison#expected
605
+ #
606
+ # @api private
607
+ def expected
608
+ loaded_value = self.loaded_value
609
+ if loaded_value.kind_of?(Range)
610
+ typecast_range(loaded_value)
611
+ elsif loaded_value.respond_to?(:map)
612
+ # FIXME: causes a lazy load when a Collection
613
+ loaded_value.map { |val| super(val) }
614
+ else
615
+ super
616
+ end
617
+ end
618
+
619
+ # @api private
620
+ def valid_collection?(collection)
621
+ valid_for_subject?(collection)
622
+ end
623
+
624
+ # @api private
625
+ def valid_range?(range)
626
+ (range.any? || negated?) && valid_for_subject?(range.first) && valid_for_subject?(range.last)
627
+ end
628
+
629
+ # @api private
630
+ def valid_enumerable?(enumerable)
631
+ (!enumerable.empty? || negated?) && enumerable.all? { |entry| valid_for_subject?(entry) }
632
+ end
633
+
634
+ # @api private
635
+ def typecast_property(value)
636
+ if value.kind_of?(Range)
637
+ typecast_range(value)
638
+ elsif value.respond_to?(:map) && !value.kind_of?(String)
639
+ value.map { |entry| super(entry) }
640
+ else
641
+ super
642
+ end
643
+ end
644
+
645
+ # @api private
646
+ def typecast_range(range)
647
+ range.class.new(typecast_property(range.first), typecast_property(range.last), range.exclude_end?)
648
+ end
649
+
650
+ # @api private
651
+ def typecast_relationship(value)
652
+ case value
653
+ when Hash then typecast_hash(value)
654
+ when Resource then typecast_resource(value)
655
+ when Collection then typecast_collection(value)
656
+ when Enumerable then typecast_enumerable(value)
657
+ end
658
+ end
659
+
660
+ # @api private
661
+ def typecast_hash(hash)
662
+ subject = self.subject
663
+ subject.target_model.all(subject.query.merge(hash))
664
+ end
665
+
666
+ # @api private
667
+ def typecast_resource(resource)
668
+ resource.collection_for_self
669
+ end
670
+
671
+ # @api private
672
+ def typecast_collection(collection)
673
+ collection
674
+ end
675
+
676
+ # @api private
677
+ def typecast_enumerable(enumerable)
678
+ collection = nil
679
+ enumerable.each do |entry|
680
+ typecasted = typecast_relationship(entry)
681
+ if collection
682
+ collection |= typecasted
683
+ else
684
+ collection = typecasted
685
+ end
686
+ end
687
+ collection
688
+ end
689
+
690
+ # Dumps the given +val+ using subject#value
691
+ #
692
+ # @return [Array<Object>]
693
+ #
694
+ # @see AbtractComparison#dump
695
+ #
696
+ # @api private
697
+ def dump
698
+ loaded_value = self.loaded_value
699
+ if subject.respond_to?(:dump) && loaded_value.respond_to?(:map) && !loaded_value.kind_of?(Range)
700
+ dumped_value = loaded_value.map { |value| dump_property(value) }
701
+ dumped_value.uniq!
702
+ dumped_value
703
+ else
704
+ super
705
+ end
706
+ end
707
+
708
+ # @return [String]
709
+ #
710
+ # @see AbstractComparison#to_s
711
+ #
712
+ # @api private
713
+ def comparator_string
714
+ 'IN'
715
+ end
716
+ end # class InclusionComparison
717
+
718
+ # Tests whether the value in the record matches the expected
719
+ # regexp set for the Comparison.
720
+ class RegexpComparison < AbstractComparison
721
+ slug :regexp
722
+
723
+ # Checks that the Comparison is valid
724
+ #
725
+ # @see AbstractComparison#valid?
726
+ #
727
+ # @api semipublic
728
+ def valid?
729
+ loaded_value.kind_of?(Regexp)
730
+ end
731
+
732
+ private
733
+
734
+ # Returns the value untouched
735
+ #
736
+ # @return [Object]
737
+ #
738
+ # @api private
739
+ def typecast(value)
740
+ value
741
+ end
742
+
743
+ # @return [String]
744
+ #
745
+ # @see AbstractComparison#to_s
746
+ #
747
+ # @api private
748
+ def comparator_string
749
+ '=~'
750
+ end
751
+ end # class RegexpComparison
752
+
753
+ # Tests whether the value in the record is like the expected set
754
+ # for the Comparison. Equivalent to a LIKE clause in an SQL database.
755
+ #
756
+ # TODO: move this to dm-more with DataObjectsAdapter plugins
757
+ class LikeComparison < AbstractComparison
758
+ slug :like
759
+
760
+ private
761
+
762
+ # Overloads the +expected+ method in AbstractComparison
763
+ #
764
+ # Return a regular expression suitable for matching against the
765
+ # records value.
766
+ #
767
+ # @return [Regexp]
768
+ #
769
+ # @see AbtractComparison#expected
770
+ #
771
+ # @api semipublic
772
+ def expected
773
+ Regexp.new('\A' << super.gsub('%', '.*').tr('_', '.') << '\z')
774
+ end
775
+
776
+ # @return [String]
777
+ #
778
+ # @see AbstractComparison#to_s
779
+ #
780
+ # @api private
781
+ def comparator_string
782
+ 'LIKE'
783
+ end
784
+ end # class LikeComparison
785
+
786
+ # Tests whether the value in the record is greater than the
787
+ # expected set for the Comparison.
788
+ class GreaterThanComparison < AbstractComparison
789
+ slug :gt
790
+
791
+ # Tests that the record value matches the comparison
792
+ #
793
+ # @param [Resource, Hash] record
794
+ # The record containing the value to be matched
795
+ #
796
+ # @return [Boolean]
797
+ #
798
+ # @api semipublic
799
+ def matches?(record)
800
+ return false if expected.nil?
801
+ record_value = record_value(record)
802
+ !record_value.nil? && record_value > expected
803
+ end
804
+
805
+ private
806
+
807
+ # @return [String]
808
+ #
809
+ # @see AbstractComparison#to_s
810
+ #
811
+ # @api private
812
+ def comparator_string
813
+ '>'
814
+ end
815
+ end # class GreaterThanComparison
816
+
817
+ # Tests whether the value in the record is less than the expected
818
+ # set for the Comparison.
819
+ class LessThanComparison < AbstractComparison
820
+ slug :lt
821
+
822
+ # Tests that the record value matches the comparison
823
+ #
824
+ # @param [Resource, Hash] record
825
+ # The record containing the value to be matched
826
+ #
827
+ # @return [Boolean]
828
+ #
829
+ # @api semipublic
830
+ def matches?(record)
831
+ return false if expected.nil?
832
+ record_value = record_value(record)
833
+ !record_value.nil? && record_value < expected
834
+ end
835
+
836
+ private
837
+
838
+ # @return [String]
839
+ #
840
+ # @see AbstractComparison#to_s
841
+ #
842
+ # @api private
843
+ def comparator_string
844
+ '<'
845
+ end
846
+ end # class LessThanComparison
847
+
848
+ # Tests whether the value in the record is greater than, or equal to,
849
+ # the expected set for the Comparison.
850
+ class GreaterThanOrEqualToComparison < AbstractComparison
851
+ slug :gte
852
+
853
+ # Tests that the record value matches the comparison
854
+ #
855
+ # @param [Resource, Hash] record
856
+ # The record containing the value to be matched
857
+ #
858
+ # @return [Boolean]
859
+ #
860
+ # @api semipublic
861
+ def matches?(record)
862
+ return false if expected.nil?
863
+ record_value = record_value(record)
864
+ !record_value.nil? && record_value >= expected
865
+ end
866
+
867
+ private
868
+
869
+ # @see AbstractComparison#to_s
870
+ #
871
+ # @api private
872
+ def comparator_string
873
+ '>='
874
+ end
875
+ end # class GreaterThanOrEqualToComparison
876
+
877
+ # Tests whether the value in the record is less than, or equal to, the
878
+ # expected set for the Comparison.
879
+ class LessThanOrEqualToComparison < AbstractComparison
880
+ slug :lte
881
+
882
+ # Tests that the record value matches the comparison
883
+ #
884
+ # @param [Resource, Hash] record
885
+ # The record containing the value to be matched
886
+ #
887
+ # @return [Boolean]
888
+ #
889
+ # @api semipublic
890
+ def matches?(record)
891
+ return false if expected.nil?
892
+ record_value = record_value(record)
893
+ !record_value.nil? && record_value <= expected
894
+ end
895
+
896
+ private
897
+
898
+ # @return [String]
899
+ #
900
+ # @see AbstractComparison#to_s
901
+ #
902
+ # @api private
903
+ def comparator_string
904
+ '<='
905
+ end
906
+ end # class LessThanOrEqualToComparison
907
+
908
+ end # module Conditions
909
+ end # class Query
910
+ end # module DataMapper