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