trailblazer-finder 0.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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rubocop.yml +45 -0
  4. data/.rubocop_todo.yml +52 -0
  5. data/.travis.yml +15 -0
  6. data/Gemfile +12 -0
  7. data/LICENSE.txt +23 -0
  8. data/README.md +494 -0
  9. data/Rakefile +29 -0
  10. data/lib/trailblazer/finder/adapters/active_record/paging.rb +20 -0
  11. data/lib/trailblazer/finder/adapters/active_record/sorting.rb +20 -0
  12. data/lib/trailblazer/finder/adapters/active_record.rb +30 -0
  13. data/lib/trailblazer/finder/adapters/data_mapper/paging.rb +20 -0
  14. data/lib/trailblazer/finder/adapters/data_mapper/sorting.rb +25 -0
  15. data/lib/trailblazer/finder/adapters/data_mapper.rb +30 -0
  16. data/lib/trailblazer/finder/adapters/friendly_id.rb +33 -0
  17. data/lib/trailblazer/finder/adapters/kaminari.rb +18 -0
  18. data/lib/trailblazer/finder/adapters/sequel/paging.rb +20 -0
  19. data/lib/trailblazer/finder/adapters/sequel/sorting.rb +25 -0
  20. data/lib/trailblazer/finder/adapters/sequel.rb +30 -0
  21. data/lib/trailblazer/finder/adapters/will_paginate.rb +18 -0
  22. data/lib/trailblazer/finder/adapters.rb +26 -0
  23. data/lib/trailblazer/finder/base.rb +98 -0
  24. data/lib/trailblazer/finder/errors/block_ignored.rb +11 -0
  25. data/lib/trailblazer/finder/errors/invalid_defined_by_value.rb +11 -0
  26. data/lib/trailblazer/finder/errors/invalid_number.rb +16 -0
  27. data/lib/trailblazer/finder/errors/missing_entity_type.rb +11 -0
  28. data/lib/trailblazer/finder/errors/with_ignored.rb +11 -0
  29. data/lib/trailblazer/finder/features/paging.rb +55 -0
  30. data/lib/trailblazer/finder/features/sorting.rb +66 -0
  31. data/lib/trailblazer/finder/features.rb +22 -0
  32. data/lib/trailblazer/finder/filter.rb +66 -0
  33. data/lib/trailblazer/finder/find.rb +29 -0
  34. data/lib/trailblazer/finder/utils/extra.rb +31 -0
  35. data/lib/trailblazer/finder/utils/params.rb +28 -0
  36. data/lib/trailblazer/finder/utils/parse.rb +25 -0
  37. data/lib/trailblazer/finder/utils/string.rb +35 -0
  38. data/lib/trailblazer/finder/version.rb +5 -0
  39. data/lib/trailblazer/finder.rb +29 -0
  40. data/lib/trailblazer/operation/finder.rb +61 -0
  41. data/spec/spec_helper.rb +15 -0
  42. data/spec/spec_helper_active_record.rb +50 -0
  43. data/spec/spec_helper_data_mapper.rb +35 -0
  44. data/spec/spec_helper_sequel.rb +32 -0
  45. data/spec/support/paging_shared_example.rb +65 -0
  46. data/spec/support/sorting_shared_example.rb +95 -0
  47. data/spec/trailblazer/finder/adapters/active_record/base_spec.rb +112 -0
  48. data/spec/trailblazer/finder/adapters/active_record/paging_spec.rb +64 -0
  49. data/spec/trailblazer/finder/adapters/active_record/sorting_spec.rb +82 -0
  50. data/spec/trailblazer/finder/adapters/data_mapper/base_spec.rb +112 -0
  51. data/spec/trailblazer/finder/adapters/data_mapper/paging_spec.rb +64 -0
  52. data/spec/trailblazer/finder/adapters/data_mapper/sorting_spec.rb +85 -0
  53. data/spec/trailblazer/finder/adapters/friendly_id_spec.rb +46 -0
  54. data/spec/trailblazer/finder/adapters/kaminari_spec.rb +64 -0
  55. data/spec/trailblazer/finder/adapters/sequel/base_spec.rb +112 -0
  56. data/spec/trailblazer/finder/adapters/sequel/paging_spec.rb +64 -0
  57. data/spec/trailblazer/finder/adapters/sequel/sorting_spec.rb +82 -0
  58. data/spec/trailblazer/finder/adapters/will_paginate_spec.rb +71 -0
  59. data/spec/trailblazer/finder/adapters_spec.rb +110 -0
  60. data/spec/trailblazer/finder/base_spec.rb +329 -0
  61. data/spec/trailblazer/finder/features/paging_spec.rb +104 -0
  62. data/spec/trailblazer/finder/features/sorting_spec.rb +100 -0
  63. data/spec/trailblazer/finder/features_spec.rb +55 -0
  64. data/spec/trailblazer/finder/filter_spec.rb +133 -0
  65. data/spec/trailblazer/finder/find_spec.rb +72 -0
  66. data/spec/trailblazer/finder/utils/extra_spec.rb +41 -0
  67. data/spec/trailblazer/finder/utils/params_spec.rb +39 -0
  68. data/spec/trailblazer/finder/utils/parse_spec.rb +33 -0
  69. data/spec/trailblazer/finder/utils/string_spec.rb +25 -0
  70. data/spec/trailblazer/operation/finder_spec.rb +103 -0
  71. data/spec/trailblazer/operation/paging_spec.rb +68 -0
  72. data/spec/trailblazer/operation/sorting_spec.rb +80 -0
  73. data/trailblazer-finder.gemspec +41 -0
  74. metadata +402 -0
