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,721 @@
1
+ module DataMapper
2
+ class Query
3
+ module Conditions
4
+ class Operation
5
+ # Factory method to initialize an operation
6
+ #
7
+ # @example
8
+ # operation = Operation.new(:and, comparison)
9
+ #
10
+ # @param [Symbol] slug
11
+ # the identifier for the operation class
12
+ # @param [Array] *operands
13
+ # the operands to initialize the operation with
14
+ #
15
+ # @return [AbstractOperation]
16
+ # the operation matching the slug
17
+ #
18
+ # @api semipublic
19
+ def self.new(slug, *operands)
20
+ if klass = operation_class(slug)
21
+ klass.new(*operands)
22
+ else
23
+ raise ArgumentError, "No Operation class for #{slug.inspect} has been defined"
24
+ end
25
+ end
26
+
27
+ # Return an Array of all the slugs for the operation classes
28
+ #
29
+ # @return [Array]
30
+ # the slugs of all the operation classes
31
+ #
32
+ # @api private
33
+ def self.slugs
34
+ AbstractOperation.descendants.map { |operation_class| operation_class.slug }
35
+ end
36
+
37
+ class << self
38
+ private
39
+
40
+ # Returns a Hash mapping the slugs to each class
41
+ #
42
+ # @return [Hash]
43
+ # Hash mapping the slug to the class
44
+ #
45
+ # @api private
46
+ def operation_classes
47
+ @operation_classes ||= {}
48
+ end
49
+
50
+ # Lookup the operation class based on the slug
51
+ #
52
+ # @example
53
+ # operation_class = Operation.operation_class(:and)
54
+ #
55
+ # @param [Symbol] slug
56
+ # the identifier for the operation class
57
+ #
58
+ # @return [Class]
59
+ # the operation class
60
+ #
61
+ # @api private
62
+ def operation_class(slug)
63
+ operation_classes[slug] ||= AbstractOperation.descendants.detect { |operation_class| operation_class.slug == slug }
64
+ end
65
+ end
66
+ end # class Operation
67
+
68
+ class AbstractOperation
69
+ include DataMapper::Assertions
70
+ include Enumerable
71
+ extend Equalizer
72
+
73
+ equalize :sorted_operands
74
+
75
+ # Returns the parent operation
76
+ #
77
+ # @return [AbstractOperation]
78
+ # the parent operation
79
+ #
80
+ # @api semipublic
81
+ attr_accessor :parent
82
+
83
+ # Returns the child operations and comparisons
84
+ #
85
+ # @return [Set<AbstractOperation, AbstractComparison, Array>]
86
+ # the set of operations and comparisons
87
+ #
88
+ # @api semipublic
89
+ attr_reader :operands
90
+
91
+ alias_method :children, :operands
92
+
93
+ # Returns the classes that inherit from AbstractComparison
94
+ #
95
+ # @return [Set]
96
+ # the descendant classes
97
+ #
98
+ # @api private
99
+ def self.descendants
100
+ @descendants ||= DescendantSet.new
101
+ end
102
+
103
+ # Hook executed when inheriting from AbstractComparison
104
+ #
105
+ # @return [undefined]
106
+ #
107
+ # @api private
108
+ def self.inherited(descendant)
109
+ descendants << descendant
110
+ super
111
+ end
112
+
113
+ # Get and set the slug for the operation class
114
+ #
115
+ # @param [Symbol] slug
116
+ # optionally set the slug for the operation class
117
+ #
118
+ # @return [Symbol]
119
+ # the slug for the operation class
120
+ #
121
+ # @api semipublic
122
+ def self.slug(slug = nil)
123
+ slug ? @slug = slug : @slug
124
+ end
125
+
126
+ # Return the comparison class slug
127
+ #
128
+ # @return [Symbol]
129
+ # the comparison class slug
130
+ #
131
+ # @api private
132
+ def slug
133
+ self.class.slug
134
+ end
135
+
136
+ # Get the first operand
137
+ #
138
+ # @return [AbstractOperation, AbstractComparison, Array]
139
+ # returns the first operand
140
+ #
141
+ # @api semipublic
142
+ def first
143
+ each { |operand| return operand }
144
+ nil
145
+ end
146
+
147
+ # Iterate through each operand in the operation
148
+ #
149
+ # @yield [operand]
150
+ # yields to each operand
151
+ #
152
+ # @yieldparam [AbstractOperation, AbstractComparison, Array] operand
153
+ # each operand
154
+ #
155
+ # @return [self]
156
+ # returns the operation
157
+ #
158
+ # @api semipublic
159
+ def each
160
+ @operands.each { |op| yield op }
161
+ self
162
+ end
163
+
164
+ # Test to see if there are operands
165
+ #
166
+ # @return [Boolean]
167
+ # returns true if there are operands
168
+ #
169
+ # @api semipublic
170
+ def empty?
171
+ @operands.empty?
172
+ end
173
+
174
+ # Test to see if there is one operand
175
+ #
176
+ # @return [Boolean]
177
+ # true if there is only one operand
178
+ #
179
+ # @api semipublic
180
+ def one?
181
+ @operands.size == 1
182
+ end
183
+
184
+ # Test if the operation is valid
185
+ #
186
+ # @return [Boolean]
187
+ # true if the operation is valid, false if not
188
+ #
189
+ # @api semipublic
190
+ def valid?
191
+ any? && all? { |op| valid_operand?(op) }
192
+ end
193
+
194
+ # Add an operand to the operation
195
+ #
196
+ # @param [AbstractOperation, AbstractComparison, Array] operand
197
+ # the operand to add
198
+ #
199
+ # @return [self]
200
+ # the operation
201
+ #
202
+ # @api semipublic
203
+ def <<(operand)
204
+ assert_valid_operand_type(operand)
205
+ @operands << relate_operand(operand)
206
+ self
207
+ end
208
+
209
+ # Add operands to the operation
210
+ #
211
+ # @param [#each] operands
212
+ # the operands to add
213
+ #
214
+ # @return [self]
215
+ # the operation
216
+ #
217
+ # @api semipublic
218
+ def merge(operands)
219
+ operands.each { |op| self << op }
220
+ self
221
+ end
222
+
223
+ # Return the union with another operand
224
+ #
225
+ # @param [AbstractOperation] other
226
+ # the operand to union with
227
+ #
228
+ # @return [OrOperation]
229
+ # the union of the operation and operand
230
+ #
231
+ # @api semipublic
232
+ def union(other)
233
+ Operation.new(:or, dup, other.dup).minimize
234
+ end
235
+
236
+ alias_method :|, :union
237
+ alias_method :+, :union
238
+
239
+ # Return the intersection of the operation and another operand
240
+ #
241
+ # @param [AbstractOperation] other
242
+ # the operand to intersect with
243
+ #
244
+ # @return [AndOperation]
245
+ # the intersection of the operation and operand
246
+ #
247
+ # @api semipublic
248
+ def intersection(other)
249
+ Operation.new(:and, dup, other.dup).minimize
250
+ end
251
+
252
+ alias_method :&, :intersection
253
+
254
+ # Return the difference of the operation and another operand
255
+ #
256
+ # @param [AbstractOperation] other
257
+ # the operand to not match
258
+ #
259
+ # @return [AndOperation]
260
+ # the intersection of the operation and operand
261
+ #
262
+ # @api semipublic
263
+ def difference(other)
264
+ Operation.new(:and, dup, Operation.new(:not, other.dup)).minimize
265
+ end
266
+
267
+ alias_method :-, :difference
268
+
269
+ # Minimize the operation
270
+ #
271
+ # @return [self]
272
+ # the minimized operation
273
+ #
274
+ # @api semipublic
275
+ def minimize
276
+ self
277
+ end
278
+
279
+ # Clear the operands
280
+ #
281
+ # @return [self]
282
+ # the operation
283
+ #
284
+ # @api semipublic
285
+ def clear
286
+ @operands.clear
287
+ self
288
+ end
289
+
290
+ # Return the string representation of the operation
291
+ #
292
+ # @return [String]
293
+ # the string representation of the operation
294
+ #
295
+ # @api semipublic
296
+ def to_s
297
+ empty? ? '' : "(#{sort_by { |op| op.to_s }.map { |op| op.to_s }.join(" #{slug.to_s.upcase} ")})"
298
+ end
299
+
300
+ # Test if the operation is negated
301
+ #
302
+ # Defaults to return false.
303
+ #
304
+ # @return [Boolean]
305
+ # true if the operation is negated, false if not
306
+ #
307
+ # @api private
308
+ def negated?
309
+ parent = self.parent
310
+ parent ? parent.negated? : false
311
+ end
312
+
313
+ # Return a list of operands in predictable order
314
+ #
315
+ # @return [Array<AbstractOperation, AbstractComparison, Array>]
316
+ # list of operands sorted in deterministic order
317
+ #
318
+ # @api private
319
+ def sorted_operands
320
+ sort_by { |op| op.hash }
321
+ end
322
+
323
+ private
324
+
325
+ # Initialize an operation
326
+ #
327
+ # @param [Array<AbstractOperation, AbstractComparison, Array>] *operands
328
+ # the operands to include in the operation
329
+ #
330
+ # @return [AbstractOperation]
331
+ # the operation
332
+ #
333
+ # @api semipublic
334
+ def initialize(*operands)
335
+ @operands = Set.new
336
+ merge(operands)
337
+ end
338
+
339
+ # Copy an operation
340
+ #
341
+ # @param [AbstractOperation] original
342
+ # the original operation
343
+ #
344
+ # @return [undefined]
345
+ #
346
+ # @api semipublic
347
+ def initialize_copy(*)
348
+ @operands = map { |op| op.dup }.to_set
349
+ end
350
+
351
+ # Minimize the operands recursively
352
+ #
353
+ # @return [undefined]
354
+ #
355
+ # @api private
356
+ def minimize_operands
357
+ # FIXME: why does Set#map! not work here?
358
+ @operands = map do |op|
359
+ relate_operand(op.respond_to?(:minimize) ? op.minimize : op)
360
+ end.to_set
361
+ end
362
+
363
+ # Prune empty operands recursively
364
+ #
365
+ # @return [undefined]
366
+ #
367
+ # @api private
368
+ def prune_operands
369
+ @operands.delete_if { |op| op.respond_to?(:empty?) ? op.empty? : false }
370
+ end
371
+
372
+ # Test if the operand is valid
373
+ #
374
+ # @param [AbstractOperation, AbstractComparison, Array] operand
375
+ # the operand to test
376
+ #
377
+ # @return [Boolean]
378
+ # true if the operand is valid
379
+ #
380
+ # @api private
381
+ def valid_operand?(operand)
382
+ if operand.respond_to?(:valid?)
383
+ operand.valid?
384
+ else
385
+ true
386
+ end
387
+ end
388
+
389
+ # Set self to be the operand's parent
390
+ #
391
+ # @return [AbstractOperation, AbstractComparison, Array]
392
+ # the operand that was related to self
393
+ #
394
+ # @api private
395
+ def relate_operand(operand)
396
+ operand.parent = self if operand.respond_to?(:parent=)
397
+ operand
398
+ end
399
+
400
+ # Assert that the operand is a valid type
401
+ #
402
+ # @param [AbstractOperation, AbstractComparison, Array] operand
403
+ # the operand to test
404
+ #
405
+ # @return [undefined]
406
+ #
407
+ # @raise [ArgumentError]
408
+ # raised if the operand is not a valid type
409
+ #
410
+ # @api private
411
+ def assert_valid_operand_type(operand)
412
+ assert_kind_of 'operand', operand, AbstractOperation, AbstractComparison, Array
413
+ end
414
+ end # class AbstractOperation
415
+
416
+ module FlattenOperation
417
+ # Add an operand to the operation, flattening the same types
418
+ #
419
+ # Flattening means that if the operand is the same as the
420
+ # operation, we should just include the operand's operands
421
+ # in the operation and prune that part of the tree. This results
422
+ # in a shallower tree, is faster to match and usually generates
423
+ # more efficient queries in the adapters.
424
+ #
425
+ # @param [AbstractOperation, AbstractComparison, Array] operand
426
+ # the operand to add
427
+ #
428
+ # @return [self]
429
+ # the operation
430
+ #
431
+ # @api semipublic
432
+ def <<(operand)
433
+ if kind_of?(operand.class)
434
+ merge(operand.operands)
435
+ else
436
+ super
437
+ end
438
+ end
439
+ end # module FlattenOperation
440
+
441
+ class AndOperation < AbstractOperation
442
+ include FlattenOperation
443
+
444
+ slug :and
445
+
446
+ # Match the record
447
+ #
448
+ # @example with a Hash
449
+ # operation.matches?({ :id => 1 }) # => true
450
+ #
451
+ # @example with a Resource
452
+ # operation.matches?(Blog::Article.new(:id => 1)) # => true
453
+ #
454
+ # @param [Resource, Hash] record
455
+ # the resource to match
456
+ #
457
+ # @return [true]
458
+ # true if the record matches, false if not
459
+ #
460
+ # @api semipublic
461
+ def matches?(record)
462
+ all? { |op| op.respond_to?(:matches?) ? op.matches?(record) : true }
463
+ end
464
+
465
+ # Minimize the operation
466
+ #
467
+ # @return [self]
468
+ # the minimized AndOperation
469
+ # @return [AbstractOperation, AbstractComparison, Array]
470
+ # the minimized operation
471
+ #
472
+ # @api semipublic
473
+ def minimize
474
+ minimize_operands
475
+
476
+ return Operation.new(:null) if any? && all? { |op| op.nil? }
477
+
478
+ prune_operands
479
+
480
+ one? ? first : self
481
+ end
482
+ end # class AndOperation
483
+
484
+ class OrOperation < AbstractOperation
485
+ include FlattenOperation
486
+
487
+ slug :or
488
+
489
+ # Match the record
490
+ #
491
+ # @param [Resource, Hash] record
492
+ # the resource to match
493
+ #
494
+ # @return [true]
495
+ # true if the record matches, false if not
496
+ #
497
+ # @api semipublic
498
+ def matches?(record)
499
+ any? { |op| op.respond_to?(:matches?) ? op.matches?(record) : true }
500
+ end
501
+
502
+ # Test if the operation is valid
503
+ #
504
+ # An OrOperation is valid if one of it's operands is valid.
505
+ #
506
+ # @return [Boolean]
507
+ # true if the operation is valid, false if not
508
+ #
509
+ # @api semipublic
510
+ def valid?
511
+ any? { |op| valid_operand?(op) }
512
+ end
513
+
514
+ # Minimize the operation
515
+ #
516
+ # @return [self]
517
+ # the minimized OrOperation
518
+ # @return [AbstractOperation, AbstractComparison, Array]
519
+ # the minimized operation
520
+ #
521
+ # @api semipublic
522
+ def minimize
523
+ minimize_operands
524
+
525
+ return Operation.new(:null) if any? { |op| op.nil? }
526
+
527
+ prune_operands
528
+
529
+ one? ? first : self
530
+ end
531
+ end # class OrOperation
532
+
533
+ class NotOperation < AbstractOperation
534
+ slug :not
535
+
536
+ # Match the record
537
+ #
538
+ # @param [Resource, Hash] record
539
+ # the resource to match
540
+ #
541
+ # @return [true]
542
+ # true if the record matches, false if not
543
+ #
544
+ # @api semipublic
545
+ def matches?(record)
546
+ operand = self.operand
547
+ operand.respond_to?(:matches?) ? !operand.matches?(record) : true
548
+ end
549
+
550
+ # Add an operand to the operation
551
+ #
552
+ # This will only allow a single operand to be added.
553
+ #
554
+ # @param [AbstractOperation, AbstractComparison, Array] operand
555
+ # the operand to add
556
+ #
557
+ # @return [self]
558
+ # the operation
559
+ #
560
+ # @api semipublic
561
+ def <<(operand)
562
+ assert_one_operand(operand)
563
+ assert_no_self_reference(operand)
564
+ super
565
+ end
566
+
567
+ # Return the only operand in the operation
568
+ #
569
+ # @return [AbstractOperation, AbstractComparison, Array]
570
+ # the operand
571
+ #
572
+ # @api semipublic
573
+ def operand
574
+ first
575
+ end
576
+
577
+ # Minimize the operation
578
+ #
579
+ # @return [self]
580
+ # the minimized NotOperation
581
+ # @return [AbstractOperation, AbstractComparison, Array]
582
+ # the minimized operation
583
+ #
584
+ # @api semipublic
585
+ def minimize
586
+ minimize_operands
587
+ prune_operands
588
+
589
+ # factor out double negatives if possible
590
+ operand = self.operand
591
+ one? && instance_of?(operand.class) ? operand.operand : self
592
+ end
593
+
594
+ # Return the string representation of the operation
595
+ #
596
+ # @return [String]
597
+ # the string representation of the operation
598
+ #
599
+ # @api semipublic
600
+ def to_s
601
+ empty? ? '' : "NOT(#{operand.to_s})"
602
+ end
603
+
604
+ # Test if the operation is negated
605
+ #
606
+ # Defaults to return false.
607
+ #
608
+ # @return [Boolean]
609
+ # true if the operation is negated, false if not
610
+ #
611
+ # @api private
612
+ def negated?
613
+ parent = self.parent
614
+ parent ? !parent.negated? : true
615
+ end
616
+
617
+ private
618
+
619
+ # Assert there is only one operand
620
+ #
621
+ # @param [AbstractOperation, AbstractComparison, Array] operand
622
+ # the operand to test
623
+ #
624
+ # @return [undefined]
625
+ #
626
+ # @raise [ArgumentError]
627
+ # raised if the operand is not a valid type
628
+ #
629
+ # @api private
630
+ def assert_one_operand(operand)
631
+ unless empty? || self.operand == operand
632
+ raise ArgumentError, "#{self.class} cannot have more than one operand"
633
+ end
634
+ end
635
+
636
+ # Assert the operand is not equal to self
637
+ #
638
+ # @param [AbstractOperation, AbstractComparison, Array] operand
639
+ # the operand to test
640
+ #
641
+ # @return [undefined]
642
+ #
643
+ # @raise [ArgumentError]
644
+ # raised if object is appended to itself
645
+ #
646
+ # @api private
647
+ def assert_no_self_reference(operand)
648
+ if equal?(operand)
649
+ raise ArgumentError, 'cannot append operand to itself'
650
+ end
651
+ end
652
+ end # class NotOperation
653
+
654
+ class NullOperation < AbstractOperation
655
+ undef_method :<<
656
+ undef_method :merge
657
+
658
+ slug :null
659
+
660
+ # Match the record
661
+ #
662
+ # A NullOperation matches every record.
663
+ #
664
+ # @param [Resource, Hash] record
665
+ # the resource to match
666
+ #
667
+ # @return [true]
668
+ # every record matches
669
+ #
670
+ # @api semipublic
671
+ def matches?(record)
672
+ record.kind_of?(Hash) || record.kind_of?(Resource)
673
+ end
674
+
675
+ # Test validity of the operation
676
+ #
677
+ # A NullOperation is always valid.
678
+ #
679
+ # @return [true]
680
+ # always valid
681
+ #
682
+ # @api semipublic
683
+ def valid?
684
+ true
685
+ end
686
+
687
+ # Treat the operation the same as nil
688
+ #
689
+ # @return [true]
690
+ # should be treated as nil
691
+ #
692
+ # @api semipublic
693
+ def nil?
694
+ true
695
+ end
696
+
697
+ # Inspecting the operation should return the same as nil
698
+ #
699
+ # @return [String]
700
+ # return the string 'nil'
701
+ #
702
+ # @api semipublic
703
+ def inspect
704
+ 'nil'
705
+ end
706
+
707
+ private
708
+
709
+ # Initialize a NullOperation
710
+ #
711
+ # @return [NullOperation]
712
+ # the operation
713
+ #
714
+ # @api semipublic
715
+ def initialize
716
+ @operands = Set.new
717
+ end
718
+ end
719
+ end # module Conditions
720
+ end # class Query
721
+ end # module DataMapper