pursuit 0.4.5 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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