@@ -0,0 +1,329 @@
1
+ require 'spec_helper'
2
+ puts 'AR is loaded' if Gem.loaded_specs.key?('active_record')
3
+ module Trailblazer
4
+ class Finder # rubocop:disable Metrics/ClassLength
5
+ describe Base do
6
+ def define_finder_class(&block)
7
+ Class.new do
8
+ include Finder::Base
9
+
10
+ class_eval(&block)
11
+ end
12
+ end
13
+
14
+ def finder_class(default_entity_type = [], &block)
15
+ define_finder_class do
16
+ entity_type { default_entity_type }
17
+
18
+ if block.nil?
19
+ filter_by :value do |entity_type, value|
20
+ entity_type.select { |v| v == value }
21
+ end
22
+ else
23
+ class_eval(&block)
24
+ end
25
+ end
26
+ end
27
+
28
+ def new_finder(default_entity_type = [], filter = {}, &block)
29
+ finder_class(default_entity_type, &block).new filter: filter
30
+ end
31
+
32
+ it 'can have its #initialize method overwritten' do
33
+ finder = new_finder do
34
+ attr_reader :initialized
35
+
36
+ alias_method :initialized?, :initialized
37
+
38
+ def initialize(filter = {})
39
+ @initialized = true
40
+ super filter
41
+ end
42
+ end
43
+
44
+ expect(finder).to be_initialized
45
+ end
46
+
47
+ it 'can have multiple subclasses' do
48
+ finder1 = new_finder [1, 2, 3], filter: 1 do
49
+ filter_by :filter do |entity_type, value|
50
+ entity_type.select { |v| v == value }
51
+ end
52
+ end
53
+
54
+ finder2 = new_finder [1, 2, 3], filter: 1 do
55
+ filter_by :filter, 2 do |entity_type, value|
56
+ entity_type.reject { |v| v == value }
57
+ end
58
+ end
59
+
60
+ expect(finder1.results).not_to eq finder2.results
61
+ end
62
+
63
+ it 'can be inherited' do
64
+ equality_finder = Class.new(finder_class([1, 2, 3])) do
65
+ filter_by :value do |entity_type, value|
66
+ entity_type.select { |v| v == value }
67
+ end
68
+ end
69
+
70
+ inequality_finder = Class.new(finder_class([1, 2, 3])) do
71
+ filter_by :value do |entity_type, value|
72
+ entity_type.select { |v| v > value }
73
+ end
74
+ end
75
+
76
+ expect(equality_finder.new(filter: { value: 1 }).results).to eq [1]
77
+ expect(inequality_finder.new(filter: { value: 1 }).results).to eq [2, 3]
78
+ end
79
+
80
+ describe 'entity_type' do
81
+ def finder_class
82
+ define_finder_class do
83
+ filter_by :name do
84
+ end
85
+ end
86
+ end
87
+
88
+ it 'accepts entity_type as argument' do
89
+ expect(finder_class.new(entity_type: 'entity_type').results).to eq 'entity_type'
90
+ end
91
+
92
+ it 'raises an error if no entity_type is provided' do
93
+ expect { finder_class.new }.to raise_error Finder::Error::MissingEntityType
94
+ end
95
+
96
+ it 'can overwrite the finder entity_type' do
97
+ finder_class = define_finder_class do
98
+ entity_type { 'entity_type' }
99
+ end
100
+
101
+ expect(finder_class.new(entity_type: 'other entity_type').results).to eq 'other entity_type'
102
+ end
103
+
104
+ it 'accepts a block in context of finder object' do
105
+ finder_class = define_finder_class do
106
+ entity_type { inner_entity_type }
107
+
108
+ attr_reader :inner_entity_type
109
+
110
+ def initialize
111
+ @inner_entity_type = 'entity_type'
112
+ super
113
+ end
114
+ end
115
+
116
+ expect(finder_class.new.results).to eq 'entity_type'
117
+ end
118
+
119
+ it 'passing nil as entity_type in constructor, falls back to default entity_type' do
120
+ finder_class = define_finder_class do
121
+ entity_type { 'entity_type' }
122
+ end
123
+
124
+ expect(finder_class.new(entity_type: nil).results).to eq 'entity_type'
125
+ end
126
+ end
127
+
128
+ describe 'filter_by' do
129
+ it 'has a default filter' do
130
+ entity_type = [{ id: 1, value: 'Test 1' }, { id: 2, value: 'Test 2' }, { id: 3, value: 'Test 3' }]
131
+ finder = new_finder entity_type, value: 'Test 1' do
132
+ filter_by :value
133
+ end
134
+
135
+ expect(finder.results.map { |n| n[:value] }).to eq ['Test 1']
136
+ end
137
+
138
+ it 'has a default filter working when it\'s nested' do
139
+ entity_type = [{ id: 1, value: [id: 4, value: 'Test 1'] }, { id: 2, value: 'Test 2' }, { id: 3, value: 'Test 3' }]
140
+ finder = new_finder entity_type, value: 'Test 1' do
141
+ filter_by :value
142
+ end
143
+
144
+ expect(finder.results.map { |n| n[:value] }).to eq ['Test 1']
145
+ expect(finder.results.map { |n| n[:id] }).to eq [4]
146
+ end
147
+
148
+ it 'has another default filter' do
149
+ entity_type = [{ id: 1, value: 'Test 1' }, { id: 2, value: 'Test 2' }, { id: 3, value: 'Test 3' }]
150
+ finder = new_finder entity_type, id: 2 do
151
+ filter_by :id
152
+ end
153
+
154
+ expect(finder.results.map { |n| n[:value] }).to eq ['Test 2']
155
+ end
156
+
157
+ it 'returns the entity_type if nil is returned' do
158
+ entity_type = [1, 2, 3]
159
+ finder = new_finder entity_type, value: 'some' do
160
+ filter_by :value do
161
+ nil
162
+ end
163
+ end
164
+
165
+ expect(finder.results).to eq entity_type
166
+ end
167
+
168
+ it 'can use methods from the object' do
169
+ finder1 = new_finder [1, 2, 3], filter: 1 do
170
+ filter_by :filter do |entity_type, value|
171
+ some_instance_method(entity_type, value)
172
+ end
173
+
174
+ private
175
+
176
+ def some_instance_method(entity_type, value)
177
+ entity_type.select { |v| v == value }
178
+ end
179
+ end
180
+
181
+ expect(finder1.results).to eq [1]
182
+ end
183
+
184
+ it 'can dispatch with instance methods' do
185
+ finder = new_finder [1, 2, 3], filter: 1 do
186
+ filter_by :filter, with: :some_instance_method
187
+
188
+ private
189
+
190
+ def some_instance_method(entity_type, value)
191
+ entity_type.select { |v| v == value }
192
+ end
193
+ end
194
+
195
+ expect(finder.results).to eq [1]
196
+ end
197
+ end
198
+
199
+ describe 'filter_by attributes' do
200
+ it 'accesses filter values' do
201
+ finder = new_finder [], value: 1
202
+ expect(finder.value).to eq 1
203
+ end
204
+
205
+ it 'returns default filter value if filter_by is not specified' do
206
+ finder = new_finder do
207
+ filter_by :value, 1
208
+ end
209
+ expect(finder.value).to eq 1
210
+ end
211
+
212
+ it 'does not include invalid filters' do
213
+ finder = new_finder [], invalid: 'option'
214
+ expect { finder.invalid }.to raise_error NoMethodError
215
+ end
216
+ end
217
+
218
+ describe '.results' do
219
+ it 'shortcut for creating new finder and immediately returning results' do
220
+ klass = finder_class [1, 2, 3]
221
+ expect(klass.results(filter: { value: 1 })).to eq [1]
222
+ end
223
+ end
224
+
225
+ describe '#results' do
226
+ it 'returns only the filtered finder results' do
227
+ finder = new_finder [1, 2, 3], value: 1
228
+ expect(finder.results).to eq [1]
229
+ end
230
+
231
+ it 'can apply several filters' do
232
+ values = [1, 2, 3, 4, 5, 6, 7]
233
+ finder = new_finder values, bigger_than: 3, odd: true do
234
+ filter_by :bigger_than do |entity_type, value|
235
+ entity_type.select { |v| v > value }
236
+ end
237
+
238
+ filter_by :odd do |entity_type, value|
239
+ entity_type.select(&:odd?) if value
240
+ end
241
+ end
242
+
243
+ expect(finder.results).to eq [5, 7]
244
+ end
245
+
246
+ it 'ignores invalid filters' do
247
+ finder = new_finder [1, 2, 3], invalid: 'filter_by'
248
+ expect(finder.results).to eq [1, 2, 3]
249
+ end
250
+
251
+ it 'can be overwritten by overwriting #fetch_results' do
252
+ finder = new_finder [1, 2, 3], value: 1 do
253
+ filter_by :value do |entity_type, value|
254
+ entity_type.select { |v| v == value }
255
+ end
256
+
257
+ def fetch_results
258
+ super.map { |v| "~#{v}~" }
259
+ end
260
+ end
261
+
262
+ expect(finder.results).to eq ['~1~']
263
+ end
264
+
265
+ it 'applies to default filters' do
266
+ finder = new_finder [1, 2, 3] do
267
+ filter_by :value, 1 do |entity_type, value|
268
+ entity_type.select { |v| v == value }
269
+ end
270
+ end
271
+ expect(finder.results).to eq [1]
272
+ end
273
+ end
274
+
275
+ describe '#results?' do
276
+ it 'returns true if there are results' do
277
+ expect(new_finder([1, 2, 3], value: 1)).to be_results
278
+ end
279
+
280
+ it 'returns false if there are no results' do
281
+ expect(new_finder([1, 2, 3], value: 4)).not_to be_results
282
+ end
283
+ end
284
+
285
+ describe '#count' do
286
+ it 'counts the number of results' do
287
+ expect(new_finder([1, 2, 3], value: 1).count).to eq 1
288
+ end
289
+
290
+ it 'can not be bypassed by features' do
291
+ finder = new_finder [1, 2, 3] do
292
+ def fetch_results; end
293
+ end
294
+
295
+ expect(finder.count).to eq 3
296
+ end
297
+ end
298
+
299
+ describe '#params' do
300
+ it 'exports filters as params' do
301
+ finder = new_finder [], value: 1
302
+ expect(finder.params).to eq 'value' => 1
303
+ end
304
+
305
+ it 'can overwrite filters (mainly used for url handers)' do
306
+ finder = new_finder [], value: 1
307
+ expect(finder.params(value: 2)).to eq 'value' => 2
308
+ end
309
+
310
+ it 'ignores missing filters' do
311
+ finder = new_finder
312
+ expect(finder.params).to eq({})
313
+ end
314
+
315
+ it 'ignores invalid filters' do
316
+ finder = new_finder [], invalid: 'option'
317
+ expect(finder.params).to eq({})
318
+ end
319
+
320
+ it 'includes default filters' do
321
+ finder = new_finder do
322
+ filter_by :value, 1
323
+ end
324
+ expect(finder.params).to eq 'value' => 1
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+ require 'support/paging_shared_example'
3
+
4
+ module Trailblazer
5
+ class Finder
6
+ module Features
7
+ describe Paging do
8
+ it_behaves_like 'a paging feature'
9
+
10
+ it 'uses a pre-defined Hash entity_type instead of any ORM' do
11
+ true
12
+ end
13
+
14
+ def define_finder_class(&block)
15
+ Class.new do
16
+ include Trailblazer::Finder::Base
17
+ include Trailblazer::Finder::Features::Paging
18
+
19
+ instance_eval(&block) if block_given?
20
+ end
21
+ end
22
+
23
+ def finder_class # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
24
+ define_finder_class do
25
+ entity_type do
26
+ [
27
+ {
28
+ id: 1,
29
+ name: 'product_1',
30
+ price: '11',
31
+ created_at: Time.now,
32
+ updated_at: Time.now
33
+ },
34
+ {
35
+ id: 2,
36
+ name: 'product_2',
37
+ price: '12',
38
+ created_at: Time.now,
39
+ updated_at: Time.now
40
+ },
41
+ {
42
+ id: 3,
43
+ name: 'product_3',
44
+ price: '13',
45
+ created_at: Time.now,
46
+ updated_at: Time.now
47
+ },
48
+ {
49
+ id: 4,
50
+ name: 'product_4',
51
+ price: '14',
52
+ created_at: Time.now,
53
+ updated_at: Time.now
54
+ },
55
+ {
56
+ id: 5,
57
+ name: 'product_5',
58
+ price: '15',
59
+ created_at: Time.now,
60
+ updated_at: Time.now
61
+ },
62
+ {
63
+ id: 6,
64
+ name: 'product_6',
65
+ price: '15',
66
+ created_at: Time.now,
67
+ updated_at: Time.now
68
+ }
69
+ ]
70
+ end
71
+
72
+ per_page 2
73
+
74
+ min_per_page 2
75
+ max_per_page 10
76
+ end
77
+ end
78
+
79
+ def finder_with_page(page = nil, per_page = nil)
80
+ finder_class.new page: page, per_page: per_page
81
+ end
82
+
83
+ it 'can be inherited' do
84
+ child_class = Class.new(finder_class)
85
+ expect(child_class.new.per_page).to eq 2
86
+ end
87
+
88
+ describe '#results' do
89
+ it 'paginates results' do
90
+ finder = finder_with_page 2, 2
91
+ expect(finder.results.map { |n| n[:name] }).to eq %w[product_3 product_4]
92
+ end
93
+ end
94
+
95
+ describe '#count' do
96
+ it 'gives the real count' do
97
+ finder = finder_with_page 1
98
+ expect(finder.count).to eq 6
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+ require 'support/sorting_shared_example'
3
+
4
+ module Trailblazer
5
+ class Finder
6
+ module Features
7
+ describe Sorting do
8
+ it 'uses a pre-defined Hash entity_type instead of any ORM' do
9
+ true
10
+ end
11
+
12
+ def finder_class # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
13
+ Class.new do
14
+ include Trailblazer::Finder::Base
15
+ include Trailblazer::Finder::Features::Sorting
16
+
17
+ entity_type do
18
+ [
19
+ {
20
+ id: 1,
21
+ name: 'product_1',
22
+ price: '11',
23
+ created_at: Time.now,
24
+ updated_at: Time.now
25
+ },
26
+ {
27
+ id: 2,
28
+ name: 'product_2',
29
+ price: '12',
30
+ created_at: Time.now,
31
+ updated_at: Time.now
32
+ },
33
+ {
34
+ id: 3,
35
+ name: 'product_3',
36
+ price: '13',
37
+ created_at: Time.now,
38
+ updated_at: Time.now
39
+ },
40
+ {
41
+ id: 4,
42
+ name: 'product_4',
43
+ price: '14',
44
+ created_at: Time.now,
45
+ updated_at: Time.now
46
+ },
47
+ {
48
+ id: 5,
49
+ name: 'product_5',
50
+ price: '15',
51
+ created_at: Time.now,
52
+ updated_at: Time.now
53
+ }
54
+ ]
55
+ end
56
+
57
+ sortable_by :name, :price, :created_at
58
+
59
+ filter_by :name
60
+ filter_by :price
61
+ filter_by(:category) { |entity_type, _| entity_type.joins(:category) }
62
+ end
63
+ end
64
+
65
+ def finder_with_sort(sort = nil, filters = {})
66
+ finder_class.new filter: { sort: sort }.merge(filters)
67
+ end
68
+
69
+ it 'can be inherited' do
70
+ child_class = Class.new(finder_class)
71
+ expect(child_class.new.sort_attribute).to eq 'name'
72
+ end
73
+
74
+ describe 'sorting' do
75
+ it 'sorts results based on the sort option desc' do
76
+ finder = finder_with_sort 'price desc'
77
+ expect(finder.results.map { |n| n[:price] }).to eq %w[15 14 13 12 11]
78
+ end
79
+
80
+ it 'sorts results based on the sort option asc' do
81
+ finder = finder_with_sort 'price asc'
82
+ expect(finder.results.map { |n| n[:price] }).to eq %w[11 12 13 14 15]
83
+ end
84
+
85
+ it 'defaults to first sort by option' do
86
+ finder = finder_with_sort
87
+ expect(finder.results.map { |n| n[:name] }).to eq %w[product_5 product_4 product_3 product_2 product_1]
88
+ end
89
+
90
+ it 'ignores invalid sort values' do
91
+ finder = finder_with_sort 'invalid attribute'
92
+ expect { finder.results.to_a }.not_to raise_error
93
+ end
94
+ end
95
+
96
+ it_behaves_like 'a sorting feature'
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ module Trailblazer
4
+ class Finder
5
+ describe Features do
6
+ def define_finder_class(&block)
7
+ Class.new do
8
+ include Trailblazer::Finder::Base
9
+ include Trailblazer::Finder::Features
10
+
11
+ instance_eval(&block) if block_given?
12
+ end
13
+ end
14
+
15
+ it '.adapters' do
16
+ finder_class = define_finder_class do
17
+ entity_type { 'entity_type' }
18
+ end
19
+
20
+ expect(finder_class).to respond_to(:features)
21
+ end
22
+
23
+ describe Paging do
24
+ it 'can load Paging feature' do
25
+ expect do
26
+ define_finder_class do
27
+ entity_type { 'entity_type' }
28
+ features Paging
29
+ end
30
+ end.not_to raise_error
31
+ end
32
+ end
33
+
34
+ describe Sorting do
35
+ it 'can load Sorting feature' do
36
+ expect do
37
+ define_finder_class do
38
+ entity_type { 'entity_type' }
39
+ features Sorting
40
+ end
41
+ end.not_to raise_error
42
+ end
43
+ end
44
+
45
+ it 'raises an error on non-existing feature' do
46
+ expect do
47
+ define_finder_class do
48
+ entity_type { 'entity_type' }
49
+ features Unknown
50
+ end
51
+ end.to raise_error NameError
52
+ end
53
+ end
54
+ end
55
+ end