pursuit 0.4.5 → 1.1.0

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 (51) 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 +86 -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 +66 -0
  18. data/lib/pursuit/term_parser.rb +44 -0
  19. data/lib/pursuit/term_search.rb +74 -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 +3 -14
  25. data/spec/internal/app/models/product_category.rb +3 -1
  26. data/spec/internal/app/models/product_variation.rb +3 -1
  27. data/spec/internal/app/searches/product_category_search.rb +50 -0
  28. data/spec/internal/app/searches/product_search.rb +53 -0
  29. data/spec/internal/app/searches/product_variation_search.rb +53 -0
  30. data/spec/lib/pursuit/predicate_parser_spec.rb +1604 -0
  31. data/spec/lib/pursuit/predicate_search_spec.rb +116 -0
  32. data/spec/lib/pursuit/predicate_transform_spec.rb +624 -0
  33. data/spec/lib/pursuit/simple_search_spec.rb +73 -0
  34. data/spec/lib/pursuit/term_parser_spec.rb +271 -0
  35. data/spec/lib/pursuit/term_search_spec.rb +85 -0
  36. data/spec/lib/pursuit/term_transform_spec.rb +105 -0
  37. metadata +50 -25
  38. data/.travis.yml +0 -26
  39. data/lib/pursuit/dsl.rb +0 -28
  40. data/lib/pursuit/railtie.rb +0 -13
  41. data/lib/pursuit/search.rb +0 -172
  42. data/lib/pursuit/search_options.rb +0 -86
  43. data/lib/pursuit/search_term_parser.rb +0 -46
  44. data/spec/lib/pursuit/dsl_spec.rb +0 -22
  45. data/spec/lib/pursuit/search_options_spec.rb +0 -146
  46. data/spec/lib/pursuit/search_spec.rb +0 -516
  47. data/spec/lib/pursuit/search_term_parser_spec.rb +0 -34
  48. data/travis/gemfiles/5.2.gemfile +0 -8
  49. data/travis/gemfiles/6.0.gemfile +0 -8
  50. data/travis/gemfiles/6.1.gemfile +0 -8
  51. data/travis/gemfiles/7.0.gemfile +0 -8
@@ -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
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Pursuit::SimpleSearch do
4
+ subject(:simple_search) { described_class.new(default_table: Product.arel_table) }
5
+
6
+ describe '#initialize' do
7
+ it 'is expected to set #default_table eq `default_table`' do
8
+ expect(simple_search).to have_attributes(default_table: Product.arel_table)
9
+ end
10
+
11
+ it 'is expected to invoke the passed block' do
12
+ expect { |block| described_class.new(&block) }.to yield_control
13
+ end
14
+ end
15
+
16
+ describe '#search_attribute' do
17
+ subject(:search_attribute) { simple_search.search_attribute(attribute) }
18
+
19
+ context 'when `attribute` is a Symbol' do
20
+ let(:attribute) { :title }
21
+
22
+ it 'is expected to add the attribute from #default_table to #attributes' do
23
+ search_attribute
24
+ expect(simple_search.attributes).to include(Product.arel_table[:title])
25
+ end
26
+ end
27
+
28
+ context 'when `attribute` is an Arel::Attributes::Attribute' do
29
+ let(:attribute) { ProductVariation.arel_table[:currency] }
30
+
31
+ it 'is expected to add the attribute to #attributes' do
32
+ search_attribute
33
+ expect(simple_search.attributes).to include(ProductVariation.arel_table[:currency])
34
+ end
35
+ end
36
+ end
37
+
38
+ describe '#parse' do
39
+ subject(:parse) { simple_search.parse('Shirt') }
40
+
41
+ before do
42
+ simple_search.attributes << Product.arel_table[:title]
43
+ simple_search.attributes << ProductVariation.arel_table[:title]
44
+ end
45
+
46
+ it 'is expected to equal the ARel node' do
47
+ expect(parse).to eq(
48
+ Product.arel_table[:title].matches('%Shirt%').or(
49
+ ProductVariation.arel_table[:title].matches('%Shirt%')
50
+ )
51
+ )
52
+ end
53
+ end
54
+
55
+ describe '#apply' do
56
+ subject(:apply) { simple_search.apply('Shirt', Product.left_outer_joins(:variations).group(:id)) }
57
+
58
+ before do
59
+ simple_search.attributes << Product.arel_table[:title]
60
+ simple_search.attributes << ProductVariation.arel_table[:title]
61
+ end
62
+
63
+ it 'is expected to equal `relation` with simple clauses applied' do
64
+ expect(apply).to eq(
65
+ Product.left_outer_joins(:variations).group(:id).where(
66
+ Product.arel_table[:title].matches('%Shirt%').or(
67
+ ProductVariation.arel_table[:title].matches('%Shirt%')
68
+ )
69
+ )
70
+ )
71
+ end
72
+ end
73
+ end