clearly-query 0.3.1.pre

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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +8 -0
  3. data/.gitignore +42 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +10 -0
  6. data/CHANGELOG.md +43 -0
  7. data/Gemfile +7 -0
  8. data/Guardfile +19 -0
  9. data/LICENSE +22 -0
  10. data/README.md +102 -0
  11. data/Rakefile +7 -0
  12. data/SPEC.md +101 -0
  13. data/bin/guard +16 -0
  14. data/bin/rake +16 -0
  15. data/bin/rspec +16 -0
  16. data/bin/yard +16 -0
  17. data/clearly-query.gemspec +33 -0
  18. data/lib/clearly/query.rb +22 -0
  19. data/lib/clearly/query/cleaner.rb +63 -0
  20. data/lib/clearly/query/compose/comparison.rb +102 -0
  21. data/lib/clearly/query/compose/conditions.rb +215 -0
  22. data/lib/clearly/query/compose/core.rb +75 -0
  23. data/lib/clearly/query/compose/custom.rb +268 -0
  24. data/lib/clearly/query/compose/range.rb +114 -0
  25. data/lib/clearly/query/compose/special.rb +24 -0
  26. data/lib/clearly/query/compose/subset.rb +115 -0
  27. data/lib/clearly/query/composer.rb +269 -0
  28. data/lib/clearly/query/definition.rb +165 -0
  29. data/lib/clearly/query/errors.rb +27 -0
  30. data/lib/clearly/query/graph.rb +63 -0
  31. data/lib/clearly/query/helper.rb +50 -0
  32. data/lib/clearly/query/validate.rb +296 -0
  33. data/lib/clearly/query/version.rb +8 -0
  34. data/spec/lib/clearly/query/cleaner_spec.rb +42 -0
  35. data/spec/lib/clearly/query/compose/custom_spec.rb +77 -0
  36. data/spec/lib/clearly/query/composer_query_spec.rb +50 -0
  37. data/spec/lib/clearly/query/composer_spec.rb +422 -0
  38. data/spec/lib/clearly/query/definition_spec.rb +23 -0
  39. data/spec/lib/clearly/query/graph_spec.rb +81 -0
  40. data/spec/lib/clearly/query/helper_spec.rb +17 -0
  41. data/spec/lib/clearly/query/version_spec.rb +7 -0
  42. data/spec/spec_helper.rb +89 -0
  43. data/spec/support/db/migrate/001_db_create.rb +62 -0
  44. data/spec/support/models/customer.rb +63 -0
  45. data/spec/support/models/order.rb +66 -0
  46. data/spec/support/models/part.rb +63 -0
  47. data/spec/support/models/product.rb +67 -0
  48. data/spec/support/shared_setup.rb +13 -0
  49. data/tmp/.gitkeep +0 -0
  50. metadata +263 -0
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clearly::Query::Cleaner do
4
+ include_context 'shared_setup'
5
+
6
+ it 'can be instantiated' do
7
+ Clearly::Query::Cleaner.new
8
+ end
9
+
10
+ context 'produces the expected result when cleaning a hash that' do
11
+ it 'has simple values' do
12
+ dirty = {'hello' => ['testing', 'testing'], 'again' => {'something' => :small_one}}
13
+ clean = {hello: ['testing', 'testing'], again: {something: :small_one}}
14
+
15
+ expect(cleaner.do(dirty)).to eq(clean)
16
+ end
17
+
18
+ it 'has complex values' do
19
+ current_time = Time.zone.now
20
+ dirty = {current_time => [Range.new(2, 5), 123456], 'again' => {'something' => :small_one}}
21
+ clean = {
22
+ current_time.to_s.underscore.to_sym => [2..5, 123456],
23
+ again: {something: :small_one}
24
+ }
25
+
26
+ expect(cleaner.do(dirty)).to eq(clean)
27
+ end
28
+
29
+ it 'has nested arrays and hashes' do
30
+ dirty = {
31
+ what_is_this: [{test: Range.new(2, 5)}, [['more-more-more'], 123456]],
32
+ 'again' => {'something' => :small_one}
33
+ }
34
+ clean = {
35
+ what_is_this: [{test: 2..5}, [['more-more-more'], 123456]],
36
+ again: {something: :small_one}
37
+ }
38
+
39
+ expect(cleaner.do(dirty)).to eq(clean)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clearly::Query::Compose::Custom do
4
+ include_context 'shared_setup'
5
+ include Clearly::Query::Compose::Core
6
+
7
+ it 'can be instantiated' do
8
+ Clearly::Query::Compose::Custom.new
9
+ end
10
+
11
+ it 'build expected sql' do
12
+
13
+
14
+ custom = Clearly::Query::Compose::Custom.new
15
+
16
+ table = product_def.table
17
+ column_name = :name
18
+ allowed = product_def.all_fields
19
+ value = 'test'
20
+ values = [value]
21
+ value_bool = true
22
+ value_range = {interval: '(test1,test2]'}
23
+
24
+ combined = self.send(
25
+ :compose_and,
26
+ custom.compose_eq(table, column_name, allowed, value),
27
+ custom.compose_not_eq(table, column_name, allowed, value),
28
+ custom.compose_lt(table, column_name, allowed, value),
29
+ custom.compose_not_lt(table, column_name, allowed, value),
30
+ custom.compose_gt(table, column_name, allowed, value),
31
+ custom.compose_not_gt(table, column_name, allowed, value),
32
+ custom.compose_lteq(table, column_name, allowed, value),
33
+ custom.compose_not_lteq(table, column_name, allowed, value),
34
+ custom.compose_gteq(table, column_name, allowed, value),
35
+ custom.compose_not_gteq(table, column_name, allowed, value),
36
+ custom.compose_contains(table, column_name, allowed, value),
37
+ custom.compose_not_contains(table, column_name, allowed, value),
38
+ custom.compose_starts_with(table, column_name, allowed, value),
39
+ custom.compose_not_starts_with(table, column_name, allowed, value),
40
+ custom.compose_ends_with(table, column_name, allowed, value),
41
+ custom.compose_not_ends_with(table, column_name, allowed, value),
42
+ custom.compose_in(table, column_name, allowed, values),
43
+ custom.compose_not_in(table, column_name, allowed, values),
44
+ #custom.compose_regex(table, column_name, allowed, value),
45
+ #custom.compose_not_regex(table, column_name, allowed, value),
46
+ custom.compose_null(table, column_name, allowed, value_bool),
47
+ custom.compose_range(table, column_name, allowed, value_range),
48
+ custom.compose_not_range(table, column_name, allowed, value_range),
49
+ )
50
+
51
+ expected_sql = [
52
+ "\"products\".\"name\" = 'test'",
53
+ "\"products\".\"name\" != 'test'",
54
+ "\"products\".\"name\" < 'test'",
55
+ "\"products\".\"name\" >= 'test'",
56
+ "\"products\".\"name\" > 'test'",
57
+ "\"products\".\"name\" <= 'test'",
58
+ "\"products\".\"name\" <= 'test'",
59
+ "\"products\".\"name\" > 'test'",
60
+ "\"products\".\"name\" >= 'test'",
61
+ "\"products\".\"name\" < 'test'",
62
+ "\"products\".\"name\" LIKE '%test%'",
63
+ "\"products\".\"name\" NOT LIKE '%test%'",
64
+ "\"products\".\"name\" LIKE 'test%'",
65
+ "\"products\".\"name\" NOT LIKE 'test%'",
66
+ "\"products\".\"name\" LIKE '%test'",
67
+ "\"products\".\"name\" NOT LIKE '%test'",
68
+ "\"products\".\"name\" IN ('test')",
69
+ "\"products\".\"name\" NOT IN ('test')",
70
+ "\"products\".\"name\" IS NULL",
71
+ "\"products\".\"name\" > 'test1' AND \"products\".\"name\" <= 'test2'",
72
+ "(\"products\".\"name\" <= 'test1' OR \"products\".\"name\" > 'test2')"
73
+ ]
74
+ expect(combined.to_sql).to eq(expected_sql.join(' AND '))
75
+ end
76
+
77
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clearly::Query::Composer do
4
+ include_context 'shared_setup'
5
+ let(:product_attributes) { {name: 'plastic cup',
6
+ code: '000475PC',
7
+ brand: 'Generic',
8
+ introduced_at: '2015-01-01 00:00:00',
9
+ discontinued_at: nil}}
10
+
11
+ it 'finds the only product' do
12
+ product = Product.create!(product_attributes)
13
+ query_hash = cleaner.do({name: {contains: 'cup'}})
14
+ result = composer.query(Product, query_hash)
15
+ expect(result.size).to eq(1)
16
+
17
+ query_ar = Product.where(result[0])
18
+ expect(query_ar.count).to eq(1)
19
+
20
+ result_item = query_ar.to_a[0]
21
+ expect(result_item.name).to eq(product_attributes[:name])
22
+ expect(result_item.code).to eq(product_attributes[:code])
23
+ expect(result_item.brand).to eq(product_attributes[:brand])
24
+ expect(result_item.introduced_at).to eq(product_attributes[:introduced_at])
25
+ expect(result_item.discontinued_at).to eq(product_attributes[:discontinued_at])
26
+ end
27
+
28
+ it 'finds the matching product' do
29
+ (1..10).each do |i|
30
+ attrs = product_attributes.dup
31
+ attrs[:name] = attrs[:name] + i.to_s
32
+ attrs[:code] = attrs[:code] + i.to_s
33
+ Product.create!(attrs)
34
+ end
35
+
36
+ query_hash = cleaner.do({name: {contains: '5'}})
37
+ result = composer.query(Product, query_hash)
38
+ expect(result.size).to eq(1)
39
+
40
+ query_ar = Product.where(result[0])
41
+ expect(query_ar.count).to eq(1)
42
+
43
+ result_item = query_ar.to_a[0]
44
+ expect(result_item.name).to eq(product_attributes[:name] + '5')
45
+ expect(result_item.code).to eq(product_attributes[:code] + '5')
46
+ expect(result_item.brand).to eq(product_attributes[:brand])
47
+ expect(result_item.introduced_at).to eq(product_attributes[:introduced_at])
48
+ expect(result_item.discontinued_at).to eq(product_attributes[:discontinued_at])
49
+ end
50
+ end
@@ -0,0 +1,422 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clearly::Query::Composer do
4
+ include_context 'shared_setup'
5
+
6
+ it 'can be instantiated' do
7
+ Clearly::Query::Composer.new(all_defs)
8
+ end
9
+
10
+ it 'matches test setup' do
11
+ composer_definitions = Clearly::Query::Composer.from_active_record.definitions.reject{ |d| d.model.nil? }.map { |d| d.model.name }
12
+ test_definitions = all_defs.map { |d| d.model.name }
13
+ expect(test_definitions).to eq(composer_definitions)
14
+ end
15
+
16
+ it 'raises error when more than one definition matches' do
17
+ invalid_composer = Clearly::Query::Composer.new([customer_def, customer_def])
18
+ query = cleaner.do({})
19
+ expect {
20
+ invalid_composer.query(Customer, query)
21
+ }.to raise_error(Clearly::Query::QueryArgumentError, "exactly one definition must match, found '2'")
22
+ end
23
+
24
+ it 'finds all active record models' do
25
+ definitions = Clearly::Query::Composer.from_active_record.definitions.reject{ |d| d.model.nil? }.map { |d| d.model.name }
26
+ expect([Customer.name, Order.name, Part.name, Product.name]).to eq(definitions)
27
+ end
28
+
29
+ it 'finds all habtm tables' do
30
+ definitions = Clearly::Query::Composer.from_active_record.definitions.reject{ |d| !d.model.nil? }.map { |d| d.table.name }
31
+ expect(definitions).to eq(['orders_products', 'parts_products'])
32
+ end
33
+
34
+ context 'test query' do
35
+ context 'fails when it' do
36
+ it 'is given an empty query' do
37
+ query = cleaner.do({})
38
+ expect {
39
+ composer.query(Customer, query)
40
+ }.to raise_error(Clearly::Query::QueryArgumentError, "filter hash must have at least 1 entry, got '0'")
41
+ end
42
+
43
+ it 'uses a regex operator using sqlite' do
44
+ expect {
45
+ conditions = composer.query(Product, {name: {regex: 'test'}})
46
+ query = Product.all
47
+ conditions.each { |c| query = query.where(c) }
48
+ expect(query.to_a).to eq([])
49
+ }.to raise_error(NotImplementedError, "~ not implemented for this db")
50
+ end
51
+
52
+ # TODO
53
+ # fail FilterArgumentError.new("'Not' must have a single combiner or field name, got #{filter_hash.size}", {hash: filter_hash}) if filter_hash.size != 1
54
+ #fail FilterArgumentError.new("'Not' must have a single filter, got #{hash.size}.", {hash: filter_hash}) if result.size != 1
55
+
56
+ it 'contains an unrecognised filter' do
57
+ expect {
58
+ composer.query(Customer, {
59
+ or: {
60
+ name: {
61
+ not_a_real_filter: 'Hello'
62
+ }
63
+ }
64
+ })
65
+ }.to raise_error(Clearly::Query::QueryArgumentError, "unrecognised operator 'not_a_real_filter'")
66
+ end
67
+
68
+ it 'has no entry' do
69
+ expect {
70
+ composer.query(Customer, {
71
+ or: {
72
+ name: {
73
+
74
+ }
75
+ }
76
+ })
77
+ }.to raise_error(Clearly::Query::QueryArgumentError, "filter hash must have at least 1 entry, got '0'")
78
+ end
79
+
80
+ it 'has not with no entries' do
81
+ expect {
82
+ composer.query(Customer, {
83
+ not: {
84
+ }
85
+ })
86
+ }.to raise_error(Clearly::Query::QueryArgumentError, "filter hash must have at least 1 entry, got '0'")
87
+ end
88
+
89
+ it 'has or with no entries' do
90
+ expect {
91
+ composer.query(Customer, {
92
+ or: {
93
+ }
94
+ })
95
+ }.to raise_error(Clearly::Query::QueryArgumentError, "filter hash must have at least 1 entry, got '0'")
96
+ end
97
+
98
+ it 'has not with more than one field' do
99
+ expect {
100
+ composer.query(Product, {
101
+ not: {
102
+ name: {
103
+ contains: 'Hello'
104
+ },
105
+ code: {
106
+ contains: 'Hello'
107
+ }
108
+ }
109
+ })
110
+ }.to_not raise_error
111
+ end
112
+
113
+ it 'has not with more than one filter' do
114
+ expect {
115
+ composer.query(Product, {
116
+ not: {
117
+ name: {
118
+ contains: 'Hello',
119
+ eq: 2
120
+ }
121
+ }
122
+ })
123
+ }.to_not raise_error
124
+ end
125
+
126
+ it 'has a combiner that is not recognised with valid filters' do
127
+ expect {
128
+ composer.query(Product, {
129
+ not_a_valid_combiner: {
130
+ name: {
131
+ contains: 'Hello'
132
+ },
133
+ code: {
134
+ contains: 'Hello'
135
+ }
136
+ }
137
+ })
138
+ }.to raise_error(Clearly::Query::QueryArgumentError, "unrecognised operator or field 'not_a_valid_combiner'")
139
+ end
140
+
141
+ it "has a range missing 'from'" do
142
+ expect {
143
+ composer.query(Customer, {
144
+ and: {
145
+ name: {
146
+ range: {
147
+ to: 200
148
+ }
149
+ }
150
+ }
151
+ })
152
+ }.to raise_error(Clearly::Query::QueryArgumentError, "range filter missing 'from'")
153
+ end
154
+
155
+ it "has a range missing 'to'" do
156
+ expect {
157
+ composer.query(Product, {
158
+ and: {
159
+ code: {
160
+ range: {
161
+ from: 200
162
+ }
163
+ }
164
+ }
165
+ })
166
+ }.to raise_error(Clearly::Query::QueryArgumentError, "range filter missing 'to'")
167
+ end
168
+
169
+ it 'has a range with from/to and interval' do
170
+ expect {
171
+ composer.query(Customer, {
172
+ and: {
173
+ name: {
174
+ range: {
175
+ from: 200,
176
+ to: 200,
177
+ interval: '[1,2]'
178
+ }
179
+ }}
180
+ })
181
+ }.to raise_error(Clearly::Query::QueryArgumentError, "range filter must use either ('from' and 'to') or ('interval'), not both")
182
+ end
183
+
184
+ it 'has a range with no recognised properties' do
185
+ expect {
186
+ composer.query(Customer, {
187
+ and: {
188
+ name: {
189
+ range: {
190
+ ignored_in_a_range: '[34,34]'
191
+ }
192
+ }
193
+ }
194
+ })
195
+ }.to raise_error(Clearly::Query::QueryArgumentError, "range filter did not contain ('from' and 'to') or ('interval'), got '{:ignored_in_a_range=>\"[34,34]\"}'")
196
+ end
197
+
198
+ it 'has a property that has no filters' do
199
+ expect {
200
+ composer.query(Customer, {
201
+ or: {
202
+ name: {
203
+ }
204
+ }
205
+ })
206
+ }.to raise_error(Clearly::Query::QueryArgumentError, "filter hash must have at least 1 entry, got '0'")
207
+ end
208
+
209
+ it "occurs with a deformed 'in' filter" do
210
+ filter_params = {'name' => {'in' => [
211
+ {'blah1' => nil, 'blah2' => nil, 'blah3' => nil, 'id' => 508, 'blah4' => true, 'name' => 'blah blah',
212
+ 'blah5' => [397], 'links' => ['blah']},
213
+ {'blah1' => nil, 'blah2' => nil, 'blah3' => nil, 'id' => 400, 'blah4' => true, 'name' => 'blah blah',
214
+ 'blah5' => [397], 'links' => ['blah']}
215
+ ]}}
216
+
217
+ expect {
218
+ query = cleaner.do(filter_params)
219
+ composer.query(Customer, query)
220
+ }.to raise_error(Clearly::Query::QueryArgumentError, 'array values cannot be hashes')
221
+ end
222
+
223
+ it 'occurs for an invalid range filter' do
224
+ filter_params = {"name" => {"inRange" => "(5,6)"}}
225
+ expect {
226
+ query = cleaner.do(filter_params)
227
+ composer.query(Customer, query)
228
+ }.to raise_error(Clearly::Query::QueryArgumentError, "range filter must be {'from': 'value', 'to': 'value'} or {'interval': '(|[.*,.*]|)'} got '(5,6)'")
229
+ end
230
+
231
+
232
+ end
233
+ context 'succeeds when it' do
234
+ it 'is given a valid query without combiners' do
235
+ hash = cleaner.do({name: {contains: 'test'}})
236
+ conditions = composer.query(Customer, hash)
237
+ expect(conditions.size).to eq(1)
238
+
239
+ # sqlite only supports LIKE
240
+ expect(conditions.first.to_sql).to eq("\"customers\".\"name\" LIKE '%test%'")
241
+
242
+ query = Customer.all
243
+ conditions.each { |c| query = query.where(c) }
244
+ expect(query.to_a).to eq([])
245
+ end
246
+
247
+ it 'is given a valid query with or combiner' do
248
+ hash = cleaner.do({or: {name: {contains: 'test'}, code: {eq: 4}}})
249
+ conditions = composer.query(Product, hash)
250
+ expect(conditions.size).to eq(1)
251
+
252
+ expect(conditions.first.to_sql).to eq("(\"products\".\"name\" LIKE '%test%' OR \"products\".\"code\" = '4')")
253
+
254
+ query = Product.all
255
+ conditions.each { |c| query = query.where(c) }
256
+ expect(query.to_a).to eq([])
257
+ end
258
+
259
+ it 'is given a valid query with camel cased keys' do
260
+ hash = cleaner.do({name: {does_not_start_with: 'test'}})
261
+ conditions = composer.query(Customer, hash)
262
+ expect(conditions.size).to eq(1)
263
+
264
+ expect(conditions.first.to_sql).to eq("\"customers\".\"name\" NOT LIKE 'test%'")
265
+
266
+ query = Customer.all
267
+ conditions.each { |c| query = query.where(c) }
268
+ expect(query.to_a).to eq([])
269
+ end
270
+
271
+ it 'is given a valid range query that excludes the start and includes the end' do
272
+ hash = cleaner.do({name: {notInRange: {interval: '(2,5]'}}})
273
+ conditions = composer.query(Customer, hash)
274
+ expect(conditions.size).to eq(1)
275
+
276
+ expect(conditions.first.to_sql).to eq("(\"customers\".\"name\" <= '2' OR \"customers\".\"name\" > '5')")
277
+
278
+ query = Customer.all
279
+ conditions.each { |c| query = query.where(c) }
280
+ expect(query.to_a).to eq([])
281
+ end
282
+
283
+ it 'is given a valid query that uses a table one step away' do
284
+ hash = cleaner.do({and: {name: {contains: 'test'}, 'orders.shipped_at' => {lt: '2015-10-24'}}})
285
+ conditions = composer.query(Customer, hash)
286
+ expect(conditions.size).to eq(1)
287
+
288
+ expected = "\"customers\".\"name\" LIKE '%test%' AND EXISTS (SELECT 1 FROM \"orders\" WHERE \"orders\".\"shipped_at\" < '2015-10-24' AND \"orders\".\"customer_id\" = \"customers\".\"id\")"
289
+ expect(conditions.first.to_sql).to eq(expected)
290
+
291
+ query = Customer.all
292
+ conditions.each { |c| query = query.where(c) }
293
+ expect(query.to_a).to eq([])
294
+ end
295
+
296
+ it 'is given a valid query that uses a table five steps away' do
297
+ # TODO: requires rewriting Composer#parse_filter to use definitions for table.field field names
298
+ # instead of the hash in Definition#parse_table_field
299
+
300
+ hash = cleaner.do({and: {name: {contains: 'test'}, 'customers.name' => {lt: '2015-10-24'}}})
301
+ conditions = composer.query(Part, hash)
302
+ expect(conditions.size).to eq(1)
303
+
304
+ expected = "\"parts\".\"name\" LIKE '%test%' AND EXISTS (SELECT 1 FROM \"customers\" INNER JOIN \"parts_products\" ON \"products\".\"id\" = \"parts_products\".\"product_id\" INNER JOIN \"products\" ON \"products\".\"id\" = \"orders_products\".\"product_id\" INNER JOIN \"orders_products\" ON \"orders\".\"id\" = \"orders_products\".\"order_id\" INNER JOIN \"orders\" ON \"orders\".\"customer_id\" = \"customers\".\"id\" WHERE \"customers\".\"name\" < '2015-10-24' AND \"customers\".\"id\" = \"orders\".\"customer_id\")"
305
+ expect(conditions.first.to_sql).to eq(expected)
306
+
307
+ query = Part.all
308
+ conditions.each { |c| query = query.where(c) }
309
+ expect(query.to_a).to eq([])
310
+ end
311
+
312
+ it 'is given a valid query that uses a custom field mapping' do
313
+ hash = cleaner.do({and: {shipped_at: {lt: '2015-10-24'}, title: {does_not_start_with: 'alice'}}})
314
+ conditions = composer.query(Order, hash)
315
+ expect(conditions.size).to eq(1)
316
+
317
+ expected = "\"orders\".\"shipped_at\" < '2015-10-24' AND (SELECT \"customers\".\"name\" FROM \"customers\" WHERE \"customers\".\"id\" = \"orders\".\"customer_id\") || ' (' || CASE WHEN \"orders\".\"shipped_at\" IS NULL THEN 'not shipped' ELSE \"orders\".\"shipped_at\" END || ')' NOT LIKE 'alice%'"
318
+ expect(conditions.first.to_sql).to eq(expected)
319
+
320
+ query = Order.all
321
+ conditions.each { |c| query = query.where(c) }
322
+ expect(query.to_a).to eq([])
323
+ end
324
+
325
+ it 'is given a valid query that uses all possible comparisons' do
326
+
327
+ operator_value = 'test'
328
+ column = '"products"."name"'
329
+
330
+ not_implemented_sqlite = Clearly::Query::Compose::Conditions::OPERATORS_REGEX
331
+ skip_in_test = []
332
+ operator_hash = {}
333
+ Clearly::Query::Compose::Conditions::OPERATORS
334
+ .reject { |o| not_implemented_sqlite.include?(o) }
335
+ .reject { |o| skip_in_test.include?(o) }
336
+ .each do |o|
337
+ op_value_mod = "#{operator_value}_#{o.to_s}"
338
+ operator_value1 = "#{op_value_mod}_1"
339
+ operator_value2 = "#{op_value_mod}_2"
340
+ operator_hash[o] =
341
+ case o
342
+ when :range, :not_range, :not_in_range
343
+ {from: operator_value1, to: operator_value2}
344
+ when :in_range
345
+ {interval: "(#{operator_value1},#{operator_value2}]"}
346
+ when :in, :not_in, :is_in, :in_not_in
347
+ [op_value_mod]
348
+ when :null
349
+ true
350
+ when :is_null
351
+ false
352
+ else
353
+ op_value_mod
354
+ end
355
+ end
356
+
357
+ hash = cleaner.do({and: {name: operator_hash}})
358
+ conditions = composer.query(Product, hash)
359
+ expect(conditions.size).to eq(1)
360
+
361
+ expected = {
362
+ eq: "#{column} = '#{operator_value}_eq'",
363
+ equal: "#{column} = '#{operator_value}_equal'",
364
+ not_eq: "#{column} != '#{operator_value}_not_eq'",
365
+ not_equal: "#{column} != '#{operator_value}_not_equal'",
366
+ lt: "#{column} < '#{operator_value}_lt'",
367
+ less_than: "#{column} < '#{operator_value}_less_than'",
368
+ not_lt: "#{column} >= '#{operator_value}_not_lt'",
369
+ not_less_than: "#{column} >= '#{operator_value}_not_less_than'",
370
+ gt: "#{column} > '#{operator_value}_gt'",
371
+ greater_than: "#{column} > '#{operator_value}_greater_than'",
372
+ not_gt: "#{column} <= '#{operator_value}_not_gt'",
373
+ not_greater_than: "#{column} <= '#{operator_value}_not_greater_than'",
374
+ lteq: "#{column} <= '#{operator_value}_lteq'",
375
+ less_than_or_equal: "#{column} <= '#{operator_value}_less_than_or_equal'",
376
+ not_lteq: "#{column} > '#{operator_value}_not_lteq'",
377
+ not_less_than_or_equal: "#{column} > '#{operator_value}_not_less_than_or_equal'",
378
+ gteq: "#{column} >= '#{operator_value}_gteq'",
379
+ greater_than_or_equal: "#{column} >= '#{operator_value}_greater_than_or_equal'",
380
+ not_gteq: "#{column} < '#{operator_value}_not_gteq'",
381
+ not_greater_than_or_equal: "#{column} < '#{operator_value}_not_greater_than_or_equal'",
382
+
383
+ range: "#{column} >= '#{operator_value}_range_1' AND #{column} < '#{operator_value}_range_2'",
384
+ in_range: "#{column} > '#{operator_value}_in_range_1' AND #{column} <= '#{operator_value}_in_range_2'",
385
+ not_range: "(#{column} < '#{operator_value}_not_range_1' OR #{column} >= '#{operator_value}_not_range_2')",
386
+ not_in_range: "(#{column} < '#{operator_value}_not_in_range_1' OR #{column} >= '#{operator_value}_not_in_range_2')",
387
+ in: "#{column} IN ('#{operator_value}_in')",
388
+ is_in: "#{column} IN ('#{operator_value}_is_in')",
389
+ not_in: "#{column} NOT IN ('#{operator_value}_not_in')",
390
+ is_not_in: "#{column} NOT IN ('#{operator_value}_is_not_in')",
391
+ contains: "#{column} LIKE '%#{operator_value}\\_contains%'",
392
+ contain: "#{column} LIKE '%#{operator_value}\\_contain%'",
393
+ not_contains: "#{column} NOT LIKE '%#{operator_value}\\_not\\_contains%'",
394
+ not_contain: "#{column} NOT LIKE '%#{operator_value}\\_not\\_contain%'",
395
+ does_not_contain: "#{column} NOT LIKE '%#{operator_value}\\_does\\_not\\_contain%'",
396
+ starts_with: "#{column} LIKE '#{operator_value}\\_starts\\_with%'",
397
+ start_with: "#{column} LIKE '#{operator_value}\\_start\\_with%'",
398
+ not_starts_with: "#{column} NOT LIKE '#{operator_value}\\_not\\_starts\\_with%'",
399
+ not_start_with: "#{column} NOT LIKE '#{operator_value}\\_not\\_start\\_with%'",
400
+ does_not_start_with: "#{column} NOT LIKE '#{operator_value}\\_does\\_not\\_start\\_with%'",
401
+ ends_with: "#{column} LIKE '%#{operator_value}\\_ends\\_with'",
402
+ end_with: "#{column} LIKE '%#{operator_value}\\_end\\_with'",
403
+ not_ends_with: "#{column} NOT LIKE '%#{operator_value}\\_not\\_ends\\_with'",
404
+ not_end_with: "#{column} NOT LIKE '%#{operator_value}\\_not\\_end\\_with'",
405
+ does_not_end_with: "#{column} NOT LIKE '%#{operator_value}\\_does\\_not\\_end\\_with'",
406
+
407
+ null: "#{column} IS NULL",
408
+ is_null: "#{column} IS NOT NULL"
409
+ }
410
+
411
+ expect(conditions.first.to_sql).to eq(expected.values.join(' AND '))
412
+
413
+ query = Product.all
414
+ conditions.each { |c| query = query.where(c) }
415
+ expect(query.to_a).to eq([])
416
+ end
417
+ end
418
+
419
+ end
420
+
421
+
422
+ end