ardm-core 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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