pursuit 0.4.5 → 1.0.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rubygem.yaml +46 -0
  3. data/Gemfile +14 -13
  4. data/Gemfile.lock +14 -13
  5. data/README.md +210 -27
  6. data/bin/console +10 -0
  7. data/lib/pursuit/aggregate_modifier_not_found.rb +20 -0
  8. data/lib/pursuit/aggregate_modifier_required.rb +20 -0
  9. data/lib/pursuit/aggregate_modifiers_not_available.rb +13 -0
  10. data/lib/pursuit/attribute_not_found.rb +20 -0
  11. data/lib/pursuit/constants.rb +1 -1
  12. data/lib/pursuit/error.rb +7 -0
  13. data/lib/pursuit/predicate_parser.rb +181 -0
  14. data/lib/pursuit/predicate_search.rb +83 -0
  15. data/lib/pursuit/predicate_transform.rb +231 -0
  16. data/lib/pursuit/query_error.rb +7 -0
  17. data/lib/pursuit/simple_search.rb +64 -0
  18. data/lib/pursuit/term_parser.rb +44 -0
  19. data/lib/pursuit/term_search.rb +69 -0
  20. data/lib/pursuit/term_transform.rb +35 -0
  21. data/lib/pursuit.rb +18 -4
  22. data/pursuit.gemspec +4 -3
  23. data/spec/internal/app/models/application_record.rb +5 -0
  24. data/spec/internal/app/models/product.rb +25 -9
  25. data/spec/internal/app/models/product_category.rb +23 -1
  26. data/spec/internal/app/models/product_variation.rb +26 -1
  27. data/spec/lib/pursuit/predicate_parser_spec.rb +1604 -0
  28. data/spec/lib/pursuit/predicate_search_spec.rb +80 -0
  29. data/spec/lib/pursuit/predicate_transform_spec.rb +624 -0
  30. data/spec/lib/pursuit/simple_search_spec.rb +59 -0
  31. data/spec/lib/pursuit/term_parser_spec.rb +271 -0
  32. data/spec/lib/pursuit/term_search_spec.rb +71 -0
  33. data/spec/lib/pursuit/term_transform_spec.rb +105 -0
  34. metadata +47 -25
  35. data/.travis.yml +0 -26
  36. data/lib/pursuit/dsl.rb +0 -28
  37. data/lib/pursuit/railtie.rb +0 -13
  38. data/lib/pursuit/search.rb +0 -172
  39. data/lib/pursuit/search_options.rb +0 -86
  40. data/lib/pursuit/search_term_parser.rb +0 -46
  41. data/spec/lib/pursuit/dsl_spec.rb +0 -22
  42. data/spec/lib/pursuit/search_options_spec.rb +0 -146
  43. data/spec/lib/pursuit/search_spec.rb +0 -516
  44. data/spec/lib/pursuit/search_term_parser_spec.rb +0 -34
  45. data/travis/gemfiles/5.2.gemfile +0 -8
  46. data/travis/gemfiles/6.0.gemfile +0 -8
  47. data/travis/gemfiles/6.1.gemfile +0 -8
  48. data/travis/gemfiles/7.0.gemfile +0 -8
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Pursuit::PredicateSearch do
4
+ subject(:predicate_search) do
5
+ described_class.new(
6
+ Product.left_outer_joins(:variations).group(:id),
7
+ permit_aggregate_modifiers: true
8
+ ) do
9
+ permit_attribute :title
10
+ permit_attribute :variation, ProductVariation.arel_table[:id]
11
+ permit_attribute :variation_title, ProductVariation.arel_table[:title]
12
+ end
13
+ end
14
+
15
+ describe '#initialize' do
16
+ it 'is expected to set #relation eq `relation`' do
17
+ expect(predicate_search).to have_attributes(relation: Product.left_outer_joins(:variations).group(:id))
18
+ end
19
+
20
+ it 'is expected to set #permit_aggregate_modifiers eq `permit_aggregate_modifiers`' do
21
+ expect(predicate_search).to have_attributes(permit_aggregate_modifiers: true)
22
+ end
23
+
24
+ it 'is expected to evaluate the passed block' do
25
+ expect(predicate_search.permitted_attributes).to be_present
26
+ end
27
+ end
28
+
29
+ describe '#parser' do
30
+ subject(:parser) { predicate_search.parser }
31
+
32
+ it { is_expected.to be_a(Pursuit::PredicateParser) }
33
+ end
34
+
35
+ describe '#transform' do
36
+ subject(:transform) { predicate_search.transform }
37
+
38
+ it { is_expected.to be_a(Pursuit::PredicateTransform) }
39
+ end
40
+
41
+ describe '#permit_attribute' do
42
+ subject(:permit_attribute) do
43
+ predicate_search.permit_attribute(:variation_currency, ProductVariation.arel_table[:currency])
44
+ end
45
+
46
+ it 'is expected to add the attribute to #permitted_attributes' do
47
+ permit_attribute
48
+ expect(predicate_search.permitted_attributes).to match(
49
+ hash_including(
50
+ variation_currency: ProductVariation.arel_table[:currency]
51
+ )
52
+ )
53
+ end
54
+ end
55
+
56
+ describe '#parse' do
57
+ subject(:parse) { predicate_search.parse('title ~ Shirt & #variation > 0') }
58
+
59
+ it 'is expected to equal a Hash containing the ARel nodes' do
60
+ expect(parse).to eq(
61
+ {
62
+ where: Product.arel_table[:title].matches('%Shirt%'),
63
+ having: ProductVariation.arel_table[:id].count.gt(0)
64
+ }
65
+ )
66
+ end
67
+ end
68
+
69
+ describe '#apply' do
70
+ subject(:apply) { predicate_search.apply('title ~ Shirt') }
71
+
72
+ it 'is expected to equal #relation with predicate clauses applied' do
73
+ expect(apply).to eq(
74
+ Product.left_outer_joins(:variations).group(:id).where(
75
+ Product.arel_table[:title].matches('%Shirt%')
76
+ )
77
+ )
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,624 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Pursuit::PredicateTransform do
4
+ subject(:transform) { described_class.new }
5
+
6
+ describe '#apply' do
7
+ subject(:apply) do
8
+ transform.apply(
9
+ tree,
10
+ permitted_attributes: permitted_attributes,
11
+ permit_aggregate_modifiers: permit_aggregate_modifiers
12
+ )
13
+ end
14
+
15
+ let(:permitted_attributes) do
16
+ {
17
+ title: Product.arel_table[:title],
18
+ created_at: Product.arel_table[:created_at],
19
+ updated_at: Product.arel_table[:updated_at],
20
+ variations: ProductVariation.arel_table[Arel.star],
21
+ variation_title: ProductVariation.arel_table[:title],
22
+ variation_currency: ProductVariation.arel_table[:currency],
23
+ variation_amount: ProductVariation.arel_table[:amount]
24
+ }.with_indifferent_access
25
+ end
26
+
27
+ let(:permit_aggregate_modifiers) { true }
28
+
29
+ context 'when passed a truthy tree' do
30
+ let(:tree) do
31
+ { truthy: 'true' }
32
+ end
33
+
34
+ it { is_expected.to be(true) }
35
+ end
36
+
37
+ context 'when passed a falsey tree' do
38
+ let(:tree) do
39
+ { falsey: 'false' }
40
+ end
41
+
42
+ it { is_expected.to be(false) }
43
+ end
44
+
45
+ context 'when passed an unsigned integer tree' do
46
+ let(:tree) do
47
+ { integer: '3' }
48
+ end
49
+
50
+ it { is_expected.to be_an(Integer) }
51
+
52
+ it 'is expected to equal the correct integer value' do
53
+ expect(apply).to eq(3)
54
+ end
55
+ end
56
+
57
+ context 'when passed a signed integer tree' do
58
+ let(:tree) do
59
+ { integer: '-3' }
60
+ end
61
+
62
+ it { is_expected.to be_an(Integer) }
63
+
64
+ it 'is expected to equal the correct integer value' do
65
+ expect(apply).to eq(-3)
66
+ end
67
+ end
68
+
69
+ context 'when passed an unsigned decimal tree' do
70
+ let(:tree) do
71
+ { decimal: '3.14' }
72
+ end
73
+
74
+ it { is_expected.to be_a(BigDecimal) }
75
+
76
+ it 'is expected to equal the correct decimal value' do
77
+ expect(apply).to eq(3.14)
78
+ end
79
+ end
80
+
81
+ context 'when passed a signed decimal tree' do
82
+ let(:tree) do
83
+ { decimal: '-3.14' }
84
+ end
85
+
86
+ it { is_expected.to be_a(BigDecimal) }
87
+
88
+ it 'is expected to equal the correct decimal value' do
89
+ expect(apply).to eq(-3.14)
90
+ end
91
+ end
92
+
93
+ context 'when passed an empty double quoted string' do
94
+ let(:tree) do
95
+ { string_double_quotes: [] }
96
+ end
97
+
98
+ it 'is expected to equal an empty string' do
99
+ expect(apply).to eq('')
100
+ end
101
+ end
102
+
103
+ context 'when passed a double quoted string' do
104
+ let(:tree) do
105
+ { string_double_quotes: 'Hello \\"World\\"' }
106
+ end
107
+
108
+ it 'is expected to equal the correct string' do
109
+ expect(apply).to eq('Hello "World"')
110
+ end
111
+ end
112
+
113
+ context 'when passed an empty single quoted string' do
114
+ let(:tree) do
115
+ { string_single_quotes: [] }
116
+ end
117
+
118
+ it 'is expected to equal an empty string' do
119
+ expect(apply).to eq('')
120
+ end
121
+ end
122
+
123
+ context 'when passed a single quoted string' do
124
+ let(:tree) do
125
+ { string_single_quotes: "Hello \\'World\\'" }
126
+ end
127
+
128
+ it 'is expected to equal the correct string' do
129
+ expect(apply).to eq("Hello 'World'")
130
+ end
131
+ end
132
+
133
+ context 'when passed an unquoted string' do
134
+ let(:tree) do
135
+ { string_no_quotes: 'hello_world' }
136
+ end
137
+
138
+ it 'is expected to equal the correct string' do
139
+ expect(apply).to eq('hello_world')
140
+ end
141
+ end
142
+
143
+ context 'when passed a comparison with the "=" comparator' do
144
+ let(:tree) do
145
+ {
146
+ attribute: { string_no_quotes: 'title' },
147
+ comparator: '=',
148
+ value: { string_no_quotes: 'Shirt' }
149
+ }
150
+ end
151
+
152
+ it 'is expected to equal the correct ARel node' do
153
+ expect(apply).to eq(Product.arel_table[:title].eq('Shirt'))
154
+ end
155
+
156
+ context 'when the value is blank' do
157
+ let(:tree) do
158
+ {
159
+ attribute: { string_no_quotes: 'title' },
160
+ comparator: '=',
161
+ value: { string_single_quotes: [] }
162
+ }
163
+ end
164
+
165
+ it 'is expected to equal the correct ARel node' do
166
+ expect(apply).to eq(
167
+ Product.arel_table[:title].eq(nil).or(
168
+ Product.arel_table[:title].matches_regexp('^\s*$')
169
+ )
170
+ )
171
+ end
172
+ end
173
+ end
174
+
175
+ context 'when passed a comparison with the "!=" comparator' do
176
+ let(:tree) do
177
+ {
178
+ attribute: { string_no_quotes: 'title' },
179
+ comparator: '!=',
180
+ value: { string_no_quotes: 'Shirt' }
181
+ }
182
+ end
183
+
184
+ it 'is expected to equal the correct ARel node' do
185
+ expect(apply).to eq(Product.arel_table[:title].not_eq('Shirt'))
186
+ end
187
+
188
+ context 'when the value is blank' do
189
+ let(:tree) do
190
+ {
191
+ attribute: { string_no_quotes: 'title' },
192
+ comparator: '!=',
193
+ value: { string_single_quotes: [] }
194
+ }
195
+ end
196
+
197
+ it 'is expected to equal the correct ARel node' do
198
+ expect(apply).to eq(
199
+ Product.arel_table[:title].not_eq(nil).and(
200
+ Product.arel_table[:title].does_not_match_regexp('^\s*$')
201
+ )
202
+ )
203
+ end
204
+ end
205
+ end
206
+
207
+ context 'when passed a comparison with the "<" comparator' do
208
+ let(:tree) do
209
+ {
210
+ attribute: { string_no_quotes: 'created_at' },
211
+ comparator: '<',
212
+ value: { string_no_quotes: '1970-01-01' }
213
+ }
214
+ end
215
+
216
+ it 'is expected to equal the correct ARel node' do
217
+ expect(apply).to eq(Product.arel_table[:created_at].lt('1970-01-01'))
218
+ end
219
+ end
220
+
221
+ context 'when passed a comparison with the ">" comparator' do
222
+ let(:tree) do
223
+ {
224
+ attribute: { string_no_quotes: 'created_at' },
225
+ comparator: '>',
226
+ value: { string_no_quotes: '1970-01-01' }
227
+ }
228
+ end
229
+
230
+ it 'is expected to equal the correct ARel node' do
231
+ expect(apply).to eq(Product.arel_table[:created_at].gt('1970-01-01'))
232
+ end
233
+ end
234
+
235
+ context 'when passed a comparison with the "<=" comparator' do
236
+ let(:tree) do
237
+ {
238
+ attribute: { string_no_quotes: 'created_at' },
239
+ comparator: '<=',
240
+ value: { string_no_quotes: '1970-01-01' }
241
+ }
242
+ end
243
+
244
+ it 'is expected to equal the correct ARel node' do
245
+ expect(apply).to eq(Product.arel_table[:created_at].lteq('1970-01-01'))
246
+ end
247
+ end
248
+
249
+ context 'when passed a comparison with the ">=" comparator' do
250
+ let(:tree) do
251
+ {
252
+ attribute: { string_no_quotes: 'created_at' },
253
+ comparator: '>=',
254
+ value: { string_no_quotes: '1970-01-01' }
255
+ }
256
+ end
257
+
258
+ it 'is expected to equal the correct ARel node' do
259
+ expect(apply).to eq(Product.arel_table[:created_at].gteq('1970-01-01'))
260
+ end
261
+ end
262
+
263
+ context 'when passed a comparison with the "~" comparator' do
264
+ let(:tree) do
265
+ {
266
+ attribute: { string_no_quotes: 'title' },
267
+ comparator: '~',
268
+ value: { string_no_quotes: 'Shirt' }
269
+ }
270
+ end
271
+
272
+ it 'is expected to equal the correct ARel node' do
273
+ expect(apply).to eq(Product.arel_table[:title].matches('%Shirt%'))
274
+ end
275
+
276
+ context 'when the value is blank' do
277
+ let(:tree) do
278
+ {
279
+ attribute: { string_no_quotes: 'title' },
280
+ comparator: '~',
281
+ value: { string_single_quotes: [] }
282
+ }
283
+ end
284
+
285
+ it 'is expected to equal the correct ARel node' do
286
+ expect(apply).to eq(Product.arel_table[:title].matches('%'))
287
+ end
288
+ end
289
+ end
290
+
291
+ context 'when passed a comparison with the "!~" comparator' do
292
+ let(:tree) do
293
+ {
294
+ attribute: { string_no_quotes: 'title' },
295
+ comparator: '!~',
296
+ value: { string_no_quotes: 'Shirt' }
297
+ }
298
+ end
299
+
300
+ it 'is expected to equal the correct ARel node' do
301
+ expect(apply).to eq(Product.arel_table[:title].does_not_match('%Shirt%'))
302
+ end
303
+
304
+ context 'when the value is blank' do
305
+ let(:tree) do
306
+ {
307
+ attribute: { string_no_quotes: 'title' },
308
+ comparator: '!~',
309
+ value: { string_single_quotes: [] }
310
+ }
311
+ end
312
+
313
+ it 'is expected to equal the correct ARel node' do
314
+ expect(apply).to eq(Product.arel_table[:title].does_not_match('%'))
315
+ end
316
+ end
317
+ end
318
+
319
+ context 'when passed a comparison with an unpermitted attribute' do
320
+ let(:tree) do
321
+ {
322
+ attribute: { string_no_quotes: 'id' },
323
+ comparator: '=',
324
+ value: { integer: '123' }
325
+ }
326
+ end
327
+
328
+ it { expect { apply }.to raise_exception(Pursuit::AttributeNotFound) }
329
+ end
330
+
331
+ context 'when passed a comparison with an attribute representing "*"' do
332
+ let(:tree) do
333
+ {
334
+ attribute: { string_no_quotes: 'variations' },
335
+ comparator: '=',
336
+ value: { integer: '123' }
337
+ }
338
+ end
339
+
340
+ it { expect { apply }.to raise_exception(Pursuit::AggregateModifierRequired) }
341
+ end
342
+
343
+ context 'when passed an aggregate comparison with the "=" comparator' do
344
+ let(:tree) do
345
+ {
346
+ aggregate_modifier: '*',
347
+ attribute: { string_no_quotes: 'variation_amount' },
348
+ comparator: '=',
349
+ value: { integer: '5000' }
350
+ }
351
+ end
352
+
353
+ it 'is expected to equal the correct ARel node' do
354
+ expect(apply).to eq(ProductVariation.arel_table[:amount].sum.eq(5000))
355
+ end
356
+
357
+ context 'when the value is blank' do
358
+ let(:tree) do
359
+ {
360
+ aggregate_modifier: '*',
361
+ attribute: { string_no_quotes: 'variation_amount' },
362
+ comparator: '=',
363
+ value: { string_single_quotes: [] }
364
+ }
365
+ end
366
+
367
+ it 'is expected to equal the correct ARel node' do
368
+ expect(apply).to eq(
369
+ ProductVariation.arel_table[:amount].sum.eq(nil).or(
370
+ ProductVariation.arel_table[:amount].sum.matches_regexp('^\s*$')
371
+ )
372
+ )
373
+ end
374
+ end
375
+ end
376
+
377
+ context 'when passed an aggregate comparison with the "!=" comparator' do
378
+ let(:tree) do
379
+ {
380
+ aggregate_modifier: '*',
381
+ attribute: { string_no_quotes: 'variation_amount' },
382
+ comparator: '!=',
383
+ value: { integer: '5000' }
384
+ }
385
+ end
386
+
387
+ it 'is expected to equal the correct ARel node' do
388
+ expect(apply).to eq(ProductVariation.arel_table[:amount].sum.not_eq(5000))
389
+ end
390
+
391
+ context 'when the value is blank' do
392
+ let(:tree) do
393
+ {
394
+ aggregate_modifier: '*',
395
+ attribute: { string_no_quotes: 'variation_amount' },
396
+ comparator: '!=',
397
+ value: { string_single_quotes: [] }
398
+ }
399
+ end
400
+
401
+ it 'is expected to equal the correct ARel node' do
402
+ expect(apply).to eq(
403
+ ProductVariation.arel_table[:amount].sum.not_eq(nil).and(
404
+ ProductVariation.arel_table[:amount].sum.does_not_match_regexp('^\s*$')
405
+ )
406
+ )
407
+ end
408
+ end
409
+ end
410
+
411
+ context 'when passed an aggregate comparison with the "<" comparator' do
412
+ let(:tree) do
413
+ {
414
+ aggregate_modifier: '-',
415
+ attribute: { string_no_quotes: 'variation_amount' },
416
+ comparator: '<',
417
+ value: { integer: '1000' }
418
+ }
419
+ end
420
+
421
+ it 'is expected to equal the correct ARel node' do
422
+ expect(apply).to eq(ProductVariation.arel_table[:amount].minimum.lt(1000))
423
+ end
424
+ end
425
+
426
+ context 'when passed an aggregate comparison with the ">" comparator' do
427
+ let(:tree) do
428
+ {
429
+ aggregate_modifier: '+',
430
+ attribute: { string_no_quotes: 'variation_amount' },
431
+ comparator: '>',
432
+ value: { integer: '1000' }
433
+ }
434
+ end
435
+
436
+ it 'is expected to equal the correct ARel node' do
437
+ expect(apply).to eq(ProductVariation.arel_table[:amount].maximum.gt(1000))
438
+ end
439
+ end
440
+
441
+ context 'when passed an aggregate comparison with the "<=" comparator' do
442
+ let(:tree) do
443
+ {
444
+ aggregate_modifier: '-',
445
+ attribute: { string_no_quotes: 'variation_amount' },
446
+ comparator: '<=',
447
+ value: { integer: '1000' }
448
+ }
449
+ end
450
+
451
+ it 'is expected to equal the correct ARel node' do
452
+ expect(apply).to eq(ProductVariation.arel_table[:amount].minimum.lteq(1000))
453
+ end
454
+ end
455
+
456
+ context 'when passed an aggregate comparison with the ">=" comparator' do
457
+ let(:tree) do
458
+ {
459
+ aggregate_modifier: '+',
460
+ attribute: { string_no_quotes: 'variation_amount' },
461
+ comparator: '>=',
462
+ value: { integer: '1000' }
463
+ }
464
+ end
465
+
466
+ it 'is expected to equal the correct ARel node' do
467
+ expect(apply).to eq(ProductVariation.arel_table[:amount].maximum.gteq(1000))
468
+ end
469
+ end
470
+
471
+ context 'when passed an aggregate comparison with the "~" comparator' do
472
+ let(:tree) do
473
+ {
474
+ aggregate_modifier: '~',
475
+ attribute: { string_no_quotes: 'variation_currency' },
476
+ comparator: '~',
477
+ value: { string_no_quotes: 'USD' }
478
+ }
479
+ end
480
+
481
+ it 'is expected to equal the correct ARel node' do
482
+ expect(apply).to eq(ProductVariation.arel_table[:currency].average.matches('%USD%'))
483
+ end
484
+
485
+ context 'when the value is blank' do
486
+ let(:tree) do
487
+ {
488
+ aggregate_modifier: '~',
489
+ attribute: { string_no_quotes: 'variation_currency' },
490
+ comparator: '~',
491
+ value: { string_single_quotes: [] }
492
+ }
493
+ end
494
+
495
+ it 'is expected to equal the correct ARel node' do
496
+ expect(apply).to eq(ProductVariation.arel_table[:currency].average.matches('%'))
497
+ end
498
+ end
499
+ end
500
+
501
+ context 'when passed an aggregate comparison with the "!~" comparator' do
502
+ let(:tree) do
503
+ {
504
+ aggregate_modifier: '~',
505
+ attribute: { string_no_quotes: 'variation_currency' },
506
+ comparator: '!~',
507
+ value: { string_no_quotes: 'USD' }
508
+ }
509
+ end
510
+
511
+ it 'is expected to equal the correct ARel node' do
512
+ expect(apply).to eq(ProductVariation.arel_table[:currency].average.does_not_match('%USD%'))
513
+ end
514
+
515
+ context 'when the value is blank' do
516
+ let(:tree) do
517
+ {
518
+ aggregate_modifier: '~',
519
+ attribute: { string_no_quotes: 'variation_currency' },
520
+ comparator: '!~',
521
+ value: { string_single_quotes: [] }
522
+ }
523
+ end
524
+
525
+ it 'is expected to equal the correct ARel node' do
526
+ expect(apply).to eq(ProductVariation.arel_table[:currency].average.does_not_match('%'))
527
+ end
528
+ end
529
+ end
530
+
531
+ context 'when passed an aggregate comparison with an unpermitted attribute' do
532
+ let(:tree) do
533
+ {
534
+ aggregate_modifier: '#',
535
+ attribute: { string_no_quotes: 'variation_id' },
536
+ comparator: '>',
537
+ value: { integer: '0' }
538
+ }
539
+ end
540
+
541
+ it { expect { apply }.to raise_exception(Pursuit::AttributeNotFound) }
542
+ end
543
+
544
+ context 'when passed an aggregate comparison with an unknown aggregate modifier' do
545
+ let(:tree) do
546
+ {
547
+ aggregate_modifier: '!',
548
+ attribute: { string_no_quotes: 'variations' },
549
+ comparator: '>',
550
+ value: { integer: '0' }
551
+ }
552
+ end
553
+
554
+ it { expect { apply }.to raise_exception(Pursuit::AggregateModifierNotFound) }
555
+ end
556
+
557
+ context 'when passed an aggregate comparison with aggregate modifiers disabled' do
558
+ let(:permit_aggregate_modifiers) { false }
559
+
560
+ let(:tree) do
561
+ {
562
+ aggregate_modifier: '#',
563
+ attribute: { string_no_quotes: 'variations' },
564
+ comparator: '>',
565
+ value: { integer: '0' }
566
+ }
567
+ end
568
+
569
+ it { expect { apply }.to raise_exception(Pursuit::AggregateModifiersNotAvailable) }
570
+ end
571
+
572
+ context 'when passed a join using the "|" joiner' do
573
+ let(:tree) do
574
+ {
575
+ left: {
576
+ attribute: { string_no_quotes: 'title' },
577
+ comparator: '=',
578
+ value: { string_no_quotes: 'Shirt' }
579
+ },
580
+ joiner: '|',
581
+ right: {
582
+ attribute: { string_no_quotes: 'title' },
583
+ comparator: '=',
584
+ value: { string_no_quotes: 'Jumper' }
585
+ }
586
+ }
587
+ end
588
+
589
+ it 'is expected to equal the correct ARel node' do
590
+ expect(apply).to eq(
591
+ Product.arel_table[:title].eq('Shirt').or(
592
+ Product.arel_table[:title].eq('Jumper')
593
+ )
594
+ )
595
+ end
596
+ end
597
+
598
+ context 'when passed a join using the "&" joiner' do
599
+ let(:tree) do
600
+ {
601
+ left: {
602
+ attribute: { string_no_quotes: 'title' },
603
+ comparator: '~',
604
+ value: { string_no_quotes: 'Shirt' }
605
+ },
606
+ joiner: '&',
607
+ right: {
608
+ attribute: { string_no_quotes: 'title' },
609
+ comparator: '~',
610
+ value: { string_no_quotes: 'Green' }
611
+ }
612
+ }
613
+ end
614
+
615
+ it 'is expected to equal the correct ARel node' do
616
+ expect(apply).to eq(
617
+ Product.arel_table[:title].matches('%Shirt%').and(
618
+ Product.arel_table[:title].matches('%Green%')
619
+ )
620
+ )
621
+ end
622
+ end
623
+ end
624
+ end