ardm-core 1.2.1

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