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,1296 @@
1
+ require_relative '../../../spec_helper'
2
+
3
+ module OperationMatchers
4
+ class HaveValidParent
5
+ def matches?(target)
6
+ stack = []
7
+ stack << [ target, target.operands ] if target.respond_to?(:operands)
8
+
9
+ while node = stack.pop
10
+ @expected, operands = node
11
+
12
+ operands.each do |operand|
13
+ @target = operand
14
+ @actual = @target.parent
15
+
16
+ return false unless @actual.equal?(@expected)
17
+
18
+ if @target.respond_to?(:operands)
19
+ stack << [ @target, @target.operands ]
20
+ end
21
+ end
22
+ end
23
+
24
+ true
25
+ end
26
+
27
+ def failure_message
28
+ <<-OUTPUT
29
+ expected: #{@expected.inspect}
30
+ got: #{@actual.inspect}
31
+ OUTPUT
32
+ end
33
+ end
34
+
35
+ def have_operands_with_valid_parent
36
+ HaveValidParent.new
37
+ end
38
+ end
39
+
40
+ shared_examples 'DataMapper::Query::Conditions::AbstractOperation' do
41
+ before :all do
42
+ module ::Blog
43
+ class Article
44
+ include DataMapper::Resource
45
+
46
+ property :id, Serial
47
+ property :title, String, :required => true
48
+ end
49
+ end
50
+ DataMapper.finalize
51
+
52
+ @model = Blog::Article
53
+ end
54
+
55
+ before do
56
+ class ::OtherOperation < DataMapper::Query::Conditions::AbstractOperation
57
+ slug :other
58
+ end
59
+ end
60
+
61
+ before do
62
+ @comparison = DataMapper::Query::Conditions::Comparison.new(:eql, @model.properties[:title], 'A title')
63
+ @and_operation = DataMapper::Query::Conditions::Operation.new(:and)
64
+ @or_operation = DataMapper::Query::Conditions::Operation.new(:or)
65
+ @not_operation = DataMapper::Query::Conditions::Operation.new(:not)
66
+ @null_operation = DataMapper::Query::Conditions::Operation.new(:null)
67
+ @other = OtherOperation.new
68
+ end
69
+
70
+ it { expect(@operation.class).to respond_to(:new) }
71
+
72
+ describe '.new' do
73
+ describe 'with no arguments' do
74
+ subject { @operation.class.new }
75
+
76
+ it { is_expected.to be_kind_of(@operation.class) }
77
+ end
78
+
79
+ describe 'with arguments' do
80
+ subject { @operation.class.new(@comparison) }
81
+
82
+ it { is_expected.to be_kind_of(@operation.class) }
83
+ end
84
+ end
85
+
86
+ it { expect(@operation.class).to respond_to(:slug) }
87
+
88
+ describe '.slug' do
89
+ describe 'with no arguments' do
90
+ subject { @operation.class.slug }
91
+
92
+ it { is_expected.to eq @slug }
93
+ end
94
+
95
+ describe 'with an argument' do
96
+ subject { @operation.class.slug(:other) }
97
+
98
+ it { is_expected.to eq :other }
99
+
100
+ # reset the AndOperation slug
101
+ after { @operation.class.slug(@slug) }
102
+ end
103
+ end
104
+
105
+ it { is_expected.to respond_to(:==) }
106
+
107
+ describe '#==' do
108
+ describe 'when the other AbstractOperation is equal' do
109
+ # artificially modify the object so #== will throw an
110
+ # exception if the equal? branch is not followed when heckling
111
+ before { @operation.singleton_class.send(:undef_method, :slug) }
112
+
113
+ subject { @operation == @operation }
114
+
115
+ it { is_expected.to be(true) }
116
+ end
117
+
118
+ describe 'when the other AbstractOperation is the same class' do
119
+ subject { @operation == DataMapper::Query::Conditions::Operation.new(@slug) }
120
+
121
+ it { is_expected.to be(true) }
122
+ end
123
+
124
+ describe 'when the other AbstractOperation is a different class, with the same slug' do
125
+ before { @other.class.slug(@slug) }
126
+
127
+ subject { @operation == @other }
128
+
129
+ it { is_expected.to be(false) }
130
+
131
+ # reset the OtherOperation slug
132
+ after { @other.class.slug(:other) }
133
+ end
134
+
135
+ describe 'when the other AbstractOperation is the same class, with different operands' do
136
+ subject { @operation == DataMapper::Query::Conditions::Operation.new(@slug, @comparison) }
137
+
138
+ it { is_expected.to be(false) }
139
+ end
140
+ end
141
+
142
+ it { is_expected.to respond_to(:<<) }
143
+
144
+ describe '#<<' do
145
+ describe 'with a NullOperation' do
146
+ subject { @operation << @null_operation }
147
+
148
+ it { is_expected.to equal(@operation) }
149
+
150
+ it 'merges the operand' do
151
+ expect(subject.to_a).to eq [ @null_operation.class.new ]
152
+ end
153
+
154
+ it { is_expected.to have_operands_with_valid_parent }
155
+ end
156
+
157
+ describe 'with a duplicate operand' do
158
+ before { @operation << @comparison.dup }
159
+
160
+ subject { @operation << @comparison.dup }
161
+
162
+ it { is_expected.to equal(@operation) }
163
+
164
+ it 'has unique operands' do
165
+ expect(subject.to_a).to eq [ @comparison ]
166
+ end
167
+
168
+ it { is_expected.to have_operands_with_valid_parent }
169
+ end
170
+
171
+ describe 'with an invalid operand' do
172
+ subject { @operation << '' }
173
+
174
+ it { expect { method(:subject) }.to raise_error(ArgumentError, '+operand+ should be DataMapper::Query::Conditions::AbstractOperation or DataMapper::Query::Conditions::AbstractComparison or Array, but was String') }
175
+ end
176
+ end
177
+
178
+ it { is_expected.to respond_to(:children) }
179
+
180
+ describe '#children' do
181
+ subject { @operation.children }
182
+
183
+ it { is_expected.to be_kind_of(Set) }
184
+
185
+ it { is_expected.to be_empty }
186
+
187
+ it { is_expected.to equal(@operation.operands) }
188
+ end
189
+
190
+ it { is_expected.to respond_to(:clear) }
191
+
192
+ describe '#clear' do
193
+ before do
194
+ @operation << @other
195
+ expect(@operation).not_to be_empty
196
+ end
197
+
198
+ subject { @operation.clear }
199
+
200
+ it { is_expected.to equal(@operation) }
201
+
202
+ it 'clears the operands' do
203
+ expect(subject).to be_empty
204
+ end
205
+ end
206
+
207
+ [ :difference, :- ].each do |method|
208
+ it { is_expected.to respond_to(method) }
209
+
210
+ describe "##{method}" do
211
+ subject { @operation.send(method, @comparison) }
212
+
213
+ it { is_expected.to eql(@not_operation.class.new(@comparison)) }
214
+ end
215
+ end
216
+
217
+ it { is_expected.to respond_to(:dup) }
218
+
219
+ describe '#dup' do
220
+ subject { @operation.dup }
221
+
222
+ it { is_expected.not_to equal(@operation) }
223
+
224
+ it { expect(subject.to_a).to eq @operation.to_a }
225
+ end
226
+
227
+ it { is_expected.to respond_to(:each) }
228
+
229
+ describe '#each' do
230
+ before do
231
+ @yield = []
232
+ @operation << @other
233
+ end
234
+
235
+ subject { @operation.each { |operand| @yield << operand } }
236
+
237
+ it { is_expected.to equal(@operation) }
238
+
239
+ it 'yields to every operand' do
240
+ subject
241
+ expect(@yield).to eq [ @other ]
242
+ end
243
+ end
244
+
245
+ it { is_expected.to respond_to(:eql?) }
246
+
247
+ describe '#eql?' do
248
+ describe 'when the other AbstractOperation is equal' do
249
+ # artificially modify the object so #eql? will throw an
250
+ # exception if the equal? branch is not followed when heckling
251
+ before { @operation.singleton_class.send(:undef_method, :slug) }
252
+
253
+ subject { @operation.eql?(@operation) }
254
+
255
+ it { is_expected.to be(true) }
256
+ end
257
+
258
+ describe 'when the other AbstractOperation is the same class' do
259
+ subject { @operation.eql?(DataMapper::Query::Conditions::Operation.new(@slug)) }
260
+
261
+ it { is_expected.to be(true) }
262
+ end
263
+
264
+ describe 'when the other AbstractOperation is a different class' do
265
+ subject { @operation.eql?(DataMapper::Query::Conditions::Operation.new(:other)) }
266
+
267
+ it { is_expected.to be(false) }
268
+ end
269
+
270
+ describe 'when the other AbstractOperation is the same class, with different operands' do
271
+ subject { @operation.eql?(DataMapper::Query::Conditions::Operation.new(@slug, @comparison)) }
272
+
273
+ it { is_expected.to be(false) }
274
+ end
275
+
276
+ describe 'when operations contain more than one operand' do
277
+ before do
278
+ @operation << DataMapper::Query::Conditions::Operation.new(:and, @comparison, @other)
279
+ @other = @operation.dup
280
+ end
281
+
282
+ subject { @operation.eql?(@other) }
283
+
284
+ it { is_expected.to be(true) }
285
+ end
286
+ end
287
+
288
+ it { is_expected.to respond_to(:hash) }
289
+
290
+ describe '#hash' do
291
+ describe 'with operands' do
292
+ before do
293
+ @operation << @comparison
294
+ end
295
+
296
+ subject { @operation.hash }
297
+
298
+ it 'matches the same AbstractOperation with the same operands' do
299
+ is_expected.to eq DataMapper::Query::Conditions::Operation.new(@slug, @comparison.dup).hash
300
+ end
301
+
302
+ it 'does not match the same AbstractOperation with different operands' do
303
+ is_expected.not_to eq DataMapper::Query::Conditions::Operation.new(@slug).hash
304
+ end
305
+
306
+ it 'does not match a different AbstractOperation with the same operands' do
307
+ is_expected.not_to eq @other.class.new(@comparison.dup).hash
308
+ end
309
+
310
+ it 'does not match a different AbstractOperation with different operands' do
311
+ is_expected.not_to eq DataMapper::Query::Conditions::Operation.new(:or).hash
312
+ end
313
+ end
314
+ end
315
+
316
+ [ :intersection, :& ].each do |method|
317
+ it { is_expected.to respond_to(method) }
318
+
319
+ describe "##{method}" do
320
+ subject { @operation.send(method, @other) }
321
+
322
+ it { is_expected.to eql(@and_operation) }
323
+ end
324
+ end
325
+
326
+ it { is_expected.to respond_to(:merge) }
327
+
328
+ describe '#merge' do
329
+ describe 'with a NullOperation' do
330
+ subject { @operation.merge([ @null_operation ]) }
331
+
332
+ it { is_expected.to equal(@operation) }
333
+
334
+ it 'merges the operand' do
335
+ expect(subject.to_a).to eq [ @null_operation.class.new ]
336
+ end
337
+
338
+ it { is_expected.to have_operands_with_valid_parent }
339
+ end
340
+
341
+ describe 'with a duplicate operand' do
342
+ before { @operation << @comparison.dup }
343
+
344
+ subject { @operation.merge([ @comparison.dup ]) }
345
+
346
+ it { is_expected.to equal(@operation) }
347
+
348
+ it 'has unique operands' do
349
+ expect(subject.to_a).to eq [ @comparison ]
350
+ end
351
+
352
+ it { is_expected.to have_operands_with_valid_parent }
353
+ end
354
+
355
+ describe 'with an invalid operand' do
356
+ subject { @operation.merge([ '' ]) }
357
+
358
+ it { expect { method(:subject) }.to raise_error(ArgumentError) }
359
+ end
360
+ end
361
+
362
+ it { is_expected.to respond_to(:operands) }
363
+
364
+ describe '#operands' do
365
+ subject { @operation.operands }
366
+
367
+ it { is_expected.to be_kind_of(Set) }
368
+
369
+ it { is_expected.to be_empty }
370
+
371
+ it { is_expected.to equal(@operation.children) }
372
+ end
373
+
374
+ it { is_expected.to respond_to(:parent) }
375
+
376
+ describe '#parent' do
377
+ describe 'when there is no parent' do
378
+ subject { @operation.parent }
379
+
380
+ it { is_expected.to be_nil }
381
+ end
382
+
383
+ describe 'when there is a parent' do
384
+ before { @other << @operation }
385
+
386
+ subject { @operation.parent }
387
+
388
+ it { is_expected.to equal(@other) }
389
+ end
390
+ end
391
+
392
+ it { is_expected.to respond_to(:parent=) }
393
+
394
+ describe '#parent=' do
395
+ subject { @operation.parent = @other }
396
+
397
+ it { is_expected.to equal(@other) }
398
+
399
+ it 'changes the parent' do
400
+ expect { method(:subject) }
401
+ .to change(@operation, :parent)
402
+ .from(nil)
403
+ .to(@other)
404
+ end
405
+ end
406
+
407
+ [ :union, :|, :+ ].each do |method|
408
+ it { is_expected.to respond_to(method) }
409
+
410
+ describe "##{method}" do
411
+ subject { @operation.send(method, @null_operation) }
412
+
413
+ it { is_expected.to eql(@null_operation) }
414
+ end
415
+ end
416
+
417
+ it { is_expected.to respond_to(:valid?) }
418
+
419
+ describe '#valid?' do
420
+ subject { @operation.valid? }
421
+
422
+ describe 'with no operands' do
423
+ it { is_expected.to be(false) }
424
+ end
425
+
426
+ describe 'with an operand that responds to #valid?' do
427
+ describe 'and is valid' do
428
+ before do
429
+ @operation << @comparison
430
+ end
431
+
432
+ it { is_expected.to be(true) }
433
+ end
434
+
435
+ describe 'and is not valid' do
436
+ before do
437
+ @operation << @or_operation.dup
438
+ end
439
+
440
+ it { is_expected.to be(false) }
441
+ end
442
+ end
443
+
444
+ describe 'with an operand that does not respond to #valid?' do
445
+ before do
446
+ @operation << [ 'raw = 1' ]
447
+ end
448
+
449
+ it { is_expected.to be(true) }
450
+ end
451
+ end
452
+ end
453
+
454
+ describe DataMapper::Query::Conditions::Operation do
455
+ it { expect(DataMapper::Query::Conditions::Operation).to respond_to(:new) }
456
+
457
+ describe '.new' do
458
+ {
459
+ :and => DataMapper::Query::Conditions::AndOperation,
460
+ :or => DataMapper::Query::Conditions::OrOperation,
461
+ :not => DataMapper::Query::Conditions::NotOperation,
462
+ :null => DataMapper::Query::Conditions::NullOperation,
463
+ }.each do |slug, klass|
464
+ describe "with a slug #{slug.inspect}" do
465
+ subject { DataMapper::Query::Conditions::Operation.new(slug) }
466
+
467
+ it { is_expected.to be_kind_of(klass) }
468
+
469
+ it { expect(subject).to be_empty }
470
+ end
471
+ end
472
+
473
+ describe 'with an invalid slug' do
474
+ subject { DataMapper::Query::Conditions::Operation.new(:invalid) }
475
+
476
+ it { expect { method(:subject) }.to raise_error(ArgumentError, 'No Operation class for :invalid has been defined') }
477
+ end
478
+
479
+ describe 'with operands' do
480
+ before { @or_operation = DataMapper::Query::Conditions::Operation.new(:or) }
481
+
482
+ subject { DataMapper::Query::Conditions::Operation.new(:and, @or_operation) }
483
+
484
+ it { is_expected.to be_kind_of(DataMapper::Query::Conditions::AndOperation) }
485
+
486
+ it 'sets the operands' do
487
+ expect(subject.to_a).to eq [ @or_operation ]
488
+ end
489
+ end
490
+ end
491
+ end
492
+
493
+ describe DataMapper::Query::Conditions::AndOperation do
494
+ include OperationMatchers
495
+
496
+ it_behaves_like 'DataMapper::Query::Conditions::AbstractOperation'
497
+
498
+ before do
499
+ @operation = @and_operation
500
+ @slug = @operation.slug
501
+ end
502
+
503
+ it { is_expected.to respond_to(:<<) }
504
+
505
+ describe '#<<' do
506
+ [
507
+ DataMapper::Query::Conditions::AndOperation,
508
+ DataMapper::Query::Conditions::OrOperation,
509
+ DataMapper::Query::Conditions::NotOperation,
510
+ ].each do |klass|
511
+ describe "with an #{klass.name.split('::').last}" do
512
+ before do
513
+ @other = klass.new(@comparison)
514
+ end
515
+
516
+ subject { @operation << @other }
517
+
518
+ it { is_expected.to equal(@operation) }
519
+
520
+ if klass == DataMapper::Query::Conditions::AndOperation
521
+ it 'flattens and merges the operand' do
522
+ expect(subject.to_a).to eq @other.operands.to_a
523
+ end
524
+ else
525
+ it 'merges the operand' do
526
+ expect(subject.to_a).to eq [ @other ]
527
+ end
528
+ end
529
+
530
+ it { is_expected.to have_operands_with_valid_parent }
531
+ end
532
+ end
533
+ end
534
+
535
+ it { is_expected.to respond_to(:negated?) }
536
+
537
+ describe '#negated?' do
538
+ describe 'with a negated parent' do
539
+ before do
540
+ @not_operation.class.new(@operation)
541
+ end
542
+
543
+ subject { @operation.negated? }
544
+
545
+ it { is_expected.to be(true) }
546
+ end
547
+
548
+ describe 'with a not negated parent' do
549
+ before do
550
+ @or_operation.class.new(@operation)
551
+ end
552
+
553
+ subject { @operation.negated? }
554
+
555
+ it { is_expected.to be(false) }
556
+ end
557
+
558
+ describe 'after memoizing the negation, and switching parents' do
559
+ before do
560
+ @or_operation.class.new(@operation)
561
+ expect(@operation).not_to be_negated
562
+ @not_operation.class.new(@operation)
563
+ end
564
+
565
+ subject { @operation.negated? }
566
+
567
+ it { is_expected.to be(true) }
568
+ end
569
+ end
570
+
571
+ it { is_expected.to respond_to(:matches?) }
572
+
573
+ describe '#matches?' do
574
+ before do
575
+ @operation << @comparison << @comparison.class.new(@model.properties[:id], 1)
576
+ end
577
+
578
+ supported_by :all do
579
+ describe 'with a matching Hash' do
580
+ subject { @operation.matches?('title' => 'A title', 'id' => 1) }
581
+
582
+ it { is_expected.to be(true) }
583
+ end
584
+
585
+ describe 'with a not matching Hash' do
586
+ subject { @operation.matches?('title' => 'Not matching', 'id' => 1) }
587
+
588
+ it { is_expected.to be(false) }
589
+ end
590
+
591
+ describe 'with a matching Resource' do
592
+ subject { @operation.matches?(@model.new(:title => 'A title', :id => 1)) }
593
+
594
+ it { is_expected.to be(true) }
595
+ end
596
+
597
+ describe 'with a not matching Resource' do
598
+ subject { @operation.matches?(@model.new(:title => 'Not matching', :id => 1)) }
599
+
600
+ it { is_expected.to be(false) }
601
+ end
602
+
603
+ describe 'with a raw condition' do
604
+ before do
605
+ @operation = @operation.class.new([ 'title = ?', 'Another title' ])
606
+ end
607
+
608
+ subject { @operation.matches?('title' => 'A title', 'id' => 1) }
609
+
610
+ it { is_expected.to be(true) }
611
+ end
612
+ end
613
+ end
614
+
615
+ it { is_expected.to respond_to(:minimize) }
616
+
617
+ describe '#minimize' do
618
+ subject { @operation.minimize }
619
+
620
+ describe 'with one empty operand' do
621
+ before do
622
+ @operation << @other
623
+ end
624
+
625
+ it { is_expected.to equal(@operation) }
626
+
627
+ it { expect(subject).to be_empty }
628
+ end
629
+
630
+ describe 'with more than one operation' do
631
+ before do
632
+ @operation.merge([ @comparison, @not_operation.class.new(@comparison) ])
633
+ end
634
+
635
+ it { is_expected.to equal(@operation) }
636
+
637
+ it { expect(subject.to_a).to match [ @comparison, @not_operation.class.new(@comparison) ] }
638
+ end
639
+
640
+ describe 'with one non-empty operand' do
641
+ before do
642
+ @operation << @comparison
643
+ end
644
+
645
+ it { is_expected.to eq @comparison }
646
+ end
647
+
648
+ describe 'with one null operation' do
649
+ before do
650
+ @operation << @null_operation
651
+ end
652
+
653
+ it { is_expected.to eql(@null_operation) }
654
+ end
655
+
656
+ describe 'with one null operation and one non-null operation' do
657
+ before do
658
+ @operation.merge([ @null_operation, @comparison ])
659
+ end
660
+
661
+ it { is_expected.to eql(@comparison) }
662
+ end
663
+ end
664
+
665
+ it { is_expected.to respond_to(:merge) }
666
+
667
+ describe '#merge' do
668
+ [
669
+ DataMapper::Query::Conditions::AndOperation,
670
+ DataMapper::Query::Conditions::OrOperation,
671
+ DataMapper::Query::Conditions::NotOperation,
672
+ ].each do |klass|
673
+ describe "with an #{klass.name.split('::').last}" do
674
+ before do
675
+ @other = klass.new(@comparison)
676
+ end
677
+
678
+ subject { @operation.merge([ @other ]) }
679
+
680
+ it { is_expected.to equal(@operation) }
681
+
682
+ if klass == DataMapper::Query::Conditions::AndOperation
683
+ it 'flattens and merges the operand' do
684
+ expect(subject.to_a).to eq @other.operands.to_a
685
+ end
686
+ else
687
+ it 'merges the operand' do
688
+ expect(subject.to_a).to eq [ @other ]
689
+ end
690
+ end
691
+
692
+ it { is_expected.to have_operands_with_valid_parent }
693
+ end
694
+ end
695
+ end
696
+
697
+ it { is_expected.to respond_to(:to_s) }
698
+
699
+ describe '#to_s' do
700
+ describe 'with no operands' do
701
+ subject { @operation.to_s }
702
+
703
+ it { is_expected.to eql('') }
704
+ end
705
+
706
+ describe 'with operands' do
707
+ before do
708
+ @not_operation << @comparison.dup
709
+ @operation << @comparison << @not_operation
710
+ end
711
+
712
+ subject { @operation.to_s }
713
+
714
+ it { is_expected.to eql('(NOT(title = "A title") AND title = "A title")') }
715
+ end
716
+ end
717
+
718
+ it { is_expected.to respond_to(:valid?) }
719
+
720
+ describe '#valid?' do
721
+ describe 'with one valid operand, and one invalid operand' do
722
+ before do
723
+ @operation << @comparison
724
+ @operation << DataMapper::Query::Conditions::Comparison.new(:in, @model.properties[:id], [])
725
+ end
726
+
727
+ subject { @operation.valid? }
728
+
729
+ it { is_expected.to be(false) }
730
+ end
731
+
732
+ describe 'with one invalid operand' do
733
+ before do
734
+ @operation << DataMapper::Query::Conditions::Comparison.new(:in, @model.properties[:id], [])
735
+ end
736
+
737
+ subject { @operation.valid? }
738
+
739
+ it { is_expected.to be(false) }
740
+ end
741
+ end
742
+ end
743
+
744
+ describe DataMapper::Query::Conditions::OrOperation do
745
+ include OperationMatchers
746
+
747
+ it_behaves_like 'DataMapper::Query::Conditions::AbstractOperation'
748
+
749
+ before do
750
+ @operation = @or_operation
751
+ @slug = @operation.slug
752
+ end
753
+
754
+ it { is_expected.to respond_to(:<<) }
755
+
756
+ describe '#<<' do
757
+ [
758
+ DataMapper::Query::Conditions::AndOperation,
759
+ DataMapper::Query::Conditions::OrOperation,
760
+ DataMapper::Query::Conditions::NotOperation,
761
+ ].each do |klass|
762
+ describe "with an #{klass.name.split('::').last}" do
763
+ before do
764
+ @other = klass.new(@comparison)
765
+ end
766
+
767
+ subject { @operation << @other }
768
+
769
+ it { is_expected.to equal(@operation) }
770
+
771
+ if klass == DataMapper::Query::Conditions::OrOperation
772
+ it 'flattens and merges the operand' do
773
+ expect(subject.to_a).to eq @other.operands.to_a
774
+ end
775
+ else
776
+ it 'merges the operand' do
777
+ expect(subject.to_a).to eq [ @other ]
778
+ end
779
+ end
780
+
781
+ it { is_expected.to have_operands_with_valid_parent }
782
+ end
783
+ end
784
+ end
785
+
786
+ it { is_expected.to respond_to(:negated?) }
787
+
788
+ describe '#negated?' do
789
+ describe 'with a negated parent' do
790
+ before do
791
+ @not_operation.class.new(@operation)
792
+ end
793
+
794
+ subject { @operation.negated? }
795
+
796
+ it { is_expected.to be(true) }
797
+ end
798
+
799
+ describe 'with a not negated parent' do
800
+ before do
801
+ @and_operation.class.new(@operation)
802
+ end
803
+
804
+ subject { @operation.negated? }
805
+
806
+ it { is_expected.to be(false) }
807
+ end
808
+
809
+ describe 'after memoizing the negation, and switching parents' do
810
+ before do
811
+ @or_operation.class.new(@operation)
812
+ expect(@operation).not_to be_negated
813
+ @not_operation.class.new(@operation)
814
+ end
815
+
816
+ subject { @operation.negated? }
817
+
818
+ it { is_expected.to be(true) }
819
+ end
820
+ end
821
+
822
+ it { is_expected.to respond_to(:matches?) }
823
+
824
+ describe '#matches?' do
825
+ before do
826
+ @operation << @comparison << @comparison.class.new(@model.properties[:id], 1)
827
+ end
828
+
829
+ supported_by :all do
830
+ describe 'with a matching Hash' do
831
+ subject { @operation.matches?('title' => 'A title', 'id' => 2) }
832
+
833
+ it { is_expected.to be(true) }
834
+ end
835
+
836
+ describe 'with a not matching Hash' do
837
+ subject { @operation.matches?('title' => 'Not matching', 'id' => 2) }
838
+
839
+ it { is_expected.to be(false) }
840
+ end
841
+
842
+ describe 'with a matching Resource' do
843
+ subject { @operation.matches?(@model.new(:title => 'A title', :id => 2)) }
844
+
845
+ it { is_expected.to be(true) }
846
+ end
847
+
848
+ describe 'with a not matching Resource' do
849
+ subject { @operation.matches?(@model.new(:title => 'Not matching', :id => 2)) }
850
+
851
+ it { is_expected.to be(false) }
852
+ end
853
+
854
+ describe 'with a raw condition' do
855
+ before do
856
+ @operation = @operation.class.new([ 'title = ?', 'Another title' ])
857
+ end
858
+
859
+ subject { @operation.matches?('title' => 'A title', 'id' => 2) }
860
+
861
+ it { is_expected.to be(true) }
862
+ end
863
+ end
864
+ end
865
+
866
+ it { is_expected.to respond_to(:minimize) }
867
+
868
+ describe '#minimize' do
869
+ subject { @operation.minimize }
870
+
871
+ describe 'with one empty operand' do
872
+ before do
873
+ @operation << @other
874
+ end
875
+
876
+ it { is_expected.to equal(@operation) }
877
+
878
+ it { expect(subject).to be_empty }
879
+ end
880
+
881
+ describe 'with more than one operation' do
882
+ before do
883
+ @operation.merge([ @comparison, @not_operation.class.new(@comparison) ])
884
+ end
885
+
886
+ it { is_expected.to equal(@operation) }
887
+
888
+ it { expect(subject.to_a).to match [ @comparison, @not_operation.class.new(@comparison) ] }
889
+ end
890
+
891
+ describe 'with one non-empty operand' do
892
+ before do
893
+ @operation << @comparison
894
+ end
895
+
896
+ it { is_expected.to eq @comparison }
897
+ end
898
+
899
+ describe 'with one null operation' do
900
+ before do
901
+ @operation << @null_operation
902
+ end
903
+
904
+ it { is_expected.to eql(@null_operation) }
905
+ end
906
+ end
907
+
908
+ it { is_expected.to respond_to(:merge) }
909
+
910
+ describe '#merge' do
911
+ [
912
+ DataMapper::Query::Conditions::AndOperation,
913
+ DataMapper::Query::Conditions::OrOperation,
914
+ DataMapper::Query::Conditions::NotOperation,
915
+ ].each do |klass|
916
+ describe "with an #{klass.name.split('::').last}" do
917
+ before do
918
+ @other = klass.new(@comparison)
919
+ end
920
+
921
+ subject { @operation.merge([ @other ]) }
922
+
923
+ it { is_expected.to equal(@operation) }
924
+
925
+ if klass == DataMapper::Query::Conditions::OrOperation
926
+ it 'flattens and merges the operand' do
927
+ expect(subject.to_a).to eq @other.operands.to_a
928
+ end
929
+ else
930
+ it 'merges the operand' do
931
+ expect(subject.to_a).to eq [ @other ]
932
+ end
933
+ end
934
+
935
+ it { is_expected.to have_operands_with_valid_parent }
936
+ end
937
+ end
938
+ end
939
+
940
+ it { is_expected.to respond_to(:valid?) }
941
+
942
+ describe '#valid?' do
943
+ describe 'with one valid operand, and one invalid operand' do
944
+ before do
945
+ @operation << @comparison
946
+ @operation << DataMapper::Query::Conditions::Comparison.new(:in, @model.properties[:id], [])
947
+ end
948
+
949
+ subject { @operation.valid? }
950
+
951
+ it { is_expected.to be(true) }
952
+ end
953
+
954
+ describe 'with one invalid operand' do
955
+ before do
956
+ @operation << DataMapper::Query::Conditions::Comparison.new(:in, @model.properties[:id], [])
957
+ end
958
+
959
+ subject { @operation.valid? }
960
+
961
+ it { is_expected.to be(false) }
962
+ end
963
+ end
964
+ end
965
+
966
+ describe DataMapper::Query::Conditions::NotOperation do
967
+ include OperationMatchers
968
+
969
+ it_behaves_like 'DataMapper::Query::Conditions::AbstractOperation'
970
+
971
+ before do
972
+ @operation = @not_operation
973
+ @slug = @operation.slug
974
+ end
975
+
976
+ it { is_expected.to respond_to(:<<) }
977
+
978
+ describe '#<<' do
979
+ [
980
+ DataMapper::Query::Conditions::AndOperation,
981
+ DataMapper::Query::Conditions::OrOperation,
982
+ DataMapper::Query::Conditions::NotOperation,
983
+ ].each do |klass|
984
+ describe "with an #{klass.name.split('::').last}" do
985
+ before do
986
+ @other = klass.new(@comparison)
987
+ end
988
+
989
+ subject { @operation << @other }
990
+
991
+ it { is_expected.to equal(@operation) }
992
+
993
+ it 'merges the operand' do
994
+ expect(subject.to_a).to eq [ @other ]
995
+ end
996
+
997
+ it { is_expected.to have_operands_with_valid_parent }
998
+ end
999
+ end
1000
+
1001
+ describe 'with more than one operand' do
1002
+ subject { @operation << @comparison << @other }
1003
+
1004
+ it { expect { method(:subject) }.to raise_error(ArgumentError) }
1005
+ end
1006
+
1007
+ describe 'with self as an operand' do
1008
+ subject { @operation << @operation }
1009
+
1010
+ it { expect { method(:subject) }.to raise_error(ArgumentError, 'cannot append operand to itself') }
1011
+ end
1012
+ end
1013
+
1014
+ it { is_expected.to respond_to(:negated?) }
1015
+
1016
+ describe '#negated?' do
1017
+ describe 'with a negated parent' do
1018
+ before do
1019
+ @not_operation.class.new(@operation)
1020
+ end
1021
+
1022
+ subject { @operation.negated? }
1023
+
1024
+ it { is_expected.to be(false) }
1025
+ end
1026
+
1027
+ describe 'with a not negated parent' do
1028
+ before do
1029
+ @or_operation.class.new(@operation)
1030
+ end
1031
+
1032
+ subject { @operation.negated? }
1033
+
1034
+ it { is_expected.to be(true) }
1035
+ end
1036
+
1037
+ describe 'after memoizing the negation, and switching parents' do
1038
+ before do
1039
+ @or_operation.class.new(@operation)
1040
+ expect(@operation).to be_negated
1041
+ @not_operation.class.new(@operation)
1042
+ end
1043
+
1044
+ subject { @operation.negated? }
1045
+
1046
+ it { is_expected.to be(false) }
1047
+ end
1048
+ end
1049
+
1050
+ it { is_expected.to respond_to(:matches?) }
1051
+
1052
+ describe '#matches?' do
1053
+ before do
1054
+ @operation << @comparison.class.new(@model.properties[:id], 1)
1055
+ end
1056
+
1057
+ supported_by :all do
1058
+ describe 'with a matching Hash' do
1059
+ subject { @operation.matches?('id' => 2) }
1060
+
1061
+ it { is_expected.to be(true) }
1062
+ end
1063
+
1064
+ describe 'with a not matching Hash' do
1065
+ subject { @operation.matches?('id' => 1) }
1066
+
1067
+ it { is_expected.to be(false) }
1068
+ end
1069
+
1070
+ describe 'with a matching Resource' do
1071
+ subject { @operation.matches?(@model.new(:id => 2)) }
1072
+
1073
+ it { is_expected.to be(true) }
1074
+ end
1075
+
1076
+ describe 'with a not matching Hash' do
1077
+ subject { @operation.matches?(@model.new(:id => 1)) }
1078
+
1079
+ it { is_expected.to be(false) }
1080
+ end
1081
+
1082
+ describe 'with a raw condition' do
1083
+ before do
1084
+ @operation = @operation.class.new([ 'title = ?', 'Another title' ])
1085
+ end
1086
+
1087
+ subject { @operation.matches?('id' => 2) }
1088
+
1089
+ it { is_expected.to be(true) }
1090
+ end
1091
+ end
1092
+ end
1093
+
1094
+ it { is_expected.to respond_to(:merge) }
1095
+
1096
+ describe '#merge' do
1097
+ [
1098
+ DataMapper::Query::Conditions::AndOperation,
1099
+ DataMapper::Query::Conditions::OrOperation,
1100
+ DataMapper::Query::Conditions::NotOperation,
1101
+ ].each do |klass|
1102
+ describe "with an #{klass.name.split('::').last}" do
1103
+ before do
1104
+ @other = klass.new(@comparison)
1105
+ end
1106
+
1107
+ subject { @operation.merge([ @other ]) }
1108
+
1109
+ it { is_expected.to equal(@operation) }
1110
+
1111
+ it 'merges the operand' do
1112
+ expect(subject.to_a).to eq [ @other ]
1113
+ end
1114
+
1115
+ it { is_expected.to have_operands_with_valid_parent }
1116
+ end
1117
+ end
1118
+
1119
+ describe 'with more than one operand' do
1120
+ subject { @operation.merge([ @comparison, @other ]) }
1121
+
1122
+ it { expect{ method(:subject) }.to raise_error(ArgumentError) }
1123
+ end
1124
+ end
1125
+
1126
+ it { is_expected.to respond_to(:minimize) }
1127
+
1128
+ describe '#minimize' do
1129
+ subject { @operation.minimize }
1130
+
1131
+ describe 'when no operand' do
1132
+ it { is_expected.to equal(@operation) }
1133
+ end
1134
+
1135
+ describe 'when operand is a NotOperation' do
1136
+ before do
1137
+ @operation << @not_operation.class.new(@comparison)
1138
+ end
1139
+
1140
+ it 'removes the double negative' do
1141
+ is_expected.to eql(@comparison)
1142
+ end
1143
+ end
1144
+
1145
+ describe 'when operand is not a NotOperation' do
1146
+ before do
1147
+ @operation << @comparison
1148
+ end
1149
+
1150
+ it { is_expected.to equal(@operation) }
1151
+
1152
+ it { expect(subject.to_a).to eq [ @comparison ] }
1153
+ end
1154
+
1155
+ describe 'when operand is an empty operation' do
1156
+ before do
1157
+ @operation << @and_operation
1158
+ end
1159
+
1160
+ it { is_expected.to equal(@operation) }
1161
+
1162
+ it { expect(subject).to be_empty }
1163
+ end
1164
+
1165
+ describe 'when operand is an operation containing a Comparison' do
1166
+ before do
1167
+ @operation << @and_operation.class.new(@comparison)
1168
+ end
1169
+
1170
+ it { is_expected.to equal(@operation) }
1171
+
1172
+ it { expect(subject.to_a).to eq [ @comparison ] }
1173
+ end
1174
+ end
1175
+
1176
+ it { is_expected.to respond_to(:to_s) }
1177
+
1178
+ describe '#to_s' do
1179
+ describe 'with no operands' do
1180
+ subject { @operation.to_s }
1181
+
1182
+ it { is_expected.to eql('') }
1183
+ end
1184
+
1185
+ describe 'with operands' do
1186
+ before do
1187
+ @operation << @comparison
1188
+ end
1189
+
1190
+ subject { @operation.to_s }
1191
+
1192
+ it { is_expected.to eql('NOT(title = "A title")') }
1193
+ end
1194
+ end
1195
+
1196
+ it { is_expected.to respond_to(:valid?) }
1197
+
1198
+ describe '#valid?' do
1199
+ describe 'with one invalid operand' do
1200
+ before do
1201
+ @operation << @not_operation.class.new(
1202
+ DataMapper::Query::Conditions::Comparison.new(:eql, @model.properties[:id], nil)
1203
+ )
1204
+ end
1205
+
1206
+ subject { @operation.valid? }
1207
+
1208
+ it { is_expected.to be(false) }
1209
+ end
1210
+ end
1211
+ end
1212
+
1213
+ describe DataMapper::Query::Conditions::NullOperation do
1214
+ include OperationMatchers
1215
+
1216
+ before :all do
1217
+ module ::Blog
1218
+ class Article
1219
+ include DataMapper::Resource
1220
+
1221
+ property :id, Serial
1222
+ property :title, String, :required => true
1223
+ end
1224
+ end
1225
+
1226
+ @model = Blog::Article
1227
+ end
1228
+
1229
+ before do
1230
+ @null_operation = DataMapper::Query::Conditions::Operation.new(:null)
1231
+ @operation = @null_operation
1232
+ @slug = @operation.slug
1233
+ end
1234
+
1235
+ it { is_expected.to respond_to(:slug) }
1236
+
1237
+ describe '#slug' do
1238
+ subject { @operation.slug }
1239
+
1240
+ it { is_expected.to eq :null }
1241
+ end
1242
+
1243
+ it { is_expected.to respond_to(:matches?) }
1244
+
1245
+ describe '#matches?' do
1246
+ describe 'with a Hash' do
1247
+ subject { @operation.matches?({}) }
1248
+
1249
+ it { is_expected.to be(true) }
1250
+ end
1251
+
1252
+ describe 'with a Resource' do
1253
+ subject { @operation.matches?(Blog::Article.new) }
1254
+
1255
+ it { is_expected.to be(true) }
1256
+ end
1257
+
1258
+ describe 'with any other Object' do
1259
+ subject { @operation.matches?(Object.new) }
1260
+
1261
+ it { is_expected.to be(false) }
1262
+ end
1263
+ end
1264
+
1265
+ it { is_expected.to respond_to(:minimize) }
1266
+
1267
+ describe '#minimize' do
1268
+ subject { @operation.minimize }
1269
+
1270
+ it { is_expected.to equal(@operation) }
1271
+ end
1272
+
1273
+ it { is_expected.to respond_to(:valid?) }
1274
+
1275
+ describe '#valid?' do
1276
+ subject { @operation.valid? }
1277
+
1278
+ it { is_expected.to be(true) }
1279
+ end
1280
+
1281
+ it { is_expected.to respond_to(:nil?) }
1282
+
1283
+ describe '#nil?' do
1284
+ subject { @operation.nil? }
1285
+
1286
+ it { is_expected.to be(true) }
1287
+ end
1288
+
1289
+ it { is_expected.to respond_to(:inspect) }
1290
+
1291
+ describe '#inspect' do
1292
+ subject { @operation.inspect }
1293
+
1294
+ it { is_expected.to eq 'nil' }
1295
+ end
1296
+ end