kiroshi 0.1.1 → 0.3.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.
- checksums.yaml +4 -4
- data/.codacy.yml +13 -0
- data/.markdownlint.json +6 -0
- data/.rubocop_todo.yml +4 -8
- data/README.md +62 -68
- data/lib/kiroshi/filter.rb +46 -24
- data/lib/kiroshi/filter_query/exact.rb +3 -3
- data/lib/kiroshi/filter_query/like.rb +30 -6
- data/lib/kiroshi/filter_query.rb +10 -14
- data/lib/kiroshi/filter_runner.rb +47 -35
- data/lib/kiroshi/filters/class_methods.rb +172 -0
- data/lib/kiroshi/filters.rb +89 -78
- data/lib/kiroshi/version.rb +1 -1
- data/lib/kiroshi.rb +3 -23
- data/spec/lib/kiroshi/filter_query/exact_spec.rb +41 -7
- data/spec/lib/kiroshi/filter_query/like_spec.rb +53 -12
- data/spec/lib/kiroshi/filter_runner_spec.rb +51 -26
- data/spec/lib/kiroshi/filter_spec.rb +9 -12
- data/spec/lib/kiroshi/filters/class_methods_spec.rb +103 -0
- data/spec/lib/kiroshi/filters_spec.rb +315 -9
- data/spec/spec_helper.rb +1 -0
- data/spec/support/db/schema.rb +1 -0
- metadata +6 -2
@@ -3,16 +3,16 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
RSpec.describe Kiroshi::Filters, type: :model do
|
6
|
-
|
7
|
-
|
6
|
+
subject(:filters_class) { Class.new(described_class) }
|
7
|
+
|
8
|
+
let(:filter_instance) { filters_class.new(filters) }
|
9
|
+
let(:scope) { Document.all }
|
10
|
+
let(:filters) { {} }
|
8
11
|
|
9
|
-
|
10
|
-
let(:filters) { {} }
|
12
|
+
describe '#apply' do
|
11
13
|
let!(:document) { create(:document, name: 'test_name', status: 'finished') }
|
12
14
|
let!(:other_document) { create(:document, name: 'other_name', status: 'processing') }
|
13
15
|
|
14
|
-
let(:filters_class) { Class.new(described_class) }
|
15
|
-
|
16
16
|
context 'when no filters are configured' do
|
17
17
|
context 'when no filters are provided' do
|
18
18
|
it 'returns the original scope unchanged' do
|
@@ -91,8 +91,144 @@ RSpec.describe Kiroshi::Filters, type: :model do
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
+
context 'when filters have string keys' do
|
95
|
+
before do
|
96
|
+
filters_class.filter_by :name, match: :like
|
97
|
+
filters_class.filter_by :status
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'with single string key filter' do
|
101
|
+
let(:filters) { { 'name' => 'test' } }
|
102
|
+
|
103
|
+
it 'returns documents matching the string key filter' do
|
104
|
+
expect(filter_instance.apply(scope)).to include(document)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'does not return documents not matching the string key filter' do
|
108
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'generates SQL with LIKE operation for string key' do
|
112
|
+
expect(filter_instance.apply(scope).to_sql).to include('LIKE')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'with multiple string key filters' do
|
117
|
+
let(:filters) { { 'name' => 'test', 'status' => 'finished' } }
|
118
|
+
|
119
|
+
it 'returns documents matching all string key filters' do
|
120
|
+
expect(filter_instance.apply(scope)).to include(document)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'does not return documents not matching all string key filters' do
|
124
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'with mixed string and symbol keys' do
|
129
|
+
let(:filters) { { 'name' => 'test', status: 'finished' } }
|
130
|
+
|
131
|
+
it 'returns documents matching both string and symbol key filters' do
|
132
|
+
expect(filter_instance.apply(scope)).to include(document)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'does not return documents not matching all mixed key filters' do
|
136
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'treats string and symbol keys equivalently' do
|
140
|
+
string_result = filters_class.new({ 'name' => 'test', 'status' => 'finished' }).apply(scope)
|
141
|
+
symbol_result = filters_class.new({ name: 'test', status: 'finished' }).apply(scope)
|
142
|
+
|
143
|
+
expect(string_result.to_sql).to eq(symbol_result.to_sql)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'when filters is an instance of ActionController::Parameters' do
|
149
|
+
before do
|
150
|
+
filters_class.filter_by :name, match: :like
|
151
|
+
filters_class.filter_by :status
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'with permitted parameters' do
|
155
|
+
let(:filters) do
|
156
|
+
ActionController::Parameters.new(
|
157
|
+
name: 'test',
|
158
|
+
status: 'finished',
|
159
|
+
unauthorized_param: 'ignored'
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'returns documents matching the permitted parameters' do
|
164
|
+
expect(filter_instance.apply(scope)).to include(document)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'does not return documents not matching the permitted parameters' do
|
168
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'generates SQL with LIKE operation for ActionController::Parameters' do
|
172
|
+
expect(filter_instance.apply(scope).to_sql).to include('LIKE')
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'generates SQL with exact match for status parameter' do
|
176
|
+
expect(filter_instance.apply(scope).to_sql).to include("'finished'")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context 'with unpermitted parameters' do
|
181
|
+
let(:filters) do
|
182
|
+
ActionController::Parameters.new(
|
183
|
+
name: 'test',
|
184
|
+
status: 'finished'
|
185
|
+
)
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'works with unpermitted parameters' do
|
189
|
+
expect(filter_instance.apply(scope)).to include(document)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'does not return documents not matching the parameters' do
|
193
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'with string keys in ActionController::Parameters' do
|
198
|
+
let(:filters) do
|
199
|
+
ActionController::Parameters.new(
|
200
|
+
'name' => 'test',
|
201
|
+
'status' => 'finished'
|
202
|
+
)
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'returns documents matching the string key parameters' do
|
206
|
+
expect(filter_instance.apply(scope)).to include(document)
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'does not return documents not matching the string key parameters' do
|
210
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'treats ActionController::Parameters with string keys same as regular hash' do
|
214
|
+
ac_params_result = filter_instance.apply(scope)
|
215
|
+
hash_result = filters_class.new({ 'name' => 'test', 'status' => 'finished' }).apply(scope)
|
216
|
+
|
217
|
+
expect(ac_params_result.to_sql).to eq(hash_result.to_sql)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context 'with empty ActionController::Parameters' do
|
222
|
+
let(:filters) { ActionController::Parameters.new({}) }
|
223
|
+
|
224
|
+
it 'returns the original scope unchanged' do
|
225
|
+
expect(filter_instance.apply(scope)).to eq(scope)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
94
230
|
context 'when scope has joined tables with clashing fields' do
|
95
|
-
let(:scope)
|
231
|
+
let(:scope) { Document.joins(:tags) }
|
96
232
|
let(:filters) { { name: 'test_name' } }
|
97
233
|
|
98
234
|
let!(:first_tag) { Tag.find_or_create_by(name: 'ruby') }
|
@@ -128,7 +264,6 @@ RSpec.describe Kiroshi::Filters, type: :model do
|
|
128
264
|
let(:filters) { { name: 'test' } }
|
129
265
|
|
130
266
|
before do
|
131
|
-
filters_class.instance_variable_set(:@filter_configs, [])
|
132
267
|
filters_class.filter_by :name, match: :like
|
133
268
|
end
|
134
269
|
|
@@ -144,7 +279,7 @@ RSpec.describe Kiroshi::Filters, type: :model do
|
|
144
279
|
|
145
280
|
it 'generates SQL with table-qualified LIKE operation' do
|
146
281
|
result = filter_instance.apply(scope)
|
147
|
-
expect(result.to_sql).to include('documents.name LIKE')
|
282
|
+
expect(result.to_sql).to include('"documents"."name" LIKE')
|
148
283
|
end
|
149
284
|
|
150
285
|
it 'generates SQL with correct LIKE pattern' do
|
@@ -153,5 +288,176 @@ RSpec.describe Kiroshi::Filters, type: :model do
|
|
153
288
|
end
|
154
289
|
end
|
155
290
|
end
|
291
|
+
|
292
|
+
context 'when specifying a different column' do
|
293
|
+
let(:scope) { Document.joins(:tags) }
|
294
|
+
let(:filters) { { tag_name: 'ruby' } }
|
295
|
+
|
296
|
+
let!(:ruby_tag) { Tag.find_or_create_by(name: 'ruby') }
|
297
|
+
let!(:js_tag) { Tag.find_or_create_by(name: 'javascript') }
|
298
|
+
|
299
|
+
before do
|
300
|
+
filters_class.filter_by :tag_name, table: :tags, column: :name
|
301
|
+
|
302
|
+
document.tags << [ruby_tag]
|
303
|
+
other_document.tags << [js_tag]
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'filters by the specified column name instead of filter key' do
|
307
|
+
expect(filter_instance.apply(scope)).to include(document)
|
308
|
+
end
|
309
|
+
|
310
|
+
it 'does not return documents not matching the column filter' do
|
311
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'generates SQL that filters by tags.name using the column parameter' do
|
315
|
+
expect(filter_instance.apply(scope).to_sql).to include('"tags"."name"')
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'generates SQL that includes the filter value' do
|
319
|
+
expect(filter_instance.apply(scope).to_sql).to include("'ruby'")
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'does not use the filter key name in the SQL' do
|
323
|
+
# The filter key is :tag_name but column is :name, so SQL should use 'name' not 'tag_name'
|
324
|
+
expect(filter_instance.apply(scope).to_sql).not_to include('tag_name')
|
325
|
+
end
|
326
|
+
|
327
|
+
context 'with LIKE matching' do
|
328
|
+
let(:filters) { { tag_name: 'rub' } }
|
329
|
+
|
330
|
+
before do
|
331
|
+
filters_class.filter_by :tag_name, table: :tags, column: :name, match: :like
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'applies LIKE matching to the specified column' do
|
335
|
+
expect(filter_instance.apply(scope)).to include(document)
|
336
|
+
end
|
337
|
+
|
338
|
+
it 'generates SQL with LIKE operation on the specified column' do
|
339
|
+
expect(filter_instance.apply(scope).to_sql).to include('"tags"."name" LIKE')
|
340
|
+
end
|
341
|
+
|
342
|
+
it 'generates SQL with correct LIKE pattern' do
|
343
|
+
expect(filter_instance.apply(scope).to_sql).to include("'%rub%'")
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
context 'with different filter key and column names' do
|
348
|
+
let(:filters) { { user_full_name: 'test' } }
|
349
|
+
|
350
|
+
before do
|
351
|
+
filters_class.filter_by :user_full_name, column: :name, match: :like
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'uses the column name in database queries' do
|
355
|
+
result = filter_instance.apply(Document.all)
|
356
|
+
expect(result.to_sql).to include('"documents"."name"')
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'does not use the filter key in SQL' do
|
360
|
+
result = filter_instance.apply(Document.all)
|
361
|
+
expect(result.to_sql).not_to include('user_full_name')
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
context 'when filter was defined in the superclass' do
|
367
|
+
subject(:filters_class) { Class.new(parent_class) }
|
368
|
+
|
369
|
+
let(:parent_class) { Class.new(described_class) }
|
370
|
+
let(:filters) { { name: 'test_name' } }
|
371
|
+
|
372
|
+
before do
|
373
|
+
parent_class.filter_by :name
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'applies the filter defined in the parent class' do
|
377
|
+
expect(filter_instance.apply(scope)).to include(document)
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'does not return documents not matching the inherited filter' do
|
381
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
382
|
+
end
|
383
|
+
|
384
|
+
it 'generates SQL that includes the filter value from parent class' do
|
385
|
+
result = filter_instance.apply(scope)
|
386
|
+
expect(result.to_sql).to include("'test_name'")
|
387
|
+
end
|
388
|
+
|
389
|
+
context 'when child class adds its own filter' do
|
390
|
+
let(:filters) { { name: 'test_name', status: 'finished' } }
|
391
|
+
|
392
|
+
before do
|
393
|
+
filters_class.filter_by :status
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'applies both parent and child filters' do
|
397
|
+
expect(filter_instance.apply(scope)).to include(document)
|
398
|
+
end
|
399
|
+
|
400
|
+
it 'does not return documents not matching all filters' do
|
401
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
context 'when child class overrides parent filter' do
|
406
|
+
let(:filters) { { name: 'test' } }
|
407
|
+
|
408
|
+
before do
|
409
|
+
filters_class.filter_by :name, match: :like
|
410
|
+
end
|
411
|
+
|
412
|
+
it 'uses the child class filter configuration' do
|
413
|
+
expect(filter_instance.apply(scope)).to include(document)
|
414
|
+
end
|
415
|
+
|
416
|
+
it 'does not use the parent class filter configuration' do
|
417
|
+
expect(filter_instance.apply(scope).to_sql)
|
418
|
+
.to include('LIKE')
|
419
|
+
end
|
420
|
+
|
421
|
+
it 'generates SQL that includes LIKE operation with the filter value' do
|
422
|
+
expect(filter_instance.apply(scope).to_sql)
|
423
|
+
.to include("'%test%'")
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
context 'when child class overrides parent filter with table qualification' do
|
428
|
+
let(:scope) { Document.joins(:tags) }
|
429
|
+
let(:filters) { { name: 'ruby' } }
|
430
|
+
|
431
|
+
let!(:ruby_tag) { Tag.find_or_create_by(name: 'ruby') }
|
432
|
+
let!(:js_tag) { Tag.find_or_create_by(name: 'javascript') }
|
433
|
+
|
434
|
+
before do
|
435
|
+
filters_class.filter_by :name, table: :tags
|
436
|
+
|
437
|
+
document.tags << [ruby_tag]
|
438
|
+
other_document.tags << [js_tag]
|
439
|
+
end
|
440
|
+
|
441
|
+
it 'uses the child class table qualification (tags.name)' do
|
442
|
+
expect(filter_instance.apply(scope)).to include(document)
|
443
|
+
end
|
444
|
+
|
445
|
+
it 'does not return documents with different tag names' do
|
446
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
447
|
+
end
|
448
|
+
|
449
|
+
it 'generates SQL that filters by tags.name, not documents.name' do
|
450
|
+
expect(filter_instance.apply(scope).to_sql).to include('"tags"."name"')
|
451
|
+
end
|
452
|
+
|
453
|
+
it 'generates SQL that does not include documents.name' do
|
454
|
+
expect(filter_instance.apply(scope).to_sql).not_to include('"documents"."name"')
|
455
|
+
end
|
456
|
+
|
457
|
+
it 'generates SQL that includes the tag filter value' do
|
458
|
+
expect(filter_instance.apply(scope).to_sql).to include("'ruby'")
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
156
462
|
end
|
157
463
|
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/db/schema.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kiroshi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darthjee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -46,7 +46,9 @@ extensions: []
|
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
48
|
- ".circleci/config.yml"
|
49
|
+
- ".codacy.yml"
|
49
50
|
- ".gitignore"
|
51
|
+
- ".markdownlint.json"
|
50
52
|
- ".rspec"
|
51
53
|
- ".rubocop.yml"
|
52
54
|
- ".rubocop_todo.yml"
|
@@ -70,6 +72,7 @@ files:
|
|
70
72
|
- lib/kiroshi/filter_query/like.rb
|
71
73
|
- lib/kiroshi/filter_runner.rb
|
72
74
|
- lib/kiroshi/filters.rb
|
75
|
+
- lib/kiroshi/filters/class_methods.rb
|
73
76
|
- lib/kiroshi/version.rb
|
74
77
|
- spec/integration/readme/.keep
|
75
78
|
- spec/integration/yard/.keep
|
@@ -78,6 +81,7 @@ files:
|
|
78
81
|
- spec/lib/kiroshi/filter_query_spec.rb
|
79
82
|
- spec/lib/kiroshi/filter_runner_spec.rb
|
80
83
|
- spec/lib/kiroshi/filter_spec.rb
|
84
|
+
- spec/lib/kiroshi/filters/class_methods_spec.rb
|
81
85
|
- spec/lib/kiroshi/filters_spec.rb
|
82
86
|
- spec/lib/kiroshi_spec.rb
|
83
87
|
- spec/spec_helper.rb
|