sbf-dm-core 1.3.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +44 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +468 -0
  7. data/.travis.yml +57 -0
  8. data/.yardopts +1 -0
  9. data/Gemfile +70 -0
  10. data/LICENSE +20 -0
  11. data/README.md +269 -0
  12. data/Rakefile +4 -0
  13. data/dm-core.gemspec +21 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +233 -0
  15. data/lib/dm-core/adapters/in_memory_adapter.rb +110 -0
  16. data/lib/dm-core/adapters.rb +249 -0
  17. data/lib/dm-core/associations/many_to_many.rb +477 -0
  18. data/lib/dm-core/associations/many_to_one.rb +282 -0
  19. data/lib/dm-core/associations/one_to_many.rb +332 -0
  20. data/lib/dm-core/associations/one_to_one.rb +84 -0
  21. data/lib/dm-core/associations/relationship.rb +650 -0
  22. data/lib/dm-core/backwards.rb +11 -0
  23. data/lib/dm-core/collection.rb +1486 -0
  24. data/lib/dm-core/core_ext/kernel.rb +21 -0
  25. data/lib/dm-core/core_ext/pathname.rb +4 -0
  26. data/lib/dm-core/core_ext/symbol.rb +10 -0
  27. data/lib/dm-core/identity_map.rb +6 -0
  28. data/lib/dm-core/model/hook.rb +99 -0
  29. data/lib/dm-core/model/is.rb +30 -0
  30. data/lib/dm-core/model/property.rb +244 -0
  31. data/lib/dm-core/model/relationship.rb +366 -0
  32. data/lib/dm-core/model/scope.rb +87 -0
  33. data/lib/dm-core/model.rb +876 -0
  34. data/lib/dm-core/property/binary.rb +19 -0
  35. data/lib/dm-core/property/boolean.rb +35 -0
  36. data/lib/dm-core/property/class.rb +23 -0
  37. data/lib/dm-core/property/date.rb +45 -0
  38. data/lib/dm-core/property/date_time.rb +44 -0
  39. data/lib/dm-core/property/decimal.rb +47 -0
  40. data/lib/dm-core/property/discriminator.rb +40 -0
  41. data/lib/dm-core/property/float.rb +27 -0
  42. data/lib/dm-core/property/integer.rb +32 -0
  43. data/lib/dm-core/property/invalid_value_error.rb +17 -0
  44. data/lib/dm-core/property/lookup.rb +26 -0
  45. data/lib/dm-core/property/numeric.rb +35 -0
  46. data/lib/dm-core/property/object.rb +33 -0
  47. data/lib/dm-core/property/serial.rb +13 -0
  48. data/lib/dm-core/property/string.rb +47 -0
  49. data/lib/dm-core/property/text.rb +12 -0
  50. data/lib/dm-core/property/time.rb +46 -0
  51. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  52. data/lib/dm-core/property/typecast/time.rb +33 -0
  53. data/lib/dm-core/property.rb +856 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query/conditions/comparison.rb +886 -0
  56. data/lib/dm-core/query/conditions/operation.rb +710 -0
  57. data/lib/dm-core/query/direction.rb +33 -0
  58. data/lib/dm-core/query/operator.rb +34 -0
  59. data/lib/dm-core/query/path.rb +113 -0
  60. data/lib/dm-core/query/sort.rb +38 -0
  61. data/lib/dm-core/query.rb +1352 -0
  62. data/lib/dm-core/relationship_set.rb +69 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource/persistence_state/clean.rb +36 -0
  65. data/lib/dm-core/resource/persistence_state/deleted.rb +26 -0
  66. data/lib/dm-core/resource/persistence_state/dirty.rb +91 -0
  67. data/lib/dm-core/resource/persistence_state/immutable.rb +32 -0
  68. data/lib/dm-core/resource/persistence_state/persisted.rb +25 -0
  69. data/lib/dm-core/resource/persistence_state/transient.rb +87 -0
  70. data/lib/dm-core/resource/persistence_state.rb +70 -0
  71. data/lib/dm-core/resource.rb +1220 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +63 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +21 -0
  74. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  75. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  76. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  77. data/lib/dm-core/spec/setup.rb +164 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +366 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1221 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +184 -0
  83. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  84. data/lib/dm-core/support/assertions.rb +8 -0
  85. data/lib/dm-core/support/chainable.rb +18 -0
  86. data/lib/dm-core/support/deprecate.rb +12 -0
  87. data/lib/dm-core/support/descendant_set.rb +89 -0
  88. data/lib/dm-core/support/equalizer.rb +48 -0
  89. data/lib/dm-core/support/ext/array.rb +22 -0
  90. data/lib/dm-core/support/ext/blank.rb +25 -0
  91. data/lib/dm-core/support/ext/hash.rb +67 -0
  92. data/lib/dm-core/support/ext/module.rb +47 -0
  93. data/lib/dm-core/support/ext/object.rb +57 -0
  94. data/lib/dm-core/support/ext/string.rb +24 -0
  95. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  96. data/lib/dm-core/support/hook.rb +388 -0
  97. data/lib/dm-core/support/inflections.rb +60 -0
  98. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  99. data/lib/dm-core/support/inflector/methods.rb +151 -0
  100. data/lib/dm-core/support/lazy_array.rb +451 -0
  101. data/lib/dm-core/support/local_object_space.rb +13 -0
  102. data/lib/dm-core/support/logger.rb +201 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +109 -0
  105. data/lib/dm-core/support/ordered_set.rb +381 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +251 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/lib/dm-core.rb +274 -0
  110. data/script/performance.rb +275 -0
  111. data/script/profile.rb +218 -0
  112. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  113. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +69 -0
  114. data/spec/public/associations/many_to_many_spec.rb +197 -0
  115. data/spec/public/associations/many_to_one_spec.rb +83 -0
  116. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  117. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  118. data/spec/public/associations/one_to_many_spec.rb +81 -0
  119. data/spec/public/associations/one_to_one_spec.rb +176 -0
  120. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  121. data/spec/public/collection_spec.rb +69 -0
  122. data/spec/public/finalize_spec.rb +77 -0
  123. data/spec/public/model/hook_spec.rb +245 -0
  124. data/spec/public/model/property_spec.rb +91 -0
  125. data/spec/public/model/relationship_spec.rb +1040 -0
  126. data/spec/public/model_spec.rb +456 -0
  127. data/spec/public/property/binary_spec.rb +43 -0
  128. data/spec/public/property/boolean_spec.rb +21 -0
  129. data/spec/public/property/class_spec.rb +27 -0
  130. data/spec/public/property/date_spec.rb +21 -0
  131. data/spec/public/property/date_time_spec.rb +21 -0
  132. data/spec/public/property/decimal_spec.rb +23 -0
  133. data/spec/public/property/discriminator_spec.rb +134 -0
  134. data/spec/public/property/float_spec.rb +22 -0
  135. data/spec/public/property/integer_spec.rb +22 -0
  136. data/spec/public/property/object_spec.rb +117 -0
  137. data/spec/public/property/serial_spec.rb +22 -0
  138. data/spec/public/property/string_spec.rb +21 -0
  139. data/spec/public/property/text_spec.rb +62 -0
  140. data/spec/public/property/time_spec.rb +21 -0
  141. data/spec/public/property_spec.rb +333 -0
  142. data/spec/public/resource/state_spec.rb +72 -0
  143. data/spec/public/resource_spec.rb +289 -0
  144. data/spec/public/sel_spec.rb +53 -0
  145. data/spec/public/setup_spec.rb +145 -0
  146. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  147. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  148. data/spec/public/shared/collection_shared_spec.rb +1637 -0
  149. data/spec/public/shared/finder_shared_spec.rb +1647 -0
  150. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  151. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
  152. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  153. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  154. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  155. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  156. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  157. data/spec/semipublic/associations_spec.rb +177 -0
  158. data/spec/semipublic/collection_spec.rb +110 -0
  159. data/spec/semipublic/model_spec.rb +96 -0
  160. data/spec/semipublic/property/binary_spec.rb +13 -0
  161. data/spec/semipublic/property/boolean_spec.rb +47 -0
  162. data/spec/semipublic/property/class_spec.rb +33 -0
  163. data/spec/semipublic/property/date_spec.rb +43 -0
  164. data/spec/semipublic/property/date_time_spec.rb +46 -0
  165. data/spec/semipublic/property/decimal_spec.rb +83 -0
  166. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  167. data/spec/semipublic/property/float_spec.rb +82 -0
  168. data/spec/semipublic/property/integer_spec.rb +82 -0
  169. data/spec/semipublic/property/lookup_spec.rb +29 -0
  170. data/spec/semipublic/property/serial_spec.rb +13 -0
  171. data/spec/semipublic/property/string_spec.rb +13 -0
  172. data/spec/semipublic/property/text_spec.rb +31 -0
  173. data/spec/semipublic/property/time_spec.rb +50 -0
  174. data/spec/semipublic/property_spec.rb +114 -0
  175. data/spec/semipublic/query/conditions/comparison_spec.rb +1502 -0
  176. data/spec/semipublic/query/conditions/operation_spec.rb +1296 -0
  177. data/spec/semipublic/query/path_spec.rb +471 -0
  178. data/spec/semipublic/query_spec.rb +3665 -0
  179. data/spec/semipublic/resource/state/clean_spec.rb +89 -0
  180. data/spec/semipublic/resource/state/deleted_spec.rb +79 -0
  181. data/spec/semipublic/resource/state/dirty_spec.rb +163 -0
  182. data/spec/semipublic/resource/state/immutable_spec.rb +107 -0
  183. data/spec/semipublic/resource/state/transient_spec.rb +163 -0
  184. data/spec/semipublic/resource/state_spec.rb +230 -0
  185. data/spec/semipublic/resource_spec.rb +23 -0
  186. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  187. data/spec/semipublic/shared/resource_shared_spec.rb +198 -0
  188. data/spec/semipublic/shared/resource_state_shared_spec.rb +91 -0
  189. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  190. data/spec/spec_helper.rb +34 -0
  191. data/spec/support/core_ext/hash.rb +10 -0
  192. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  193. data/spec/support/properties/huge_integer.rb +17 -0
  194. data/spec/unit/array_spec.rb +23 -0
  195. data/spec/unit/blank_spec.rb +73 -0
  196. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  197. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  198. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  199. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  200. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  201. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  202. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  203. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  204. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  205. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  206. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  207. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  208. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  216. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  217. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  218. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  219. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  220. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  221. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  222. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  223. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  224. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  226. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  227. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  228. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  229. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  230. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  231. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  232. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  233. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  237. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  239. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  240. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  241. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  242. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  243. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  244. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  245. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  246. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  247. data/spec/unit/hash_spec.rb +27 -0
  248. data/spec/unit/hook_spec.rb +1216 -0
  249. data/spec/unit/inflections_spec.rb +14 -0
  250. data/spec/unit/lazy_array_spec.rb +1949 -0
  251. data/spec/unit/mash_spec.rb +289 -0
  252. data/spec/unit/module_spec.rb +70 -0
  253. data/spec/unit/object_spec.rb +38 -0
  254. data/spec/unit/try_dup_spec.rb +46 -0
  255. data/tasks/ci.rake +1 -0
  256. data/tasks/spec.rake +18 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +323 -0
