trailblazer-finder 0.1.0

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