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
@@ -1,516 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Pursuit::Search do
4
- subject(:search) { described_class.new(search_options) }
5
-
6
- let(:search_options) do
7
- Pursuit::SearchOptions.new(Product) do |o|
8
- o.relation :category, :name
9
- o.relation :variations, :title, :stock_status
10
-
11
- o.attribute :title
12
- o.attribute :description
13
- o.attribute :rating, unkeyed: false
14
- o.attribute :title_length, unkeyed: false do
15
- Arel::Nodes::NamedFunction.new('LENGTH', [Product.arel_table[:title]])
16
- end
17
-
18
- o.attribute :cat, :category_id
19
- end
20
- end
21
-
22
- describe '#perform' do
23
- subject(:perform) { search.perform(query) }
24
-
25
- context 'when passed a blank query' do
26
- let(:query) { '' }
27
-
28
- let(:product_a) { Product.create!(title: 'Alpha') }
29
- let(:product_b) { Product.create!(title: 'Beta') }
30
-
31
- before do
32
- product_a
33
- product_b
34
- end
35
-
36
- it 'is expected to contain all records' do
37
- expect(perform).to contain_exactly(product_a, product_b)
38
- end
39
- end
40
-
41
- context 'when passed an unkeyed query' do
42
- let(:query) { 'shirt' }
43
-
44
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
45
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
46
- let(:product_c) { Product.create!(title: 'Socks') }
47
-
48
- before do
49
- product_a
50
- product_b
51
- product_c
52
- end
53
-
54
- it 'is expected to contain the matching records' do
55
- expect(perform).to contain_exactly(product_a, product_b)
56
- end
57
- end
58
-
59
- context 'when passed an `equal to` keyed attribute query' do
60
- let(:query) { 'title=="Funky Shirt"' }
61
-
62
- let(:product_a) { Product.create!(title: 'Plain Shirt', rating: 2) }
63
- let(:product_b) { Product.create!(title: 'Funky Shirt', rating: 4) }
64
- let(:product_c) { Product.create!(title: 'Socks - Pack of 4') }
65
-
66
- before do
67
- product_a
68
- product_b
69
- product_c
70
- end
71
-
72
- it 'is expected to contain the matching records' do
73
- expect(perform).to contain_exactly(product_b)
74
- end
75
- end
76
-
77
- context 'when passed a `not equal to` keyed attribute query' do
78
- let(:query) { 'title!="Funky Shirt"' }
79
-
80
- let(:product_a) { Product.create!(title: 'Plain Shirt', rating: 2) }
81
- let(:product_b) { Product.create!(title: 'Funky Shirt', rating: 4) }
82
- let(:product_c) { Product.create!(title: 'Socks - Pack of 4') }
83
-
84
- before do
85
- product_a
86
- product_b
87
- product_c
88
- end
89
-
90
- it 'is expected to contain the matching records' do
91
- expect(perform).to contain_exactly(product_a, product_c)
92
- end
93
- end
94
-
95
- context 'when passed a `match` keyed attribute query' do
96
- let(:query) { 'title*=shirt' }
97
-
98
- let(:product_a) { Product.create!(title: 'Plain Shirt', rating: 2) }
99
- let(:product_b) { Product.create!(title: 'Funky Shirt', rating: 4) }
100
- let(:product_c) { Product.create!(title: 'Socks - Pack of 4') }
101
-
102
- before do
103
- product_a
104
- product_b
105
- product_c
106
- end
107
-
108
- it 'is expected to contain the matching records' do
109
- expect(perform).to contain_exactly(product_a, product_b)
110
- end
111
- end
112
-
113
- context 'when passed a `not match` keyed attribute query' do
114
- let(:query) { 'title!*=socks' }
115
-
116
- let(:product_a) { Product.create!(title: 'Plain Shirt', rating: 2) }
117
- let(:product_b) { Product.create!(title: 'Funky Shirt', rating: 4) }
118
- let(:product_c) { Product.create!(title: 'Socks - Pack of 4') }
119
-
120
- before do
121
- product_a
122
- product_b
123
- product_c
124
- end
125
-
126
- it 'is expected to contain the matching records' do
127
- expect(perform).to contain_exactly(product_a, product_b)
128
- end
129
- end
130
-
131
- context 'when passed a numeric `not equal to` keyed attribute query' do
132
- let(:query) { 'rating!=2' }
133
-
134
- let(:product_a) { Product.create!(title: 'Plain Shirt', rating: 2) }
135
- let(:product_b) { Product.create!(title: 'Funky Shirt', rating: 4) }
136
- let(:product_c) { Product.create!(title: 'Socks - Pack of 4', rating: 5) }
137
-
138
- before do
139
- product_a
140
- product_b
141
- product_c
142
- end
143
-
144
- it 'is expected to contain the matching records' do
145
- expect(perform).to contain_exactly(product_b, product_c)
146
- end
147
- end
148
-
149
- context 'when passed a `greater than` keyed attribute query' do
150
- let(:query) { 'rating>2' }
151
-
152
- let(:product_a) { Product.create!(title: 'Plain Shirt', rating: 2) }
153
- let(:product_b) { Product.create!(title: 'Funky Shirt', rating: 4) }
154
- let(:product_c) { Product.create!(title: 'Socks - Pack of 4', rating: 5) }
155
-
156
- before do
157
- product_a
158
- product_b
159
- product_c
160
- end
161
-
162
- it 'is expected to contain the matching records' do
163
- expect(perform).to contain_exactly(product_b, product_c)
164
- end
165
- end
166
-
167
- context 'when passed a `greater than or equal to` keyed attribute query' do
168
- let(:query) { 'rating>=4' }
169
-
170
- let(:product_a) { Product.create!(title: 'Plain Shirt', rating: 2) }
171
- let(:product_b) { Product.create!(title: 'Funky Shirt', rating: 4) }
172
- let(:product_c) { Product.create!(title: 'Socks - Pack of 4', rating: 5) }
173
-
174
- before do
175
- product_a
176
- product_b
177
- product_c
178
- end
179
-
180
- it 'is expected to contain the matching records' do
181
- expect(perform).to contain_exactly(product_b, product_c)
182
- end
183
- end
184
-
185
- context 'when passed a `less than` keyed attribute query' do
186
- let(:query) { 'rating<5' }
187
-
188
- let(:product_a) { Product.create!(title: 'Plain Shirt', rating: 2) }
189
- let(:product_b) { Product.create!(title: 'Funky Shirt', rating: 4) }
190
- let(:product_c) { Product.create!(title: 'Socks - Pack of 4', rating: 5) }
191
-
192
- before do
193
- product_a
194
- product_b
195
- product_c
196
- end
197
-
198
- it 'is expected to contain the matching records' do
199
- expect(perform).to contain_exactly(product_a, product_b)
200
- end
201
- end
202
-
203
- context 'when passed a `less than or equal to` keyed attribute query' do
204
- let(:query) { 'rating<=4' }
205
-
206
- let(:product_a) { Product.create!(title: 'Plain Shirt', rating: 2) }
207
- let(:product_b) { Product.create!(title: 'Funky Shirt', rating: 4) }
208
- let(:product_c) { Product.create!(title: 'Socks - Pack of 4', rating: 5) }
209
-
210
- before do
211
- product_a
212
- product_b
213
- product_c
214
- end
215
-
216
- it 'is expected to contain the matching records' do
217
- expect(perform).to contain_exactly(product_a, product_b)
218
- end
219
- end
220
-
221
- context 'when passed a `greater than` and `less than` keyed attribute query' do
222
- let(:query) { 'rating>2 rating<5' }
223
-
224
- let(:product_a) { Product.create!(title: 'Plain Shirt', rating: 2) }
225
- let(:product_b) { Product.create!(title: 'Funky Shirt', rating: 4) }
226
- let(:product_c) { Product.create!(title: 'Socks - Pack of 4', rating: 5) }
227
-
228
- before do
229
- product_a
230
- product_b
231
- product_c
232
- end
233
-
234
- it 'is expected to contain the matching records' do
235
- expect(perform).to contain_exactly(product_b)
236
- end
237
- end
238
-
239
- context 'when passed a virtual keyed attribute query' do
240
- let(:query) { 'title_length==5' }
241
-
242
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
243
- let(:product_b) { Product.create!(title: 'Socks') }
244
-
245
- before do
246
- product_a
247
- product_b
248
- end
249
-
250
- it 'is expected to contain the matching records' do
251
- expect(perform).to contain_exactly(product_b)
252
- end
253
- end
254
-
255
- context 'when querying a custom attribute whose name matches a reflection' do
256
- let(:query) { 'cat==shirts' }
257
-
258
- let(:shirts_category) { ProductCategory.create!(id: 'shirts', name: 'The Shirt Collection') }
259
- let(:socks_category) { ProductCategory.create!(id: 'socks', name: 'The Sock Collection') }
260
-
261
- let(:product_a) { Product.create!(title: 'Plain Shirt', category: shirts_category) }
262
- let(:product_b) { Product.create!(title: 'Funky Shirt', category: shirts_category) }
263
- let(:product_c) { Product.create!(title: 'Socks - Pack of 4', category: socks_category) }
264
-
265
- before do
266
- product_a
267
- product_b
268
- product_c
269
- end
270
-
271
- it 'is expected to contain the matching records' do
272
- expect(perform).to contain_exactly(product_a, product_b)
273
- end
274
- end
275
-
276
- context 'when searching a #has_many relationship' do
277
- context 'when passed a `match` relationship search query' do
278
- let(:query) { 'variations*=green' }
279
-
280
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
281
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
282
-
283
- let(:product_variation_a) { ProductVariation.create!(product: product_a, title: 'Red') }
284
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
285
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
286
-
287
- before do
288
- product_a
289
- product_b
290
-
291
- product_variation_a
292
- product_variation_b
293
- product_variation_c
294
- end
295
-
296
- it 'is expected to contain the matching records' do
297
- expect(perform).to contain_exactly(product_b)
298
- end
299
- end
300
-
301
- context 'when passed an `equal to` relationship count query' do
302
- let(:query) { 'variations==1' }
303
-
304
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
305
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
306
- let(:product_c) { Product.create!(title: 'Socks') }
307
-
308
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
309
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
310
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
311
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
312
-
313
- before do
314
- product_a
315
- product_b
316
- product_c
317
-
318
- product_variation_a
319
- product_variation_b
320
- product_variation_c
321
- product_variation_d
322
- end
323
-
324
- it 'is expected to contain the matching records' do
325
- expect(perform).to contain_exactly(product_c)
326
- end
327
- end
328
-
329
- context 'when passed a `greater than` relationship count query' do
330
- let(:query) { 'variations>1' }
331
-
332
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
333
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
334
- let(:product_c) { Product.create!(title: 'Socks') }
335
-
336
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
337
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
338
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
339
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
340
- let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
341
- let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
342
-
343
- before do
344
- product_a
345
- product_b
346
- product_c
347
-
348
- product_variation_a
349
- product_variation_b
350
- product_variation_c
351
- product_variation_d
352
- product_variation_e
353
- product_variation_f
354
- end
355
-
356
- it 'is expected to contain the matching records' do
357
- expect(perform).to contain_exactly(product_a, product_b)
358
- end
359
- end
360
-
361
- context 'when passed a `greater than or equal to` relationship count query' do
362
- let(:query) { 'variations>=2' }
363
-
364
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
365
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
366
- let(:product_c) { Product.create!(title: 'Socks') }
367
-
368
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
369
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
370
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
371
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
372
- let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
373
- let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
374
-
375
- before do
376
- product_a
377
- product_b
378
- product_c
379
-
380
- product_variation_a
381
- product_variation_b
382
- product_variation_c
383
- product_variation_d
384
- product_variation_e
385
- product_variation_f
386
- end
387
-
388
- it 'is expected to contain the matching records' do
389
- expect(perform).to contain_exactly(product_a, product_b)
390
- end
391
- end
392
-
393
- context 'when passed a `less than` relationship count query' do
394
- let(:query) { 'variations<3' }
395
-
396
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
397
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
398
- let(:product_c) { Product.create!(title: 'Socks') }
399
-
400
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
401
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
402
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
403
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
404
- let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
405
- let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
406
-
407
- before do
408
- product_a
409
- product_b
410
- product_c
411
-
412
- product_variation_a
413
- product_variation_b
414
- product_variation_c
415
- product_variation_d
416
- product_variation_e
417
- product_variation_f
418
- end
419
-
420
- it 'is expected to contain the matching records' do
421
- expect(perform).to contain_exactly(product_a, product_c)
422
- end
423
- end
424
-
425
- context 'when passed a `less than or equal to` relationship count query' do
426
- let(:query) { 'variations<=2' }
427
-
428
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
429
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
430
- let(:product_c) { Product.create!(title: 'Socks') }
431
-
432
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
433
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
434
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
435
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
436
- let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
437
- let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
438
-
439
- before do
440
- product_a
441
- product_b
442
- product_c
443
-
444
- product_variation_a
445
- product_variation_b
446
- product_variation_c
447
- product_variation_d
448
- product_variation_e
449
- product_variation_f
450
- end
451
-
452
- it 'is expected to contain the matching records' do
453
- expect(perform).to contain_exactly(product_a, product_c)
454
- end
455
- end
456
-
457
- context 'when passed a `greater than` and `less than` relationship count query' do
458
- let(:query) { 'variations>1 variations<3' }
459
-
460
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
461
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
462
- let(:product_c) { Product.create!(title: 'Socks') }
463
-
464
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
465
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
466
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
467
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
468
- let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
469
- let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
470
-
471
- before do
472
- product_a
473
- product_b
474
- product_c
475
-
476
- product_variation_a
477
- product_variation_b
478
- product_variation_c
479
- product_variation_d
480
- product_variation_e
481
- product_variation_f
482
- end
483
-
484
- it 'is expected to contain the matching records' do
485
- expect(perform).to contain_exactly(product_a)
486
- end
487
- end
488
- end
489
-
490
- context 'when searching a #belongs_to relationship' do
491
- context 'when passed a `match` relationship search query' do
492
- let(:query) { 'category*=shirt' }
493
-
494
- let(:product_category_a) { ProductCategory.create!(id: 'one', name: 'Shirts') }
495
- let(:product_category_b) { ProductCategory.create!(id: 'two', name: 'Socks') }
496
-
497
- let(:product_a) { Product.create!(category: product_category_a, title: 'Plain Shirt') }
498
- let(:product_b) { Product.create!(category: product_category_a, title: 'Funky Shirt') }
499
- let(:product_c) { Product.create!(category: product_category_b, title: 'White Socks') }
500
-
501
- before do
502
- product_category_a
503
- product_category_b
504
-
505
- product_a
506
- product_b
507
- product_c
508
- end
509
-
510
- it 'is expected to contain the matching records' do
511
- expect(perform).to contain_exactly(product_a, product_b)
512
- end
513
- end
514
- end
515
- end
516
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Pursuit::SearchTermParser do
4
- subject(:parser) { described_class.new(query, keys: keys) }
5
-
6
- let(:keys) { %w[title description rating stock_status] }
7
- let(:query) do
8
- "plain title!='Socks' description*=\"green\" stock_status==in_stock shirt rating>=2 other*=thing rating<5"
9
- end
10
-
11
- describe '#unkeyed_term' do
12
- subject(:unkeyed_term) { parser.unkeyed_term }
13
-
14
- it 'is expected to eq the correct unkeyed term' do
15
- expect(unkeyed_term).to eq('plain shirt other*=thing')
16
- end
17
- end
18
-
19
- describe '#keyed_terms' do
20
- subject(:keyed_terms) { parser.keyed_terms }
21
-
22
- it 'is expected to eq the correct keyed terms' do
23
- expect(keyed_terms).to eq(
24
- [
25
- Pursuit::SearchTermParser::KeyedTerm.new('title', '!=', 'Socks'),
26
- Pursuit::SearchTermParser::KeyedTerm.new('description', '*=', 'green'),
27
- Pursuit::SearchTermParser::KeyedTerm.new('stock_status', '==', 'in_stock'),
28
- Pursuit::SearchTermParser::KeyedTerm.new('rating', '>=', '2'),
29
- Pursuit::SearchTermParser::KeyedTerm.new('rating', '<', '5')
30
- ]
31
- )
32
- end
33
- end
34
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- gemspec path: '../../'
6
-
7
- gem 'activerecord', '~> 5.2.0'
8
- gem 'activesupport', '~> 5.2.0'
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- gemspec path: '../../'
6
-
7
- gem 'activerecord', '~> 6.0.0'
8
- gem 'activesupport', '~> 6.0.0'
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- gemspec path: '../../'
6
-
7
- gem 'activerecord', '~> 6.1.0'
8
- gem 'activesupport', '~> 6.1.0'
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- gemspec path: '../../'
6
-
7
- gem 'activerecord', '~> 7.0.0'
8
- gem 'activesupport', '~> 7.0.0'