sbf-dm-core 1.3.0.beta

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