@@ -0,0 +1,710 @@
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
+ raise ArgumentError, "No Operation class for #{slug.inspect} has been defined" unless (klass = operation_class(slug))
21
+
22
+ klass.new(*operands)
23
+ end
24
+
25
+ # Return an Array of all the slugs for the operation classes
26
+ #
27
+ # @return [Array]
28
+ # the slugs of all the operation classes
29
+ #
30
+ # @api private
31
+ def self.slugs
32
+ AbstractOperation.descendants.map(&:slug)
33
+ end
34
+
35
+ class << self
36
+ # Returns a Hash mapping the slugs to each class
37
+ #
38
+ # @return [Hash]
39
+ # Hash mapping the slug to the class
40
+ #
41
+ # @api private
42
+ private def operation_classes
43
+ @operation_classes ||= {}
44
+ end
45
+
46
+ # Lookup the operation class based on the slug
47
+ #
48
+ # @example
49
+ # operation_class = Operation.operation_class(:and)
50
+ #
51
+ # @param [Symbol] slug
52
+ # the identifier for the operation class
53
+ #
54
+ # @return [Class]
55
+ # the operation class
56
+ #
57
+ # @api private
58
+ private def operation_class(slug)
59
+ operation_classes[slug] ||= AbstractOperation.descendants.detect { |operation_class| operation_class.slug == slug }
60
+ end
61
+ end
62
+ end
63
+
64
+ class AbstractOperation
65
+ include Enumerable, DataMapper::Assertions
66
+ extend Equalizer
67
+
68
+ equalize :sorted_operands
69
+
70
+ # Returns the parent operation
71
+ #
72
+ # @return [AbstractOperation]
73
+ # the parent operation
74
+ #
75
+ # @api semipublic
76
+ attr_accessor :parent
77
+
78
+ # Returns the child operations and comparisons
79
+ #
80
+ # @return [Set<AbstractOperation, AbstractComparison, Array>]
81
+ # the set of operations and comparisons
82
+ #
83
+ # @api semipublic
84
+ attr_reader :operands
85
+
86
+ alias_method :children, :operands
87
+
88
+ # Returns the classes that inherit from AbstractComparison
89
+ #
90
+ # @return [Set]
91
+ # the descendant classes
92
+ #
93
+ # @api private
94
+ def self.descendants
95
+ @descendants ||= DescendantSet.new
96
+ end
97
+
98
+ # Hook executed when inheriting from AbstractComparison
99
+ #
100
+ # @return [undefined]
101
+ #
102
+ # @api private
103
+ def self.inherited(descendant)
104
+ descendants << descendant
105
+ super
106
+ end
107
+
108
+ # Get and set the slug for the operation class
109
+ #
110
+ # @param [Symbol] slug
111
+ # optionally set the slug for the operation class
112
+ #
113
+ # @return [Symbol]
114
+ # the slug for the operation class
115
+ #
116
+ # @api semipublic
117
+ def self.slug(slug = nil)
118
+ slug ? @slug = slug : @slug
119
+ end
120
+
121
+ # Return the comparison class slug
122
+ #
123
+ # @return [Symbol]
124
+ # the comparison class slug
125
+ #
126
+ # @api private
127
+ def slug
128
+ self.class.slug
129
+ end
130
+
131
+ # Get the first operand
132
+ #
133
+ # @return [AbstractOperation, AbstractComparison, Array]
134
+ # returns the first operand
135
+ #
136
+ # @api semipublic
137
+ def first
138
+ each { |operand| return operand }
139
+ nil
140
+ end
141
+
142
+ # Iterate through each operand in the operation
143
+ #
144
+ # @yield [operand]
145
+ # yields to each operand
146
+ #
147
+ # @yieldparam [AbstractOperation, AbstractComparison, Array] operand
148
+ # each operand
149
+ #
150
+ # @return [self]
151
+ # returns the operation
152
+ #
153
+ # @api semipublic
154
+ def each(&block)
155
+ @operands.each(&block)
156
+ self
157
+ end
158
+
159
+ # Test to see if there are operands
160
+ #
161
+ # @return [Boolean]
162
+ # returns true if there are operands
163
+ #
164
+ # @api semipublic
165
+ def empty?
166
+ @operands.empty?
167
+ end
168
+
169
+ # Test to see if there is one operand
170
+ #
171
+ # @return [Boolean]
172
+ # true if there is only one operand
173
+ #
174
+ # @api semipublic
175
+ def one?
176
+ @operands.size == 1
177
+ end
178
+
179
+ # Test if the operation is valid
180
+ #
181
+ # @return [Boolean]
182
+ # true if the operation is valid, false if not
183
+ #
184
+ # @api semipublic
185
+ def valid?
186
+ any? && all? { |op| valid_operand?(op) }
187
+ end
188
+
189
+ # Add an operand to the operation
190
+ #
191
+ # @param [AbstractOperation, AbstractComparison, Array] operand
192
+ # the operand to add
193
+ #
194
+ # @return [self]
195
+ # the operation
196
+ #
197
+ # @api semipublic
198
+ def <<(operand)
199
+ assert_valid_operand_type(operand)
200
+ @operands << relate_operand(operand)
201
+ self
202
+ end
203
+
204
+ # Add operands to the operation
205
+ #
206
+ # @param [#each] operands
207
+ # the operands to add
208
+ #
209
+ # @return [self]
210
+ # the operation
211
+ #
212
+ # @api semipublic
213
+ def merge(operands)
214
+ operands.each { |op| self << op }
215
+ self
216
+ end
217
+
218
+ # Return the union with another operand
219
+ #
220
+ # @param [AbstractOperation] other
221
+ # the operand to union with
222
+ #
223
+ # @return [OrOperation]
224
+ # the union of the operation and operand
225
+ #
226
+ # @api semipublic
227
+ def union(other)
228
+ Operation.new(:or, dup, other.dup).minimize
229
+ end
230
+
231
+ alias_method :|, :union
232
+ alias_method :+, :union
233
+
234
+ # Return the intersection of the operation and another operand
235
+ #
236
+ # @param [AbstractOperation] other
237
+ # the operand to intersect with
238
+ #
239
+ # @return [AndOperation]
240
+ # the intersection of the operation and operand
241
+ #
242
+ # @api semipublic
243
+ def intersection(other)
244
+ Operation.new(:and, dup, other.dup).minimize
245
+ end
246
+
247
+ alias_method :&, :intersection
248
+
249
+ # Return the difference of the operation and another operand
250
+ #
251
+ # @param [AbstractOperation] other
252
+ # the operand to not match
253
+ #
254
+ # @return [AndOperation]
255
+ # the intersection of the operation and operand
256
+ #
257
+ # @api semipublic
258
+ def difference(other)
259
+ Operation.new(:and, dup, Operation.new(:not, other.dup)).minimize
260
+ end
261
+
262
+ alias_method :-, :difference
263
+
264
+ # Minimize the operation
265
+ #
266
+ # @return [self]
267
+ # the minimized operation
268
+ #
269
+ # @api semipublic
270
+ def minimize
271
+ self
272
+ end
273
+
274
+ # Clear the operands
275
+ #
276
+ # @return [self]
277
+ # the operation
278
+ #
279
+ # @api semipublic
280
+ def clear
281
+ @operands.clear
282
+ self
283
+ end
284
+
285
+ # Return the string representation of the operation
286
+ #
287
+ # @return [String]
288
+ # the string representation of the operation
289
+ #
290
+ # @api semipublic
291
+ def to_s
292
+ empty? ? '' : "(#{sort_by(&:to_s).map(&:to_s).join(" #{slug.to_s.upcase} ")})"
293
+ end
294
+
295
+ # Test if the operation is negated
296
+ #
297
+ # Defaults to return false.
298
+ #
299
+ # @return [Boolean]
300
+ # true if the operation is negated, false if not
301
+ #
302
+ # @api private
303
+ def negated?
304
+ parent = self.parent
305
+ parent ? parent.negated? : false
306
+ end
307
+
308
+ # Return a list of operands in predictable order
309
+ #
310
+ # @return [Array<AbstractOperation, AbstractComparison, Array>]
311
+ # list of operands sorted in deterministic order
312
+ #
313
+ # @api private
314
+ def sorted_operands
315
+ sort_by(&:hash)
316
+ end
317
+
318
+ # Initialize an operation
319
+ #
320
+ # @param [Array<AbstractOperation, AbstractComparison, Array>] *operands
321
+ # the operands to include in the operation
322
+ #
323
+ # @return [AbstractOperation]
324
+ # the operation
325
+ #
326
+ # @api semipublic
327
+ private def initialize(*operands)
328
+ @operands = Set.new
329
+ merge(operands)
330
+ end
331
+
332
+ # Copy an operation
333
+ #
334
+ # @param [AbstractOperation] *
335
+ # the original operation
336
+ #
337
+ # @return [undefined]
338
+ #
339
+ # @api semipublic
340
+ private def initialize_copy(*)
341
+ @operands = to_set(&:dup)
342
+ end
343
+
344
+ # Minimize the operands recursively
345
+ #
346
+ # @return [undefined]
347
+ #
348
+ # @api private
349
+ private def minimize_operands
350
+ # FIXME: why does Set#map! not work here?
351
+ @operands = to_set do |op|
352
+ relate_operand(op.respond_to?(:minimize) ? op.minimize : op)
353
+ end
354
+ end
355
+
356
+ # Prune empty operands recursively
357
+ #
358
+ # @return [undefined]
359
+ #
360
+ # @api private
361
+ private def prune_operands
362
+ @operands.delete_if { |op| op.respond_to?(:empty?) ? op.empty? : false }
363
+ end
364
+
365
+ # Test if the operand is valid
366
+ #
367
+ # @param [AbstractOperation, AbstractComparison, Array] operand
368
+ # the operand to test
369
+ #
370
+ # @return [Boolean]
371
+ # true if the operand is valid
372
+ #
373
+ # @api private
374
+ private def valid_operand?(operand)
375
+ if operand.respond_to?(:valid?)
376
+ operand.valid?
377
+ else
378
+ true
379
+ end
380
+ end
381
+
382
+ # Set self to be the operand's parent
383
+ #
384
+ # @return [AbstractOperation, AbstractComparison, Array]
385
+ # the operand that was related to self
386
+ #
387
+ # @api private
388
+ private def relate_operand(operand)
389
+ operand.parent = self if operand.respond_to?(:parent=)
390
+ operand
391
+ end
392
+
393
+ # Assert that the operand is a valid type
394
+ #
395
+ # @param [AbstractOperation, AbstractComparison, Array] operand
396
+ # the operand to test
397
+ #
398
+ # @return [undefined]
399
+ #
400
+ # @raise [ArgumentError]
401
+ # raised if the operand is not a valid type
402
+ #
403
+ # @api private
404
+ private def assert_valid_operand_type(operand)
405
+ assert_kind_of 'operand', operand, AbstractOperation, AbstractComparison, Array
406
+ end
407
+ end
408
+
409
+ module FlattenOperation
410
+ # Add an operand to the operation, flattening the same types
411
+ #
412
+ # Flattening means that if the operand is the same as the
413
+ # operation, we should just include the operand's operands
414
+ # in the operation and prune that part of the tree. This results
415
+ # in a shallower tree, is faster to match and usually generates
416
+ # more efficient queries in the adapters.
417
+ #
418
+ # @param [AbstractOperation, AbstractComparison, Array] operand
419
+ # the operand to add
420
+ #
421
+ # @return [self]
422
+ # the operation
423
+ #
424
+ # @api semipublic
425
+ def <<(operand)
426
+ if is_a?(operand.class)
427
+ merge(operand.operands)
428
+ else
429
+ super
430
+ end
431
+ end
432
+ end
433
+
434
+ class AndOperation < AbstractOperation
435
+ include FlattenOperation
436
+
437
+ slug :and
438
+
439
+ # Match the record
440
+ #
441
+ # @example with a Hash
442
+ # operation.matches?({ :id => 1 }) # => true
443
+ #
444
+ # @example with a Resource
445
+ # operation.matches?(Blog::Article.new(:id => 1)) # => true
446
+ #
447
+ # @param [Resource, Hash] record
448
+ # the resource to match
449
+ #
450
+ # @return [true]
451
+ # true if the record matches, false if not
452
+ #
453
+ # @api semipublic
454
+ def matches?(record)
455
+ all? { |op| op.respond_to?(:matches?) ? op.matches?(record) : true }
456
+ end
457
+
458
+ # Minimize the operation
459
+ #
460
+ # @return [self]
461
+ # the minimized AndOperation
462
+ # @return [AbstractOperation, AbstractComparison, Array]
463
+ # the minimized operation
464
+ #
465
+ # @api semipublic
466
+ def minimize
467
+ minimize_operands
468
+
469
+ return Operation.new(:null) if any? && all?(&:nil?)
470
+
471
+ prune_operands
472
+
473
+ one? ? first : self
474
+ end
475
+ end
476
+
477
+ class OrOperation < AbstractOperation
478
+ include FlattenOperation
479
+
480
+ slug :or
481
+
482
+ # Match the record
483
+ #
484
+ # @param [Resource, Hash] record
485
+ # the resource to match
486
+ #
487
+ # @return [boolean]
488
+ # true if the record matches, false if not
489
+ #
490
+ # @api semipublic
491
+ def matches?(record)
492
+ any? { |op| op.respond_to?(:matches?) ? op.matches?(record) : true }
493
+ end
494
+
495
+ # Test if the operation is valid
496
+ #
497
+ # An OrOperation is valid if one of it's operands is valid.
498
+ #
499
+ # @return [Boolean]
500
+ # true if the operation is valid, false if not
501
+ #
502
+ # @api semipublic
503
+ def valid?
504
+ any? { |op| valid_operand?(op) }
505
+ end
506
+
507
+ # Minimize the operation
508
+ #
509
+ # @return [self]
510
+ # the minimized OrOperation
511
+ # @return [AbstractOperation, AbstractComparison, Array]
512
+ # the minimized operation
513
+ #
514
+ # @api semipublic
515
+ def minimize
516
+ minimize_operands
517
+
518
+ return Operation.new(:null) if any?(&:nil?)
519
+
520
+ prune_operands
521
+
522
+ one? ? first : self
523
+ end
524
+ end
525
+
526
+ class NotOperation < AbstractOperation
527
+ slug :not
528
+
529
+ # Match the record
530
+ #
531
+ # @param [Resource, Hash] record
532
+ # the resource to match
533
+ #
534
+ # @return [true]
535
+ # true if the record matches, false if not
536
+ #
537
+ # @api semipublic
538
+ def matches?(record)
539
+ operand = self.operand
540
+ operand.respond_to?(:matches?) ? !operand&.matches?(record) : true
541
+ end
542
+
543
+ # Add an operand to the operation
544
+ #
545
+ # This will only allow a single operand to be added.
546
+ #
547
+ # @param [AbstractOperation, AbstractComparison, Array] operand
548
+ # the operand to add
549
+ #
550
+ # @return [self]
551
+ # the operation
552
+ #
553
+ # @api semipublic
554
+ def <<(operand)
555
+ assert_one_operand(operand)
556
+ assert_no_self_reference(operand)
557
+ super
558
+ end
559
+
560
+ # Return the only operand in the operation
561
+ #
562
+ # @return [AbstractOperation, AbstractComparison, Array]
563
+ # the operand
564
+ #
565
+ # @api semipublic
566
+ def operand
567
+ first
568
+ end
569
+
570
+ # Minimize the operation
571
+ #
572
+ # @return [self]
573
+ # the minimized NotOperation
574
+ # @return [AbstractOperation, AbstractComparison, Array]
575
+ # the minimized operation
576
+ #
577
+ # @api semipublic
578
+ def minimize
579
+ minimize_operands
580
+ prune_operands
581
+
582
+ # factor out double negatives if possible
583
+ operand = self.operand
584
+ (one? && instance_of?(operand.class)) ? operand&.operand : self
585
+ end
586
+
587
+ # Return the string representation of the operation
588
+ #
589
+ # @return [String]
590
+ # the string representation of the operation
591
+ #
592
+ # @api semipublic
593
+ def to_s
594
+ empty? ? '' : "NOT(#{operand})"
595
+ end
596
+
597
+ # Test if the operation is negated
598
+ #
599
+ # Defaults to return false.
600
+ #
601
+ # @return [Boolean]
602
+ # true if the operation is negated, false if not
603
+ #
604
+ # @api private
605
+ def negated?
606
+ parent = self.parent
607
+ parent ? !parent.negated? : true
608
+ end
609
+
610
+ # Assert there is only one operand
611
+ #
612
+ # @param [AbstractOperation, AbstractComparison, Array] operand
613
+ # the operand to test
614
+ #
615
+ # @return [undefined]
616
+ #
617
+ # @raise [ArgumentError]
618
+ # raised if the operand is not a valid type
619
+ #
620
+ # @api private
621
+ private def assert_one_operand(operand)
622
+ return if empty? || self.operand == operand
623
+
624
+ raise ArgumentError, "#{self.class} cannot have more than one operand"
625
+ end
626
+
627
+ # Assert the operand is not equal to self
628
+ #
629
+ # @param [AbstractOperation, AbstractComparison, Array] operand
630
+ # the operand to test
631
+ #
632
+ # @return [undefined]
633
+ #
634
+ # @raise [ArgumentError]
635
+ # raised if object is appended to itself
636
+ #
637
+ # @api private
638
+ private def assert_no_self_reference(operand)
639
+ return unless equal?(operand)
640
+
641
+ raise ArgumentError, 'cannot append operand to itself'
642
+ end
643
+ end
644
+
645
+ class NullOperation < AbstractOperation
646
+ undef_method :<<
647
+ undef_method :merge
648
+
649
+ slug :null
650
+
651
+ # Match the record
652
+ #
653
+ # A NullOperation matches every record.
654
+ #
655
+ # @param [Resource, Hash] record
656
+ # the resource to match
657
+ #
658
+ # @return [boolean]
659
+ # every record matches
660
+ #
661
+ # @api semipublic
662
+ def matches?(record)
663
+ record.is_a?(Hash) || record.is_a?(Resource)
664
+ end
665
+
666
+ # Test validity of the operation
667
+ #
668
+ # A NullOperation is always valid.
669
+ #
670
+ # @return [true]
671
+ # always valid
672
+ #
673
+ # @api semipublic
674
+ def valid?
675
+ true
676
+ end
677
+
678
+ # Treat the operation the same as nil
679
+ #
680
+ # @return [true]
681
+ # should be treated as nil
682
+ #
683
+ # @api semipublic
684
+ def nil?
685
+ true
686
+ end
687
+
688
+ # Inspecting the operation should return the same as nil
689
+ #
690
+ # @return [String]
691
+ # return the string 'nil'
692
+ #
693
+ # @api semipublic
694
+ def inspect
695
+ 'nil'
696
+ end
697
+
698
+ # Initialize a NullOperation
699
+ #
700
+ # @return [NullOperation]
701
+ # the operation
702
+ #
703
+ # @api semipublic
704
+ private def initialize
705
+ @operands = Set.new
706
+ end
707
+ end
708
+ end
709
+ end
710
+ end